email-builder-js icon indicating copy to clipboard operation
email-builder-js copied to clipboard

Can I import HTML directly?

Open iqbaladinur opened this issue 1 year ago • 5 comments

Is there any possible way to import html as a template? I don't find any documentation about it.

iqbaladinur avatar Jun 26 '24 09:06 iqbaladinur

I had a bit of a look trying to do the same thing, it looks like the JSON blocks > HTML is a one way street (I could be wrong but)

codythatsme avatar Jul 16 '24 13:07 codythatsme

This is a good idea but something that is not supported (and most likely won't work on in the near future). As @CodyCodes95 mentioned, EmailBuilder.js JSON -> HTML is a one-way export.

If there are others that would be interested in building an HTML -> EmailBuilder.js JSON converter, that would be fantastic (but likely difficult).

jordanisip avatar Aug 10 '24 13:08 jordanisip

any updates on this, anyone?

evansjp avatar Nov 18 '24 21:11 evansjp

@jordanisip were you able to make progress on HTML -> JSON?

killroywashere avatar Nov 26 '24 15:11 killroywashere

Here's a hack that I'm using somewhere to turn HTML into email-builder blocks. While it's lossy, this approach covers most usecases.

  • Convert HTML to Markdown with something like mixmark-io/turndown
  • Iterate Markdown lines and turn them into JSON blocks.

This snippet works for blocks of Markdown text interspersed by headings. Images, links etc. in text is copied as Markdown as-is, which e-mail builder has the ability to render. This can be tweaked to add support for more block types.

const markdownToVisualBlock = (markdown) => {
  const lines = markdown.split('\n');
  const blocks = [];
  const idBase = Date.now();
  let textBuf = [];

  const createBlock = (type, props, style = {}) => ({
    id: `block-${idBase + blocks.length}`,
    type,
    data: {
      props,
      style: {
        padding: {
          top: 16, bottom: 16, right: 24, left: 24,
        },
        ...style,
      },
    },
  });

  const flushText = () => {
    if (textBuf.length > 0) {
      blocks.push(createBlock('Text', { markdown: true, text: textBuf.join('\n') }));

      textBuf = [];
    }
  };

  lines.forEach((line) => {
    // Handle ATX headings (# Heading)
    const heading = line.match(/^(#+)\s+(.*)/);
    if (heading) {
      flushText();

      blocks.push(createBlock('Heading', {
        text: heading[2],
        level: `h${Math.min(heading[1].length, 6)}`,
      }));
      return;
    }

    // Handle Setext headings (===== or -----)
    const trimmed = line.trim();
    if (/^(=+|-+)$/.test(trimmed) && textBuf.length > 0) {
      const lastLine = textBuf.pop();
      if (lastLine.trim()) {
        flushText();

        blocks.push(createBlock('Heading', {
          text: lastLine,
          level: trimmed[0] === '=' ? 'h1' : 'h2',
        }));

        return;
      }

      textBuf.push(lastLine, line);
    } else {
      textBuf.push(line);
    }
  });

  flushText();

  return {
    root: {
      type: 'EmailLayout',
      data: { childrenIds: blocks.map((b) => b.id) },
    },
    ...Object.fromEntries(blocks.map((b) => [b.id, { type: b.type, data: b.data }])),
  };
};

knadh avatar Apr 07 '25 16:04 knadh