Seth McClaine
Seth McClaine

Reputation: 10040

Contentful: documentToHtmlString doesn't include embedded image in rich text

I have the following rich text document

{  
   "data":{},
   "content":[  
      {  
         "data":{},
         "content":[  
            {  
               "data":{},
               "marks":[ ],
               "value":"test",
               "nodeType":"text"
            }, {  
               "data":{},
               "marks":[],
               "value":"",
               "nodeType":"text"
            }
         ],
         "nodeType":"paragraph"
      },
      {  
         "data":{  
            "target":{  
               "sys":{  
                  "space":{  
                     "sys":{  
                        "type":"Link",
                        "linkType":"Space",
                        "id":"gedg1u5b0yz9"
                     }
                  },
                  "id":"2CzKe2pWvewCiek6w0yyoQ",
                  "type":"Asset",
                  "createdAt":"2019-01-07T22:37:55.473Z",
                  "updatedAt":"2019-01-07T22:37:55.473Z",
                  "environment":{  
                     "sys":{  
                        "id":"master",
                        "type":"Link",
                        "linkType":"Environment"
                     }
                  },
                  "revision":1,
                  "locale":"en-US"
               },
               "fields":{  
                  "title":"Test Image",
                  "description":"Image for testing",
                  "file":{  
                     "url":"//images.ctfassets.net/<hidden>/<hidden>/<hidden>/IMG_2878.JPG",
                     "details":{  
                        "size":3874595,
                        "image":{  
                           "width":5184,
                           "height":3456
                        }
                     },
                     "fileName":"IMG_2878.JPG",
                     "contentType":"image/jpeg"
                  }
               }
            }
         },
         "content":[],
         "nodeType":"embedded-asset-block"
      },
      {  
         "data":{},
         "content":[  
            {  
               "data":{},
               "marks":[],
               "value":"",
               "nodeType":"text"
            }
         ],
         "nodeType":"paragraph"
      }
   ],
   "nodeType":"document"
}

When I use documentToHtmlString (from here https://www.npmjs.com/package/@contentful/rich-text-html-renderer)

documentToHtmlString(document);

It outputs the following

<p>test</p><p></p>

Anyone know how to get it to output the img tag as well?

Upvotes: 12

Views: 5915

Answers (3)

AbePralle
AbePralle

Reputation: 992

Here's a solution that doesn't rely on the fields property existing.

I'm using Contentful with SvelteKit via GraphQL. I don't know whether it's my setup or whether the Contentful API has changed, but the node data received by my renderNode options callback (as seen in the other answers) doesn't contain fields.

Contentful's recommended <custom-component> approach doesn't work for me in SvelteKit.

I use the renderNode callback to save the asset link info and return a unique text "marker" that is embedded into the result string. After documentToHtmlString() completes, I fetch the image associated with each link and then search-and-replace the marker with the final HTML.

Define this function richTextToHTML() and call html = await richTextToHTML(json) instead of html = documentToHtmlString(json). You may need to replace contentfulFetch() with your query call and adjust the resulting <img> width and height.

async function richTextToHTML( json )
{
    const asset_links = [];
    const options = {
        renderNode: {
            [BLOCKS.EMBEDDED_ASSET]:
                (node) => { return `[embedded-asset-block:${asset_links.push(node.data.target.sys)}]`; }
        }
    };

    let html = documentToHtmlString( json, options );

    for (let i=0; i<asset_links.length; ++i) {
        const link = asset_links[i]
        const asset_query = `
        {
            asset(id:"${link.id}")
            {
                title
                description
                url
                width
                height
            }
        }
        `

        let replacement = '';
        const response = await contentfulFetch(asset_query);
        if (response.ok) {
            const asset = (await response.json()).data.asset;
            replacement = `<img src="${asset.url}" alt="${asset.description}" style="max-width:800px; height:auto;">`;
            const marker = `[embedded-asset-block:${i+1}]`;
            html = html.replace( marker, replacement );
        }
    }

    return html;
}

Upvotes: 0

Seth McClaine
Seth McClaine

Reputation: 10040

From https://github.com/contentful/rich-text/issues/58#issuecomment-452236848

You need to specify how to render that You can find more about it here https://github.com/contentful/rich-text/tree/master/packages/rich-text-html-renderer#usage

import { BLOCKS } from '@contentful/rich-text-types';
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';

const options = {
  renderNode: {
    [BLOCKS.EMBEDDED_ENTRY]: (node) => `<custom-component>${customComponentRenderer(node)}</custom-component>`
  }
}

documentToHtmlString(document, options);

My specific resolution was:

const 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}"/>`,
    },
};

Upvotes: 14

David R
David R

Reputation: 193

const options = {
    renderNode: {
        [BLOCKS.EMBEDDED_ASSET]: ({ data: { target: { fields }}}) =>
        <div dangerouslySetInnerHTML={{__html: `<img src="${fields.file['en-GB'].url}" alt="${fields.title['en-GB']}"/>`}} />,
    },
};

I found i needed to set dangerouslySetInnerHTML for it to render properly in the browser.

Upvotes: 1

Related Questions