Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gatsby-transformer-contentful-richtext should provide a documentToJSX method #54

Closed
polarathene opened this issue Dec 21, 2018 · 8 comments
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@polarathene
Copy link

polarathene commented Dec 21, 2018

gatsby-transformer-contentful-richtext being React based should support converting the document output to JSX(very common with Gatsby afaik?).

@sarahbethfederman has been so kind as to write up a version here.


If done, and since this should involve updating the Gatsby plugin docs, it'd be good to show an example of a custom renderNode option that also has inner content like a link, and a common question that'll probably be sought after, how to use the Gatsby <Link /> element for internal links.:

(Note this is pseudo-code, I don't think the node side tends to like js files with import statements instead of require statements?)

// `./components/link.js`
import React from 'react'
import {INLINES, BLOCKS, MARKS} from '@contentful/rich-text-types';
import {Link as GatsbyLink} from "gatsby"

// Taken from: https://www.gatsbyjs.org/docs/gatsby-link/
// If given link begins with a single `/`, treat as internal Gatsby Link
const Link = ({children, to, activeClassName, ...other}) => {
  const internal = /^\/(?!\/)/.test(to);

  // Use Gatsby Link for internal links, and <a> for others
  if (internal) {
    return (
      <GatsbyLink to={to} activeClassName={activeClassName} {...other}>
        {children}
      </GatsbyLink>
    )
  }
  return (
    <a href={to} {...other}>
      {children}
    </a>
  )
}

const Options = {
  renderOptions: {
    renderNode: {
      [INLINES.HYPERLINK]: (node, next) => <Link to="${node.data.uri}">${next(node.content)}</Link>
    }
  }
}

export default Options


// `./gatsby-config.js`
const renderOptions = require("./components/link");

module.exports = {
  // ..other config
  plugins: [
    {
      resolve: "gatsby-source-contentful",
      options: {
        spaceId,
        accessToken
      }
    }, {
      resolve: '@contentful/gatsby-transformer-contentful-richtext',
      options: renderOptions
    },
    // ..other plugins
  ]
}

I noticed the docs mention a custom html element like this:

[MARKS.BOLD]: text => `<custom-bold>${text}<custom-bold>`,

No closing back slash on the tag? The naming convention doesn't suggest that it's a React/JSX styled custom element/component, so I assume that's not related to Gatsby/React?

I assume we're meant to be able to at the very least integrate the Link component from Gatsby. Presently I am doing this with react-jsx-parser on the provided html string. I also have the renderOption for the plugin to create a <Link/> element for internal links.

This isn't full proof though, I assume parsing html elements like hr or img which JSX requires having closing back slashes might break it(if a documentToJSX method is made available, others elements like img might need to be catered for).

// `./gatsby-config.js`
const { BLOCKS, MARKS, INLINES } = require('@contentful/rich-text-types')

const renderOptions = {
  renderOptions: {
    renderNode: {
      [INLINES.HYPERLINK]: (node, next) => {
        // If given link begins with a single `/`, treat as internal Gatsby Link
        return /^\/(?!\/)/.test(node.data.uri)
        ? `<Link to="${node.data.uri}">${next(node.content)}</Link>`
        : `<a href="${node.data.uri}">${next(node.content)}</a>`
      }
    },
  },
}

module.exports = {
  // ..other config
  plugins: [
    {
      resolve: "gatsby-source-contentful",
      options: {
        spaceId,
        accessToken
      }
    }, {
      resolve: '@contentful/gatsby-transformer-contentful-richtext',
      options: renderOptions
    },
    // ..other plugins
  ]
}
// ./some-component.js
import React from 'react'

import JsxParser from 'react-jsx-parser'
import { Link } from 'gatsby'

const RichText = (props) => (
  <JsxParser
    components={{ Link }}
    jsx={props.content}
  />
)

Usage with example query on docs:

<RichText content={html} />
@Khaledgarbaya Khaledgarbaya added enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers labels Jan 2, 2019
@Khaledgarbaya
Copy link
Contributor

Hey @polarathene,
This is a great addition, do you want to take a stab at it?

@polarathene
Copy link
Author

@Khaledgarbaya How do you feel about the usage of JSX parser? I haven't looked into how it handles elements like <hr> or <img> where no back slash closing the tag is required... not sure if it handles that or breaks.

Do you think this is a preferred approach to @sarahbethfederman approach with a documentToJSX() method? Seems to have it's own technical issues if you want to override elements due to config being on the node side and the elements brought over not using require() but ES6+ imports? If that could be worked around it's probably a better approach than mine, note how my RichText component requires you to provide the components that will be used, and for the override it's string based(so a little more error prone).

@sarahbethfederman
Copy link

sarahbethfederman commented Jan 9, 2019

Hey, just wanted to post an update here. I've fixed a few issues here and wanted to share. Thanks to @ababol's work (https://gist.github.com/ababol/f1243d2b250ed20ccc9814f502a9a63b) I was able to fix the key issue and get more embedded entries working.

https://gist.github.com/sarahbethfederman/04613d376188f71a1995228f33c38328

@mgmolisani
Copy link
Contributor

mgmolisani commented Jan 11, 2019

So please forgive me as this will not only be the first github comment I've ever made but also the first code I will be attempting to collab on. Very nervous and not sure what proper etiquette is for working on things but I had taken a stab at this before looking at the open issues. I have been sourcing headless CMS for a family small business project and came across Contentful but also saw this need for a React transform since dangerouslySetInnerHTML limited flexibility with CSS in JS.

I looked at the code @sarahbethfederman provided but the key setting seems dangerous as it does not validate that a proper React element was passed before attempting to append the key in. This would cause text nodes to break in cases where the developer might write [MARKS.UNDERLINE]: text => text which would then spread your text string across your object letter by letter and add a key which would not be great. Since this is rich text, maybe a better approach is turning off underline in the CMS to avoid this but it is currently a valid callback.

I also considered the case where someone might want to pass a key manually on something like the embedded asset id. I can't think of a strong use case for this since this is again rich text and the idea is to pretty much mimic the text in the component, but it could in some crazy way be used to preserve state on a key that isn't relying on index.

I used the built in React functions to resolve both those issues (I think) and doesn't feel as hacky. See below:

import React, {isValidElement, cloneElement} from 'react';

const appendKeyToValidElement = (element, key) => {
  if (element && isValidElement(element) && element.key === null) {
    return cloneElement(element, {key});
  }
  return element;
};

I also reworked some of the other code to be a little more straightforward not involving the next callback (which may not be preferable in the end as it is not consistent with the other renderers, but this did however start as a personal solution to the problem). I really appreciate any feedback, especially since this is very new to me. Very excited to try to help regardless of reception. Feedback appreciated. Thanks.

index.js
full repo with tests reworked from html-renderer and some changes to test React specific renders

P.S. I have never written a React test so this was a challenge and I still don't feel like I did it right since there is so much setup involved but I ended up with snapshots that were all what I expected so hopefully that is something.

EDIT: Also worth noting that there is no need to escape the html as React does this by default hence the dangerously in 'dangerouslySetInnerHtml' for getting around it.

@mgmolisani
Copy link
Contributor

npm i -S @contentful/rich-text-react-renderer to the rescue!

@ColeTownsend
Copy link

@mgmolisani how does that work exactly with gatsby-transformer-contentful-richtext?

@mgmolisani
Copy link
Contributor

@ColeTownsend
See my response and comments below on issue #46 here . Hope this helps.

@Khaledgarbaya
Copy link
Contributor

closing this issue, for now, please npm i -S @contentful/rich-text-react-renderer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

5 participants