gatsby-source-strapi icon indicating copy to clipboard operation
gatsby-source-strapi copied to clipboard

Parse markdown to mdx?

Open mdxprograms opened this issue 4 years ago • 8 comments

Is it possible to make use of mdx when pulling markdown content? The main idea is that I'd like to create a sort of "shortcodes" setup to where when the content is pulled in it will use the components the content editor defined in the markdown (mdx style) in strapi.

This would work by adding a globalScope for mdx

module.exports = {
  plugins: [
    {
      resolve: `gatsby-mdx`,
      options: {
        globalScope: `import { SketchPicker } from 'react-color';
export default { Picker: SketchPicker }`
      }
    }
  ]
};

So then if a content type had a content body with:

<Picker />

It would then render that component that exists within Gatsby as long as the content is parsed with mdx.

Is this possible?

reference: https://www.christopherbiscardi.com/post/towards-shortcodes-for-gatsby-sites/#introducing-globalscope

Also a video of using DatoCMS with mdx: https://www.youtube.com/watch?v=Cwarh7qpEe0

mdxprograms avatar Nov 15 '19 20:11 mdxprograms

Proof on Concept

I've created PoC of that. It assumes that:

  • There is model called article in Strapi
  • article model has content, which is type of rich text
  • You have gatsby-plugin-mdx enabled

Then, in your gatsby-node.js, create onCreateNode function, which basically does three things:

  1. Checks if node type is StrapiArticle
  2. Creates new node with mediaType equal to text/markdown - it's crucial to do so, because gatsby-plugin-mdx now will be able to parse content as MDX
  3. Links this new node with current node

So it looks like this:

module.exports.onCreateNode = async ({ node, actions, createNodeId }) => {
    if (node.internal.type === "StrapiArticle") {
        const newNode = {
            id: createNodeId(`StrapiArticleContent-${node.id}`),
            parent: node.id,
            children: [],
            internal: {
                content: node.content || " ",
                type: "StrapiArticleContent",
                mediaType: "text/markdown",
                contentDigest: crypto
                    .createHash("md5")
                    .update(node.content || " ")
                    .digest("hex"),
            },
        };
        actions.createNode(newNode);
        actions.createParentChildLink({
            parent: node,
            child: newNode,
        });
    }
};

Now you are able to do similar GraphQL query:

query MyQuery {
  allStrapiArticle {
    nodes {
      id,
      title,
      childStrapiArticleContent {
        childMdx {
          body
        }
      }
    }
  }
}

After that, you can use it in the pages, even with shortcodes, like this:

{allStrapiArticle.nodes.map((article, i) => (
    <article key={i}>
        <header>
            <h2>{article.text}</h2>
        </header>
        <section>
            <MDXProvider
                components={{
                    Hi: props => <h3 {...props} />,
                }}
            >
                <MDXRenderer>
                    {article.childStrapiArticleContent.childMdx.body}
                </MDXRenderer>
            </MDXProvider>
        </section>
    </article>
))}

Of course, in Strapi, create an article with the content. Mine looked like this:

# Hello

<Hi>I'm an shortcode!</Hi>

Future work

Since there is no API to Strapi to get model, I'd place definition of "markdownable" content in the plugin configuration. So for example:

{
    resolve: "gatsby-source-strapi",
    options: {
        apiURL: "http://localhost:1337",
        contentTypes: [
            {
                type: "article",
                markdownFields: ["content"],
            },
            "user",
        ],
        queryLimit: 1000,
    },
},

If I'll find some time for that, I can submit a PR for this feature.

CC?: @lauriejim

rozpuszczalny avatar Nov 29 '19 09:11 rozpuszczalny

This is great @rozpuszczalny! I'll give it a go.

mdxprograms avatar Dec 04 '19 01:12 mdxprograms

btw @rozpuszczalny this worked perfectly

imported these two

import { MDXProvider } from "@mdx-js/react"
import { MDXRenderer } from "gatsby-plugin-mdx"

mdxprograms avatar Dec 15 '19 19:12 mdxprograms

There actually is an API to get the content type schema: /content-type-builder/content-types. image

It should be possible to query this information during startup and then create the correct sub nodes as https://github.com/strapi/gatsby-source-strapi/issues/89#issuecomment-559731259 showed (Related to #5)

olee avatar Mar 07 '20 20:03 olee

Hey guys... what's the progress of this feature? It'll be amazing having such a thing like this

raulfdm avatar Aug 15 '20 11:08 raulfdm

Any progress in this feature. Currently using onCreateNode

techesuraj avatar Jan 17 '21 20:01 techesuraj

Hi there, thank you to @rozpuszczalny for the POC. The creation of nodes works very well. I am trying to create pages programmatically from the model that has mdx content. I have used the POC to create the nodes, and that works very well. But when I add the create pages code, I run into an error. My gatsby-node.js file:

const path = require(`path`);
const crypto = require(`crypto`)

exports.onCreateNode = async ({ node, actions, createNodeId }) => {
  if (node.internal.type === "StrapiPublications") {
      const newNode = {
          id: createNodeId(`StrapiPublications-${node.slug}`),
          parent: node.slug,
          children: [],
          internal: {
              content: node.Content || " ",
              type: "StrapiPublicationsContent",
              mediaType: "text/markdown",
              contentDigest: crypto
                .createHash("md5")
                .update(node.Content || " ")
                .digest("hex"),
          },
      };
      actions.createNode(newNode);
      actions.createParentChildLink({
          parent: node,
          child: newNode,
      });
    }
};


const makeRequest = (graphql, request) => new Promise((resolve, reject) => {
  // Query for nodes to use in creating pages.
  resolve(
    graphql(request).then(result => {
      if (result.errors) {
        reject(result.errors)
      }
      
      return result;
    })
  )
});

// Implement the Gatsby API “createPages”. This is called once the
// data layer is bootstrapped to let plugins create pages from data.
exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions;
  
  const getPages = makeRequest(graphql, `
    {
      allStrapiPublications {
        edges {
          node {
            id
            slug
          }
        }
      }
    }
    `).then(result => {
    // Create pages for each article.
    result.data.allStrapiPublications.edges.forEach(({ node }) => {
      createPage({
        path: `/${node.slug}`,
        component: path.resolve(`src/templates/publication.js`),
        context: {
          id: node.id
        },
      });
    })
  });
  
  // Query for articles nodes to use in creating pages.
  return getPages;
};

When I run gatsby develop (having already developed the site previously), I receive this error:

There was an error in your GraphQL query:

Cannot query field "childStrapiPublicationsContent" on type "StrapiPublications".

If you don't expect "childStrapiPublicationsContent" to exist on the type "StrapiPublications" it is most    
likely a typo.
However, if you expect "childStrapiPublicationsContent" to exist there are a couple of solutions to common   
problems:

- If you added a new data source and/or changed something inside gatsby-node.js/gatsby-config.js, please try 
a restart of your development server
- The field might be accessible in another subfield, please try your query in GraphiQL and use the GraphiQL  
explorer to see which fields you can query and what shape they have
- You want to optionally use your field "childStrapiPublicationsContent" and right now it is not used        
anywhere. Therefore Gatsby can't infer the type and add it to the GraphQL schema. A quick fix is to add at   
least one entry with that field ("dummy content")

It is recommended to explicitly type your GraphQL schema if you want to use optional fields. This way you    
don't have to add the mentioned "dummy content". Visit our docs to learn how you can define the schema for   
"StrapiPublications":
https://www.gatsbyjs.com/docs/reference/graphql-data-layer/schema-customization#creating-type-definitions    

File: src\templates\publication.js:28:9

failed extract queries from components - 0.321s

I can clear this error by using the gatsby clean command. Next time I run gatsby develop or gatsby build, no error occurs. But if I run it again (without the gatsby clean command), the same error comes up.

Any ideas on how to resolve this issue? My thinking is that when the site is developed/built from cache, the nodes haven't been created yet. But I am not sure how to tell gatsby to wait until node creation to build the pages. I am trying to debug using gatsby's documentation here: async lifecycles and gatsby node apis

Thanks very much in advanced!

Edit: I figured out the issue: the parent id is important, I set it back to parent: node.id and it works.

mzusev avatar Sep 08 '21 12:09 mzusev

For those interested, when using Strapi v4 all rich text nodes will get automatically the text/markdown media type. No need to use the onCreateNode method as described above. Just add gatsby-plugin-mdx and you will find the childMdx nodes in GraphQL. Just make sure you use V3 and not V4 of the gatsby-plugin-mdx plugin as:

"Loading MDX from other sources as the file system is not yet supported in gatsby-plugin-mdx@^4.0.0"

I'm using this in combination with Gatsby V4 and its working fine.

rsaarloos avatar Aug 23 '22 20:08 rsaarloos

Thanks for your interest in this project. This plugin is moving into the Gatsby User Collective and this repo will be archived. Please open an issue in that repository, submit a PR if you'd like to see this implemented, or join us on Discord if you have questions!

moonmeister avatar Dec 27 '22 18:12 moonmeister