I am trying to render the bulletpoints using the Contentful Rich Text HTML renderer, however, the result is the following screenshot, where the bullet point list I want to result becomes [object Object]:
The following is my code:
<script>
import { documentToHtmlString } from '#contentful/rich-text-html-renderer'
import { BLOCKS } from '#contentful/rich-text-types'
const contentful = require('contentful')
const config = {
space: '341l220skrs9',
accessToken: 'a2kEigyr-81a4J8sPyN1-UNU9R9eO3cPQDdKb0GkRWc',
}
const client = contentful.createClient(config)
export default {
async asyncData({ params }) {
const projects = await client.getEntries({
'fields.slug': params.post,
content_type: 'blogPost',
})
const newProjects = projects.items[0].fields
console.log('DDD', projects)
return {
newProjectss: newProjects,
}
},
data() {
return {
options: {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: ({
data: {
target: { fields },
},
}) =>
`<img src="${fields.file.url}" height="${fields.file.details.image.height}" width="${fields.file.details.image.width}" alt="${fields.description}"/>`,
[BLOCKS.UL_LIST]: (node, children) => <ul>{children}</ul>,
[BLOCKS.OL_LIST]: (node, children) => <ol>{children}</ol>,
[BLOCKS.LIST_ITEM]: (node, children) => <li>{children}</li>,
},
},
}
},
methods: {
documentToHtmlString(text) {
return documentToHtmlString(text.body, this.options)
},
},
}
</script>
And this is what I am trying to render:
Related
I have recently migrated from Gatsby v3 to v4 and doing so caused images to not be generated correctly.
I have a simple data structure that has an imageUrl which I am trying to create a node for and then I am trying to create the image with createRemoteFileNode ('gatsby-source-filesystem').
Example Repo: https://github.com/stretch0/gatsby-sandbox
// source nodes
const testNodes = [
{
name: "My Node 1",
imageUrl: "https://images.unsplash.com/photo-1665081661649-8656335a6cbb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1738&q=80"
}
]
const sourceNodes = async ({ actions, createNodeId, createContentDigest }, options) => {
testNodes.forEach((testNode) => {
const node = {
...testNode,
id: createNodeId(`myNode-${testNode.name}`),
}
actions.createNode({
...node,
internal: {
type: 'myNode',
contentDigest: createContentDigest(node),
},
});
})
};
// on create nodes
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
const onCreateNode = async ({ node, cache, store, actions: { createNode }, createNodeId }) => {
if( node.internal.type === 'myNode') {
const myNode = await createRemoteFileNode({
url: node.imageUrl,
parentNodeId: node.id,
createNode,
createNodeId,
cache,
store,
});
if (myNode) {
myNode.imageData___NODE = myNode.id;
}
}
};
I am defining my schema like so:
module.exports = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type myNode implements Node {
id: String!
name: String!
imageUrl: String!
imageData: File #link(from: "imageData___NODE")
}
`;
createTypes(typeDefs);
};
But when I try query the imageData via graphql it appears the image file hasn't been created. Am I doing something wrong / has something changed between v3 and v4?
I'm trying to implement meilisearch api in React native and it is working fine with my simulator and after I publish the app some of the users cannot see the data returning from meilisearch, the error is
{"name":"Invariant Violation","framesToPop":1}
This is my code
Meilisearch.js
import axios from 'axios';
import { meilisearchConfig } from '../Config';
const MeilisearchApi = async (payload, success, failed) => {
try {
const response = await axios({
method: 'post',
url: `${meilisearchConfig?.host}indexes/activities/search`,
data: payload,
headers: {
'X-Meili-API-Key': meilisearchConfig?.apiKey,
},
});
success?.(response?.data);
} catch (err) {
failed?.(err);
}
};
export default MeilisearchApi;
This is the normalizer for returning data
import moment from 'moment';
import { IActivity, IByDateGroupFilter } from 'reducers/types';
export const activityNormalizer = (state, { hits, offset }) => {
const {
melisearchActivityData: { byDate, dates, all },
} = state;
const isRefreshing = offset === 0;
const newAll = isRefreshing ? hits : [...all, ...hits];
const datesNew: string[] = isRefreshing ? [] : dates;
const byDateNew: any = isRefreshing ? {} : byDate;
const byDateGroup: IByDateGroupFilter[] = [];
hits.forEach((activity: IActivity) => {
const date = getFormattedDate(activity.created_at);
if (byDateNew[date]) byDateNew[date].push({ ...activity });
else {
byDateNew[date] = [{ ...activity }];
datesNew.push(date);
}
});
Object.keys(byDateNew).forEach((key) => {
byDateGroup.push({
title: key,
data: byDateNew[key],
});
});
return {
dates: datesNew,
byDate: byDateNew,
byDateGroup,
all: newAll,
};
};
This is how i call my Meilisearch API method
MeilisearchApi(
{
q: search,
filters: filters,
offset: newOffset,
limit: PAGE_SIZE,
},
({ hits }: { hits: any[] }) => {
setDataLoaded(true);
setMelisearchActivitiesToRedux({ hits, offset: newOffset });
if (newOffset === 0) {
sectionList?.current?.scrollToLocation({
itemIndex: 1,
});
}
},
(err: any) => {
setDataLoaded(true);
log(err)
},
);
No Idea how this error happens, when users kill the app and logging again this works fine
I've added a blog page that I integrated with NetlifyCMS and it works wonderful. However, I need to have one more dynamic page on my site that displays services and each service will have its own generated template, will pretty much be very similar to the blog page in theory.
So I read around a bunch of different solutions but kept hitting a wall. What am I doing wrong here? and whats the simplest way to make this work, as I wont be adding any other dynamic pages moving forward.
Here was my gatsby-node.js when I just had the blog page and everything worked great:
const { createFilePath } = require("gatsby-source-filesystem")
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
//Blog
async function getPageData(graphql) {
return await graphql(`
{
blogPosts: allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
}
const path = require(`path`)
exports.createPages = async ({ graphql, actions }) => {
const { data } = await getPageData(graphql)
data.blogPosts.edges.forEach(({ node }) => {
const { slug } = node.fields
actions.createPage({
path: `/articles${slug}`,
component: path.resolve("./src/templates/articlepost.js"),
context: {slug: slug },
})
})
}
So I took a stab it and tried to do the same thing but adding my services page. Here is what that ended up looking like:
gatsby-node.js
const { createFilePath } = require("gatsby-source-filesystem")
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
async function getBlogData(graphql) {
return await graphql(`
{
blogPosts: allMarkdownRemark(
filter: { frontmatter: { template: { eq: "articlepost" } } }
) {
edges {
node {
fields {
slug
}
}
}
}
}
`)
}
async function getServiceData(graphql) {
return await graphql(`
{
servicePosts: allMarkdownRemark(
filter: { frontmatter: { template: { eq: "service" } } }
) {
edges {
node {
fields {
slug
}
}
}
}
}
`)
}
const path = require(`path`)
exports.createPages = async ({ graphql, actions }) => {
const blogPage = path.resolve("./src/templates/articlepost.js")
const servicePage = path.resolve("./src/templates/service.js")
const { data } = await getBlogData(graphql)
data.blogPosts.edges.forEach(({ node }) => {
const { slug } = node.fields
actions.createPage({
path: `/articles${slug}`,
component: blogPage,
context: { slug: slug },
})
})
const { serviceData } = await getServiceData(graphql)
serviceData.servicePosts.edges.forEach(({ node }) => {
const { slug } = node.fields
actions.createPage({
path: `/streaming-services${slug}`,
component: servicePage,
context: { slug: slug },
})
})
}
My blog page still works fine but I get error:
As you can see I added a template type to my config.yml, that now shows in my frontmatter to separate my posts, but I think it may have just confused me a little more. Totally fine with removing the template types from frontmatter, but really need a very simple way for this to work.
Many thanks in advance!
Try aliasing your loops to avoid scoped elements.
const { data } = await getBlogData(graphql)
data.blogPosts.edges.forEach(({ node: post }) => {
const { slug } = post.fields
actions.createPage({
path: `/articles${slug}`,
component: blogPage,
context: { slug: slug },
})
})
And:
serviceData.servicePosts.edges.forEach(({ node: service }) => {
const { slug } = service.fields
actions.createPage({
path: `/streaming-services${slug}`,
component: servicePage,
context: { slug: slug },
})
})
Basically, you are aliasing the loop item to make them unique (different from the previous loop) as post and service respectively.
I would recommend reading this article from David Walsh.
const { serviceData } = await getServiceData(graphql)
doesn't work as no serviceData in object returned from graphql( (returned by getServiceData) - it's always a data named property.
You can reuse data (if earlier data not required later) or use alias in destructuring:
const { data: serviceData } = await getServiceData(graphql)
i'm new to gatsby and trying to programatically create pages using different templates, but I'm struggling with that.
I have two different pages (landing & press), both written in mdx files, that need different routes and templates when being created programmatically.
my landing works good, but i failed to add the "press" page. everything i tried didn't work.
for information, i have a templateKey 'press' and 'landing' in each frontmatter of my markdown files.
Here is my gatsby-node file :
const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
const { fmImagesToRelative } = require("gatsby-remark-relative-images");
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
const landingTemplate = path.resolve("src/templates/landing.js");
// const pressTemplate = path.resolve("./src/templates/press.js");
return graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
locale
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const posts = result.data.allMarkdownRemark.edges;
posts.forEach((edge) => {
const id = edge.node.id;
createPage({
path: edge.node.fields.slug,
component: landingTemplate,
context: {
locale: edge.node.frontmatter.locale,
id,
},
});
});
});
};
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node); // convert image paths for gatsby images
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value,
});
}
};
here is my query in graphiQL
Thank you in advance for any assistance that you may be able to give me !
You have to make two different queries to get all your press and all landing data (filtering by each templateKey). Once you have the different populated objects you need to call for each object the createPage API to create pages and pass each template value. It should look like:
const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
const { fmImagesToRelative } = require("gatsby-remark-relative-images");
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions;
const landingTemplate = path.resolve("src/templates/landing.js");
const pressTemplate = path.resolve("./src/templates/press.js");
const postQuery = await graphql(`
{
allMarkdownRemark(
limit: 1000
filter: { frontmatter: { templateKey: { eq: "press" }}},
) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
locale
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const posts = postQuery.data.allMarkdownRemark.edges;
posts.forEach((edge) => {
const id = edge.node.id;
createPage({
path: edge.node.fields.slug,
component: pressTemplate,
context: {
locale: edge.node.frontmatter.locale,
id,
},
});
});
});
const landingQuery = await graphql(`
{
allMarkdownRemark(
limit: 1000
filter: { frontmatter: { templateKey: { eq: "landing" }}},
) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
locale
}
}
}
}
}
`).then((result) => {
if (result.errors) {
result.errors.forEach((e) => console.error(e.toString()));
return Promise.reject(result.errors);
}
const landings = landingQuery.data.allMarkdownRemark.edges;
landings.forEach((edge) => {
const id = edge.node.id;
createPage({
path: edge.node.fields.slug,
component: landingTemplate,
context: {
locale: edge.node.frontmatter.locale,
id,
},
});
});
});
};
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
fmImagesToRelative(node); // convert image paths for gatsby images
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value,
});
}
};
Your mistake was that you were returning your GraphQL query and that limit your capability to create new queries. Note that I've also added a promise-based approach, with an async and await functions in order to optimize the queries and the timings.
Without knowing your data structure I've duplicated the query changing the templateKey value, of course, you will need to customize the data that you want to gather for each template and the data you want to pass via context API.
So I created a method based on a netlify CMS starter, which it looks like you attempted as well, that makes sure you only make the query once:
exports.createPages = async ({graphql,actions }) => {
const { data } = await graphql(`
query Pages {
allMarkdownRemark {
nodes {
frontmatter {
slug
template
}
}
}
}
`)
data.allMarkdownRemark.nodes.forEach(node => {
actions.createPage({
path: `${node.frontmatter.slug}`,
component: path.resolve(
`./src/templates/${String(node.frontmatter.template)}.js`
),
context: {
slug: node.frontmatter.slug,
},
})
})
}
Then in the template it self you can call this query:
export const aboutPageQuery = graphql`
query AboutPage($slug: String) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
frontmatter {
title
story {
text
title
}
}
}
}
`
Note: I'm still working through a method to include templates based on path. Say if you had a blog directory within pages that you wanted to apply a single template (blog-page.js) and custom path (/blog/{slug}).
how can i handle image uploads in graphql
Through multer using express route to handle upload and query from graphql to view the images and other data
app.use('/graphql', upload);
app.use('/graphql', getData, graphqlHTTP(tokenData => ({
schema,
pretty: true,
tokenData,
graphiql: true,
})));
This is a duplicate of How would you do file uploads in a React-Relay app?
In short, yes you can do a file upload in graphql with react + relay.
You need to write the Relay update store action, for example:
onDrop: function(files) {
files.forEach((file)=> {
Relay.Store.commitUpdate(
new AddImageMutation({
file,
images: this.props.User,
}),
{onSuccess, onFailure}
);
});
},
Then implement a mutation for the Relay store
class AddImageMutation extends Relay.Mutation {
static fragments = {
images: () => Relay.QL`
fragment on User {
id,
}`,
};
getMutation() {
return Relay.QL`mutation{ introduceImage }`;
}
getFiles() {
return {
file: this.props.file,
};
}
getVariables() {
return {
imageName: this.props.file.name,
};
}
getFatQuery() {
return Relay.QL`
fragment on IntroduceImagePayload {
User {
images(first: 30) {
edges {
node {
id,
}
}
}
},
newImageEdge,
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'User',
parentID: this.props.images.id,
connectionName: 'images',
edgeName: 'newImageEdge',
rangeBehaviors: {
'': 'prepend',
},
}];
}
}
In you server-side schema, preform update
const imageMutation = Relay.mutationWithClientMutationId({
name: 'IntroduceImage',
inputFields: {
imageName: {
type: new GraphQL.GraphQLNonNull(GraphQL.GraphQLString),
},
},
outputFields: {
newImageEdge: {
type: ImageEdge,
resolve: (payload, args, options) => {
const file = options.rootValue.request.file;
//write the image to you disk
return uploadFile(file.buffer, filePath, filename)
.then(() => {
/* Find the offset for new edge*/
return Promise.all(
[(new myImages()).getAll(),
(new myImages()).getById(payload.insertId)])
.spread((allImages, newImage) => {
const newImageStr = JSON.stringify(newImage);
/* If edge is in list return index */
const offset = allImages.reduce((pre, ele, idx) => {
if (JSON.stringify(ele) === newImageStr) {
return idx;
}
return pre;
}, -1);
return {
cursor: offset !== -1 ? Relay.offsetToCursor(offset) : null,
node: newImage,
};
});
});
},
},
User: {
type: UserType,
resolve: () => (new myImages()).getAll(),
},
},
mutateAndGetPayload: (input) => {
//break the names to array.
let imageName = input.imageName.substring(0, input.imageName.lastIndexOf('.'));
const mimeType = input.imageName.substring(input.imageName.lastIndexOf('.'));
//wirte the image to database
return (new myImages())
.add(imageName)
.then(id => {
//prepare to wirte disk
return {
insertId: id,
imgNmae: imageName,
};
});
},
});
All the code above you can find them in this repo https://github.com/bfwg/relay-gallery
There is also a live demo http://fanjin.io