How to replace HTML images with GatsbyJS responsive images? - reactjs

I am querying data from the Directus CMS in GatsbyJS using the Directus 7 source like so:
query {
allDirectusBlog {
edges {
node {
name
body
}
}
}
}
My problem is that body is actually raw HTML with image tags pointing to my Directus server. Which is normally fine, however these images are extremely large and take a fair amount of time to load even over WiFi. Is there a way to replace these <img> tags with Gatsby responsive images at build time?

I've create a function using html-react-parser with the post content of WordPress to replace all img with of Gatsby and the image provide in the static folder.
If you want a way to do that, this is a part of my code that you'll be able to adapte for your project (this is a WIP function, but work well)
export const ParsePostContentHTML = dataContent => {
let indexKeyImg = 234;
const ParsedHTML = Parse(dataContent, {
replace: function(domNode) {
if(domNode.name === 'img') {
const fluidImg = data.allWordpressWpMedia.edges.filter(media => {
return media.node.source_url === domNode.attribs.src
})
if(fluidImg.length > 0) {
let srcMedia = (fluidImg[0].node.localFile.childImageSharp)
? fluidImg[0].node.localFile.childImageSharp.fluid
: fluidImg[0].node.localFile.publicURL
indexKeyImg++
if(fluidImg[0].node.localFile.childImageSharp) {
return (
<Img
key={indexKeyImg}
fluid={srcMedia}
className={`${domNode.attribs.class} gatsby-rendered-img`}
alt={fluidImg[0].node.alt_text}
/>
)
} else {
return (
<img
key={indexKeyImg}
src={srcMedia}
className={`${domNode.attribs.class} gatsby-rendered-img`}
alt={fluidImg[0].node.alt_text}
/>
)
}
}
}
}
})
return ParsedHTML
}
You just have to import this function in your components/template and using it with ParsePostContentHTML(YourPostContent)
Hope that help you!

Related

Uppy/Shrine: How to retrieve presigned url for video after successful upload (using AWS S3)

I'm using Uppy for file uploads in React, with a Rails API using Shrine.
I'm trying to show a preview for an uploaded video before submitting a form. It's important to emphasize that this is specifically for a video upload, not an image. So the 'thumbnail:generated' event will not apply here.
I can't seem to find any events that uppy provides that returns a cached video preview (like thumbnail:generated does) or anything that passes back a presigned url for the uploaded file (less expected, obviously), so the only option I see is constructing the url manually. Here's what I'm currently trying for that (irrelevant code removed for brevity):
import React, { useEffect, useState } from 'react'
import AwsS3 from '#uppy/aws-s3'
import Uppy from '#uppy/core'
import axios from 'axios'
import { DragDrop } from '#uppy/react'
import { API_BASE } from '../../../api'
const constructParams = (metadata) => ([
`?X-Amz-Algorithm=${metadata['x-amz-algorithm']}`,
`&X-Amz-Credential=${metadata['x-amz-credential']}`,
`&X-Amz-Date=${metadata['x-amz-date']}`,
'&X-Amz-Expires=900',
'&X-Amz-SignedHeaders=host',
`&X-Amz-Signature=${metadata['x-amz-signature']}`,
].join('').replaceAll('/', '%2F'))
const MediaUploader = () => {
const [videoSrc, setVideoSrc] = useState('')
const uppy = new Uppy({
meta: { type: 'content' },
restrictions: {
maxNumberOfFiles: 1
},
autoProceed: true,
})
const getPresigned = async (id, type) => {
const response = await axios.get(`${API_BASE}/s3/params?filename=${id}&type=${type}`)
const { fields, url } = response.data
const params = constructParams(fields)
const presignedUrl = `${url}/${fields.key}${params}`
console.log('presignedUrl from Shrine request data: ', presignedUrl)
setVideoSrc(presignedUrl)
}
useEffect(() => {
uppy
.use(AwsS3, {
id: `AwsS3:${Math.random()}`,
companionUrl: API_BASE,
})
uppy.on('upload-success', (file, _response) => {
const { type, meta } = file
// First attempt to construct presigned URL here
const url = 'https://my-s3-bucket.s3.us-west-1.amazonaws.com'
const params = constructParams(meta)
const presignedUrl = `${url}/${meta.key}${params}`
console.log('presignedUrl from upload-success data: ', presignedUrl)
// Second attempt to construct presigned URL here
const id = meta.key.split(`${process.env.REACT_APP_ENV}/cache/`)[1]
getPresigned(id, type)
})
}, [uppy])
return (
<div className="MediaUploader">
<div className="Uppy__preview__wrapper">
<video
src={videoSrc || ''}
className="Uppy__preview"
controls
/>
</div>
{(!videoSrc || videoSrc === '') && (
<DragDrop
uppy={uppy}
className="UploadForm"
locale={{
strings: {
dropHereOr: 'Drop here or %{browse}',
browse: 'browse',
},
}}
/>
)}
</div>
)
}
export default MediaUploader
Both urls here come back with a SignatureDoesNotMatch error from AWS.
The manual construction of the url comes mainly from constructParams. I have two different implementations of this, the first of which takes the metadata directly from the uploaded file data in the 'upload-success' event, and then just concatenates a string to build the url. The second one uses getPresigned, which makes a request to my API, which points to a generated Shrine path that should return data for a presigned URL. API_BASE simply points to my Rails API. More info on the generated Shrine route here.
It's worth noting that everything works perfectly with the upload process that passes through Shrine, and after submitting the form, I'm able to get a presigned url for the video and play it without issue on the site. So I have no reason to believe Shrine is returning incorrectly signed urls.
I've compared the two presigned urls I'm manually generating in the form, with the url returned from Shrine after uploading. All 3 are identical in structure, but have different signatures. Here are those three urls:
presignedUrl from upload-success data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132613Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=97aefd1ac7f3d42abd2c48fe3ad50b542742ad0717a51528c35f1159bfb15609
presignedUrl from Shrine request data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/023592fb14c63a45f02c1ad89a49e5fd.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132619Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=7171ac72f7db2b8871668f76d96d275aa6c53f71b683bcb6766ac972e549c2b3
presigned url displayed on site after form submission:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132734Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=9ecc98501866f9c5bd460369a7c2ce93901f94c19afa28144e0f99137cdc2aaf
The first two urls come back with SignatureDoesNotMatch, while the third url properly plays the video.
I'm aware the first and third urls have the same file name, while the second url does not. I'm not sure what to make of that, though, but the relevance of this is secondary to me, since that solution was more of a last ditch effort anyway.
I'm not at all attached to the current way I'm doing things. It's just the only solution I could come up with, due to lack of options. If there's a better way of going about this, I'm very open to suggestions.

How to upload image to image column from SharePoint List using REST API(React)

I have a problem of uploading image to image column in sharepoint online via pnpjs
I don't know how to convert image and upload to image column in sharepoint list.
I tried lot of ways (by convert image to blob, filedata) nothing works.
Keep in much this is not an attachments for the list..
It's a new column(image) in sharepoint list
reference image click here
Looks like the image is not stored in the list. JSON is stored. So you can just upload image to site assets (that's what sharepoint does when you set the image manually) and then put the json to the field. I would try something like this (assuming you are using pnpjs)
import * as React from "react";
import { sp } from "#pnp/sp/presets/all";
// hello world react component
export const HelloWorld = () => {
const uploadFile = async (evt) => {
const file: File = evt.target.files[0];
// upload to the root folder of site assets in this demo
const assets = await sp.web.lists.ensureSiteAssetsLibrary();
const fileItem = await assets.rootFolder.files.add(file.name, file, true);
// bare minimum; probably you'll want other properties as well
const img = {
"serverRelativeUrl": fileItem.data.ServerRelativeUrl,
};
// create the item, stringify json for image column
await sp.web.lists.getByTitle("YourListWithImageColumn").items.add({
Title: "Hello",
YourImageColumn: JSON.stringify(img)
});
};
return (<div>
<input type='file' onChange={uploadFile} />
</div>);
};
#azarmfa,
The image file in fact did not store in the image field. The field just references its location. You could first upload the image file to a library (by default it will be site asset), then update the item like below:
let list = sp.web.lists.getByTitle("mylinks");
let json = {
"fileName": "Search_Arrow.jpg",
"serverUrl": "https://abc.sharepoint.com",
"serverRelativeUrl": "/sites/s01/Style%20Library/Images/Search_Arrow.jpg"
};
let jsonstr = JSON.stringify(json);
const i = await list.items.getById(3).update({
Title: "My New Tit",
img: jsonstr
});
BR

How to linkify hashtag in react

I use the linkify-react module with the hashtag plugin
import Linkify from 'linkifyjs/react';
import * as linkify from 'linkifyjs';
import hashtag from 'linkifyjs/plugins/hashtag';
hashtag(linkify);
I'm not find any solution to let the hashtags links work directly in jsx component, it's possible?
<Linkify options={linkifyOptions} >{content}</Linkify>
in alternative I'm trying to use the plugin. Whit the plugin I retrieve an array of all hashtag in the content
const pTag = linkify.find(p.content || '');
// here the output
{
"type": "hashtag",
"value": "#tag",
"href": "#tag"
}
How I can replace all the hashtag with a link in the text? I've tried this solution but not works
pTag.forEach( (tag) => {
content.replace(tag, 'example.com/'+tag);
})
You can use formatHref property to add URL to each hashtag as per Linkify's documentation
https://soapbox.github.io/linkifyjs/docs/options.html
var linkifyOptions =
{
formatHref: function (href, type) {
if (type === 'hashtag') {
href = 'https://twitter.com/hashtag/' + href.substring(1);
}
return href;
}
}
var content = 'Linkify is #super #rad2015';
return <Linkify options={linkifyOptions}>{content}</Linkify>;
Checkout complete code here
https://codesandbox.io/s/linkify-sxt8c?fontsize=14

Aurelia Multi-page App?

If I have main.ts file setup like so...
Main.ts
import {Aurelia} from 'aurelia-framework'
import environment from './environment';
//Configure Bluebird Promises.
(<any>Promise).config({
warnings: {
wForgottenReturn: false
}
});
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.feature('resources');
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin('aurelia-testing');
}
// PLAYING AROUND - Log to Console the Value of ShowLanding Session Storage
let showLanding = false;
console.log(showLanding);
// let showLanding = sessionStorage.getItem("show_landing");
if (showLanding || showLanding === null) {
aurelia.start().then(() => aurelia.setRoot('landing'));
} else {
aurelia.start().then(() => aurelia.setRoot('blog/blog'));
}
}
I have a "Landing.html/.ts" file in the root of my app, and this bit of code seems to work well. Meaning, if "showLanding = false" the app will load to my "blog.html" page, and if it's true, it will load to my "Landing.html" page.
What I'm trying to do is create an Admin page. Anytime URL is visited "....com/admin" take me to "admin.html" page I have setup.
Is that possible to do front-end? Only other way I know would be to match URL and static-serve from server route, yeah?
I managed to get this working (the way I wanted it to work) by just simply reading window.location.pathname and setting my admin page as app root.
so my Main.ts was changed to:
...
if (showLanding || showLanding === null) {
aurelia.start().then(() => aurelia.setRoot('landing'));
} else if (window.location.pathname == "/admin") {
aurelia.start().then(() => aurelia.setRoot('admin/admin'));
} else {
aurelia.start().then(() => aurelia.setRoot('blog/blog'));
}
}
I'm sure this probably isn't the best way to accomplish this, but it seems to be working for now. I'll be sure and update this if there are any issues I run into.
Also, if someone else wants to chime in with any alternative thoughts, concerns, suggestions, or feedback, please do! Thanks!

How do I search text in a single page reactjs/electron application

I have a react electron application and I want to add ctrl+f functionality to it. I added a globalshortcut that responds to ctrl+f and shows a text box, then on the textbox, I gave it a listener for changes which calls window.find().
Unfortunately this never searches in the page, it always returns false. I'm assuming this has something to do with the react components, is there a way to search and highlight text of all the components on the page?
code:
mainWindow.on('focus', () => {
globalShortcut.register('CmdorCtrl+F', () => {
mainWindow.webContents.send('find_request', 'search');
});
});
ipcRenderer.on('find_request', (event, arg) => {
const modalbox = document.getElementById('modalbox');
if (modalbox.style.display === 'block') {
modalbox.style.display = 'none';
} else {
modalbox.style.display = 'block';
}
});
searchPage(event) {
console.log(event)
console.log(window.find(event.value));
}
I figured this out and feel like a complete idiot:
In main.js
mainWindow.webContents.on('found-in-page', (event, result) => {
if (result.finalUpdate) {
mainWindow.webContents.stopFindInPage('keepSelection');
}
});
ipcMain.on('search-text', (event, arg) => {
mainWindow.webContents.findInPage(arg);
});
In the page:
static searchPage(event) {
if (event.target.value.lenght > 0) {
ipcRenderer.send('search-text', event.target.value);
}
}
To give you an alternative, there are components doing this for you so you don't need to build it yourself.
Had the same problem, and first fixed by using electron-in-page-search, but this component doesn't work properly with Electron 2 or greater.
Then finally found electron-find resolved my problem. Using with Electron 4.
You just add the component to your project:
npm install electron-find --save
Add a global shortcut in your Electron main process to send an event to the renderer in a ctrl+f:
globalShortcut.register('CommandOrControl+F', () => {
window.webContents.send('on-find');
});
And then you can add this to your page (the renderer process)
const remote = require('electron').remote;
const FindInPage = require('electron-find').FindInPage;
let findInPage = new FindInPage(remote.getCurrentWebContents());
ipcRenderer.on('on-find', (e, args) => {
findInPage.openFindWindow()
})
Hope that helps.

Resources