How to source images from a YAML array in Gatsby.js - reactjs

I'm developing a blog with Gatsby.js
Each post is a YAML file in which there is an array for the gallery like this:
gallery:
-'/uploads/image1.jpg'
-'/uploads/image2.jpg'
-'/uploads/image3.jpg'
-'/uploads/image4.jpg'
-'/uploads/image5.jpg'
in the post i have something like this:
const images = data.postData.frontmatter.gallery;
return (
{images.map((image, index) => {
return (
<img src={image}/>
)
}
)};
export const query = graphql`
query PostData($slug: String!) {
postData: markdownRemark(fields: {slug: {eq: $slug}}) {
frontmatter {
gallery
}
}
}
`;
But the images don't show up as they are not processed and put in the static folder at build time.
As I understand it, the plugin 'gatsby-plugin-sharp' is not transforming the images that are found in the array in the YAML file, but it does when it's just one image...
(in some of the post there is a field like this
main-image: 'path/to/the/image'
which then I can source with graphql like this:
main-image {
fluid {
src
}
}
}
and instead for the 'gallery' array the 'fluid' node doesn't get created.)
I hope this makes some sense, I realise I'm a bit confused about how some things, I hope you can help me understand some stuff.
Thanks,
M
EDIT
I went a bit forwards thanks to #Z. Zlatev.
I insert this in gatsby-node.js:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type MarkdownRemark
implements Node {
frontmatter: Frontmatter
}
type Frontmatter {
gallery: [File]
}
`;
createTypes(typeDefs);
};
and now nodes are created for each image in the gallery array.
However, querying the images I get null...
Here some details:
The YAML File:
---
date: 2019-11-06T13:47:07.000+00:00
title: Cool Project
main_picture: "/uploads/simon-matzinger-Gpck1WkgxIk-unsplash.jpg"
gallery:
- "/uploads/PROEGELHOEF.jpg"
- "/uploads/swapnil-dwivedi-N2IJ31xZ_ks-unsplash-1.jpg"
- "/uploads/swapnil-dwivedi-N2IJ31xZ_ks-unsplash.jpg"
- "/uploads/simon-matzinger-Gpck1WkgxIk-unsplash.jpg"
---
Here the GraphQl query:
query MyQuery {
allMarkdownRemark(filter: {id: {eq: "af697225-a842-545a-b5e1-4a4bcb0baf87"}}) {
edges {
node {
frontmatter {
title
gallery {
childImageSharp {
fluid {
src
}
}
}
}
}
}
}
}
Here the data response:
{
"data": {
"allMarkdownRemark": {
"edges": [
{
"node": {
"frontmatter": {
"title": "Cool Project",
"gallery": [
{
"childImageSharp": null
},
{
"childImageSharp": null
},
{
"childImageSharp": null
},
{
"childImageSharp": null
}
]
}
}
}
]
}
}
}
I guess I'm still missing something...

I will try to explain how this works in Gatsby. First of all, it's the gatsby-transformer-sharp plugin that's transforming your File nodes to ImageSharp nodes. gatsby-plugin-sharp is of course involved too as a low-level API.
The main issue you have is that Gatsby can't recognize(infer) your data as reference to files. Once it does a chain of transformation will automatically kick in. Gatsby actually tries to figure out if string is a file path but those paths must be relative to the file they are found in.
Consider the following example:
gatsby-project
├── content
│   ├── images
│   │   └── home.png
│   └── pages
│   └── home.yml
└── src
content/pages/home.yml
title: Homepage
url: /
image: ../images/home.png
The easiest solution would be to provide a correct relative paths in your yaml files. Sadly we know that's not always possible. An example of that are files created by NetlifyCMS. If this is your case too try some of the existing solutions like:
https://www.gatsbyjs.org/packages/gatsby-plugin-netlify-cms-paths/
Since Gatsby 2.2.0 the createSchemaCustomization API exists that allows us to handle such scenarios more gracefully by defining custom resolvers and field extensions but it may be daunting for people who are not familiar with GraphQL. Read more about it here.

I solved by installing this: https://www.npmjs.com/package/#forestryio/gatsby-remark-normalize-paths
Thanks for putting me in the right direction.
M

Related

How to register two different service-workers in the same scope?

I have a service-worker.js file to make my reactjs app PWA. I now also want to add push notification using FCM, which requires me to have firebase-messaging-sw.js in the public folder. So now for both to work both are going to require to be in the same scope.
But as far as I have read from various answers on this site, we can't have two different service workers in the same scope, so how do we combine both service-worker.js and firebase-messaging-sw.js so both can function properly. One of the answers suggested that I rename the service-worker.js to firebase-messaging-sw.js which doesn't work. I did find one successful implementation on GitHub which I didn't understand much https://github.com/ERS-HCL/reactjs-pwa-firebase .
How can I have both the service-worker.js and firebase-messaging-sw.js work together?
firebase-messaging-sw.js
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
apiKey: "xxxx",
authDomain: "xxxx.firebaseapp.com",
projectId: "xxxx",
storageBucket: "xxxx",
messagingSenderId: "xxxx",
appId: "xxxx",
measurementId: "xxxx"
}
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
self.addEventListener("notificationclick", function (event) {
console.debug('SW notification click event', event)
const url = event.notification.data.link
event.waitUntil(
clients.matchAll({type: 'window'}).then( windowClients => {
// Check if there is already a window/tab open with the target URL
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
// If so, just focus it.
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// If not, then open the target URL in a new window/tab.
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
})
messaging.onBackgroundMessage(async function(payload) {
console.log("Received background message ", payload)
const notificationTitle = payload.notification.title
const notificationOptions = {
body: payload.notification.body,
icon: './logo192.png',
badge: './notification-badgex24.png',
data: {
link: payload.data?.link
}
}
self.registration.showNotification(notificationTitle, notificationOptions)
})
service-worker.js
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
Ok, after spending weeks, I figured it out. So for anyone else, using the service worker created using the default create react app, follow the steps below.
first create firebase-messaging-sw.js in public folder and put the same code as in the question, but this time also add importScripts("/service-worker.js").
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js")
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js")
importScripts(`/service-worker.js`) // <- here notice this
.
.
.
The import will import the service-worker.js after the build step. The service worker you have in the src folder is only a template. You cannot use importScript in the service-worker.js file that's in the src folder as that will throw importScripts is not defined error.
Your build folder after your build step:
└── build/
├── static/
│ ├── .
│ ├── .
│ └── .
├── index.html
├── firebase-messaging-sw.js
├── service-worker.js
├── .
└── .
Now in index.html add
<script>
if ('serviceWorker' in navigator){
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then(reg => console.debug("Service worker registered sucessfully"))
}
</script>
And that's it. Both firebase-messaging and your PWA service worker will work
or you can also
You can create a new file called sw.js in your public folder and use imporScript() to import both firebase-messaging-sw.js and default service-worker.js
make sure to pass in service worker registration in getToken of the firebase
const registration = await navigator.serviceWorker.register('/sw.js')
const currentToken = await getToken(messaging, {
serviceWorkerRegistration: registration,
vapidKey: '<VAPID_KEY>'
})
now simply register your new service worker in index file as shown
I think one way of doing this is just having only firebase-messaging-sw.js file and using workbox to inject your service-worker.js into it
https://developer.chrome.com/docs/workbox/precaching-with-workbox/
forexample :
// build-sw.js
import {injectManifest} from 'workbox-build';
injectManifest({
swSrc: './src/sw.js',
swDest: './dist/firebase-messaging-sw.js',
globDirectory: './dist',
globPatterns: [
'**/*.js',
'**/*.css',
'**/*.svg'
]
});
and all firebase config must be in sw.js to be written in firebase-messaging-sw.js
and in your package.json simply run build-sw.js before react-script start
"scripts": {
"start": "node build-sw.js && react-scripts start",
}
or you can use react-app-rewired and workbox box plugin instead

Window.OneSignal showing 404 error when i am trying to use it with next.js

I am trying to implement OneSignal web push notifications with the next.js web app. I followed this article
to implement it. But it is not implementing properly as it shows an error. I have doubt that where should I place the window.OnseSignal code shown in step 7.
What I did?
I built a component name NewOneSignal instead of pasting it in App.js (because there is no App.js file in next.js) whose code is given below:
import React, { useEffect } from "react";
const NewOneSignal=()=>{
useEffect(()=>{
window.OneSignal = window.OneSignal || [];
const OneSignal = window.OneSignal;
},[]);
return (
OneSignal.push(()=> {
OneSignal.init(
{
appId: "i pasted my app id here", //STEP 9
promptOptions: {
slidedown: {
enabled: true,
actionMessage: "We'd like to show you notifications for the latest Jobs and updates about the following categories.",
acceptButtonText: "OMG YEEEEESS!",
cancelButtonText: "NAHHH",
categories: {
tags: [
{
tag: "governmentJobs",
label: "Government Jobs",
},
{
tag: "PrivateJobs",
label: "Private Jobs",
}
]
}
}
},
welcomeNotification: {
"title": "The website",
"message": "Thanks for subscribing!",
}
},
//Automatically subscribe to the new_app_version tag
OneSignal.sendTag("new_app_version", "new_app_version", tagsSent => {
// Callback called when tag has finished sending
console.log('new_app_version TAG SENT', tagsSent);
})
);
})
)
}
export default NewOneSignal;
And imported this component in the document.js file. According to this article, I have to put step 8 code in the useEffect but didn't work also, I have tried that also
I am very much sure that the problem is in this file. I paste the OneSignalsdk script in head section of the _document.js file.Also, i moved the two service worker files in a public folder as shown in the article. Please help me to make this code work

featuredImage does not appear in GraqhiQL , Gatsby

i am trying to develop a blog site for myself.and i decided to make it with gatsby and contentful. and i followed this tutorial
query code
query MyQuery {
allContentfulBlogPost {
edges {
node {
author {
name
}
createdAt
body {
body
}
title
featuredImage {
file {
url
}
}
}
}
}
}
output :
"message": "Cannot query field \"featuredImage\" on type \"ContentfulBlogPost\".",
why featuredImage does not appear in allContentfulBlogPost ? and how can i find it ?
my gatsby-config.js file:
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-image`,
`gatsby-transformer-remark`,
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 740,
wrapperStyle: `margin-bottom: 2.2rem;`,
},
},
i already added gatsby-image and gatsby-remark-images but it didnt help.
if you have any idea about this topic please respond.
any response would be appreciated
Gatsby provides GraphQL to fetch data without remark.
import { graphql } from "gatsby"
export const pageQuery = graphql`
query BlogPostBySlug($slug: String!) {
contentfulBlog(slug: { eq: $slug }) {
blogTitle
blogImage {
fluid (maxWidth: 500) {
...GatsbyContentfulFluid_withWebp
}
title
resize {
src
width
height
}
}
}
}
`
As you can see the query, you can fetch images using fluid or fixed.
I fetched image using fluid and webp since webp image type is a perfect choice for Lighthouse score.
And use Img tag(not img) to display image.
<Img fluid={post.blogImage.fluid} alt={post.blogImage.title} />
https://www.gatsbyjs.org/packages/gatsby-image/
The issue comes from Contentful side, it breaks before reaching your Gatsby app (when gathering data) that's why your gatsby-remark-images doesn't fix it.
When dealing with images in Contentful you must upload a dummy image for your new schema (featuredImage). Just an image to be retrieved with GraphQL query. Afterwards you can replace this image for your definitive content.
This is because internally, GraphQL schema is not able to infer the type of an image (it may happen with other fields too of other types) if you don't upload it at least once for your allContentfulBlogPost content model. It's a known bug that must be bypassed by this way until they fix it.

Gatsby site's images aren't showing on mobile devices

I'm building portfolio site with Gatsby+Wordpress combination. If I run this setup locally or at Github pages everything seems to look normal when using desktop/laptop. If I visit site which is published to Github pages and view with mobile device images aren't showing at all.
I found this solution and added it to my gatsby-node.js like this:
const _ = require(`lodash`)
const Promise = require(`bluebird`)
const path = require(`path`)
const slash = require(`slash`)
// This is the solution I found but it's not working in my case
// ----------
if (process.env.NODE_ENV === "development") {
process.env.GATSBY_WEBPACK_PUBLICPATH = "/"
}
// ----------
exports.createPages = ({ graphql, actions }) => {
...
I didn't found any other solutions and it seems that I can't solve it by myself.
Link to site
Link to repo
Hopefully I provided enough information so you can catch the idea, if not ask and I tell more. Thanks in advance!
I got this working by changing the way I query images. At first I used for example this:
query {
wordpressWpPortfolio {
acf {
portfolio_gallery {
source_url
}...
This produced wrong kind of URLs for images. Images pointed to my localhost instead of folder inside my repo.
I changed query method to this:
query {
wordpressWpPortfolio {
acf {
portfolio_gallery {
localFile {
childImageSharp {
fluid(maxWidth: 500, quality: 100) {
src
srcSet
aspectRatio
sizes
base64
}...
This query for gatsby-image had to be done manually because gatsby-config.js doesn't support fragments like:
fixed(width: 300, height: 300) {
...GatsbyImageSharpFixed
}
This line in your gatsby config looks to be the issue: baseUrl: "localhost:8888/robertsalmi.fi",
I suspect the wordpress plugin uses that to prefix all the images, as your live site is showing them all with that base url. You'll need to provide the correct base, so it can build the image links properly.

Kibana 6.1.1 Custom Plugin (visualization)

I'm pretty new to Kibana, and I'm trying to make my own visualization custom plugin (for kibana 6.1.1).
At that moment I just want to see something on the screen that says "hello world" or something.
Firstly, this is my folder structure:
.
├── package.json
├── public
| ├── mainTemplate.html
| ├── optionTemplate.html
| ├── mortaController.js
| └── morta.js
├── index.js
This is morta.js looks like:
import 'plugins/morta/mortaController';
//core methods
import {CATEGORY} from 'ui/vis/vis_category';
import {VisFactoryProvider} from 'ui/vis/vis_factory';
import {VisSchemasProvider} from 'ui/vis/editors/default/schemas';
import {VisTypesRegistryProvider} from 'ui/registry/vis_types';
//templates
import mainTemplate from 'plugins/morta/mainTemplate.html';
import optionTemplate from 'plugins/morta/optionTemplate.html';
VisTypesRegistryProvider.register(MortaProvider);
function MortaProvider(Private) {
const VisFactory = Private(VisFactoryProvider);
const Schemas = Private(VisSchemasProvider);
return VisFactory.createAngularVisualization({
name: "morta",
title: "Morta Vis",
icon: "fa-terminal",
description: "Morta visualization",
category: CATEGORY.BASIC,
visConfig: {
defaults: {},
template: mainTemplate
},
editorConfig: {
optionsTemplate: optionTemplate,
schemas: new Schemas([{
group: 'metrics',
name: 'test_metrics',
title: "Testing metrics",
min: 1,
max: 1,
aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'std_dev'],
defaults: [
{schema: 'metric', type: 'count'}
]
}])
}
});
}
export default MortaProvider;
This is my controller:
import { uiModules } from 'ui/modules';
const module = uiModules.get('morta', ['kibana']);
module.controller('MortaController', mortaController);
mortaController.$inject = ['$scope'];
function mortaController($scope){
let vm = this;
}
This is the mainTemplate:
<div data-ng-controller="MortaController as vm">
<h1>Morta Visualize View</h1>
</div>
This is the optionTemplate:
<p>Test Options</p>
I've got my kibana and elasticsearch servers up and running, then i'm trying to create a new visualization with my custom plugin but i'm getting an error saying :
"Visualize: cannot read property 'group' of undefined"
I'm not sure if I'm missing something or doing something wrong, let me know if you need more information that I can provide.
This can happen in the create visualization screen after changing the name and forcing a refresh.
name: 'test_metrics',
Exit out of the 'create visualization' screen e.g. click the 'visualize' tab, and the '+' create visualization button to get back to where you were
This worked for me (on version 6.3.2)

Resources