Render blocks in RTE in headlesss project

I wonder if any has handled blocks inside RTE in headless project?

We get the following from Delivery API:

{
  "rowSpan": 1,
  "columnSpan": 12,
  "areaGridColumns": 12,
  "areas": [
    {
      "alias": "left",
      "rowSpan": 1,
      "columnSpan": 6,
      "items": [
        {
          "rowSpan": 1,
          "columnSpan": 6,
          "areaGridColumns": 12,
          "areas": [],
          "content": {
            "contentType": "blockText",
            "id": "5dd49927-1388-4755-81b0-f0b1662e9b1d",
            "properties": {
              "icon": null,
              "tagline": null,
              "heading": null,
              "text": {
                "markup": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n<umb-rte-block class=\"ng-scope ng-isolate-scope\" data-content-id=\"dd2a5d09-677c-4544-8337-e8f9a34a1ff2\"></umb-rte-block>\n<p>Bacon ipsum dolor amet beef pork loin filet mignon chuck swine cow flank andouille bacon. Chislic pastrami bacon flank jerky. Flank shank fatback chislic strip steak, ball tip frankfurter doner sausage corned beef shankle short loin porchetta landjaeger. Pork belly tongue spare ribs t-bone flank fatback, sirloin chislic kielbasa. Spare ribs shank porchetta t-bone. Landjaeger hamburger burgdoggen, bacon frankfurter capicola swine meatball leberkas jowl tenderloin.</p>",
                "blocks": [
                  {
                    "content": {
                      "contentType": "blockInfoPopup",
                      "id": "dd2a5d09-677c-4544-8337-e8f9a34a1ff2",
                      "properties": {
                        "image": [
                          {
                            "focalPoint": null,
                            "crops": [],
                            "id": "34e381d3-6864-40bb-bb88-11afa9fd0964",
                            "name": "Bearded Man With Phone",
                            "mediaType": "Image",
                            "url": "https://localhost:44370/media/0wapgnde/bearded-man-with-phone.jpg",
                            "extension": "jpg",
                            "width": 3500,
                            "height": 2333,
                            "bytes": 3566964,
                            "properties": {}
                          }
                        ],
                        "heading": "Hava laasad trenzsa gwo producgs su Idfo braid yop quiel",
                        "text": "Duis aute in voluptate velit esse cillum dolore eu fugiat nulla  pariatur. At vver eos et accusam dignissum qui blandit est praesent.  Trenz pruca beynocguon25 doas nog apoply su trenz ucu hugh rasoluguon  monugor or trenz ucugwo jag scannar....",
                        "cta": {
                          "items": [
                            {
                              "content": {
                                "contentType": "blockButton",
                                "id": "060ed857-f301-43dd-a7bd-f6ce5841f7bf",
                                "properties": {
                                  "text": "Læs mere",
                                  "link": [
                                    {
                                      "url": null,
                                      "queryString": null,
                                      "title": "Viden",
                                      "target": null,
                                      "destinationId": "36e281be-e66d-410c-8743-d7cf19e9392f",
                                      "destinationType": "standardPage",
                                      "route": {
                                        "path": "/viden/",
                                        "startItem": {
                                          "id": "16d35c1c-622d-458a-bf61-efff24aea339",
                                          "path": "forside"
                                        }
                                      },
                                      "linkType": "Content"
                                    }
                                  ]
                                }
                              },
                              "settings": {
                                "contentType": "blockSettingsButton",
                                "id": "59a57dd1-86cc-44e8-a0b7-6ae371670c27",
                                "properties": {
                                  "buttonStyle": "btn-primary"
                                }
                              }
                            }
                          ]
                        }
                      }
                    },
                    "settings": null
                  }
                ]
              },
              "cta": null
            }
          },
          "settings": {
            "contentType": "blockSettingsText",
            "id": "2ad51e2a-f0ea-4fb7-a53d-3d49dc9fc2e4",
            "properties": {
              "textAlignment": "text-start",
              "showMoreOnMobile": false
            }
          }
        }
      ]
    },
    {
      "alias": "right",
      "rowSpan": 1,
      "columnSpan": 6,
      "items": []
    }
  ],
  "content": {
    "contentType": "blockTwoColumnSection",
    "id": "8d2c280c-e34f-48e6-927d-e60bf393ef15",
    "properties": {}
  },
  "settings": {
    "contentType": "blockSettingsSection",
    "id": "8c2029dd-7605-402e-80db-575c0fb2f3c5",
    "properties": {
      "anchorReference": null,
      "horizontalAlignment": null,
      "backgroundColor": null,
      "backgroundImage": null
    }
  }
}

where it inserts the RTE block:

<umb-rte-block class="ng-scope ng-isolate-scope" data-content-id="dd2a5d09-677c-4544-8337-e8f9a34a1ff2"></umb-rte-block>

between two paragraph elements in markup and additionally has the block data in blocks property.

Some regex replacement?

I will forward your question to an old colleague, i think he found a solution.

But im not sure, because i suggested to disable blocks in RTE

1 Like

Just a thought, I’ve not tried this so no idea if it would work, I’m pretty sure you can override the Property Value Converter (it has a different name and I don’t know exactly what it is) for the RTE/TipTap that is used by the Delivery API - you could use this to pre-convert the block data for the RTE into fully formed markup potentially, then return that. Not sure if it will work though, just a thought.

An initially attempt if the order doesn’t matter and only a few blocks are used within RTE:

<div class="text" v-if="model.text?.markup" v-html="model.text.markup"></div>
<UmbracoBlockItem v-if="model.text?.blocks" v-for="block in model.text?.blocks" :item="{ content: block.content, settings: block.settings }" />

UmbracoBlockItem is a component we have to render BlockList/BlockGrid block.

Hi,
We are handling this in react/nextjs using this to create a collection of components that are either the p tag or a mapped Element for the block

Hope it helps
Matt

export const splitMarkupWithBlocks = (markup: string, blocks: DynamicContainer[]) => {
  const placeholderRegex = /<umb-rte-block[^>]*data-content-id="([^"]+)"[^>]*><\/umb-rte-block>/g;
  const result: (string | DynamicContainer | undefined)[] = [];
  let lastIndex = 0;

  // Replace placeholders and populate the result array
  markup.replace(placeholderRegex, (match, contentId, offset) => {
    // Add the text before the placeholder
    if (offset > lastIndex) {
      result.push(markup.slice(lastIndex, offset));
    }

    // Find the corresponding block
    const block = blocks.find((b) => b.id === contentId);
    if (block) {
      // Add the React component
      result.push(block);
    } else {
      result.push(undefined);
    }

    // Update the lastIndex to the end of the current match
    lastIndex = offset + match.length;

    return match;
  });

  // Add the remaining text after the last placeholder
  if (lastIndex < markup.length) {
    result.push(markup.slice(lastIndex));
  }

  return result;
};
2 Likes

Thanks for the example :hugs:

I have a slightly different version now.
We are using Nuxt 3 and Vue 3.

const splitMarkupWithBlocks = (item: types.RichTextModel | null | undefined) => {
    
    const placeholderRegex = /<umb-rte-block[^>]*data-content-id="([^"]+)"[^>]*><\/umb-rte-block>/g;
    const result: (string | types.ApiBlockItemModel | types.ApiBlockGridItemModel | undefined)[] = [];
    let lastIndex = 0;

    const markup = item?.markup || '';
    const blocks = item?.blocks || [];

    // Replace placeholders and populate the result array
    markup.replace(placeholderRegex, (match, contentId, offset) => {
        // Add the text before the placeholder
        if (offset > lastIndex) {
            result.push(markup.slice(lastIndex, offset));
        }

        // Find the corresponding block
        const block = blocks.find((b) => b.content?.id === contentId);
        if (block) {
            result.push(block);
        } else {
            result.push(undefined);
        }

        // Update the lastIndex to the end of the current match
        lastIndex = offset + match.length;

        return match;
    });

    // Add the remaining text after the last placeholder
    if (lastIndex < markup.length) {
        result.push(markup.slice(lastIndex));
    }

    return result;
};

and then the following when rendering the property.

const model = computed(() => {
  return {
    heading: props.content?.properties?.heading,
    tagline: props.content?.properties?.tagline,
    text: splitMarkupWithBlocks(props.content?.properties?.text);
  }
});
<template v-if="model.text">
   <template v-for="elem in model.text">
      <div v-if="(typeof elem === 'string')" v-html="elem"></div>
      <UmbracoBlockItem v-else :item="(elem as types.ApiBlockItemModel)" />
   </template>
</template>

It may not be pretty, but it works :smile:
Here the RTE block is inserted between two paragraphs.

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.