Commit 0523e401 authored by derek-knox's avatar derek-knox

V2 of custom html renderer

Update the custom html renderer to create a text
vs. html token. This allows us to properly prevent
WYSIWYG mode from rendering as HTML without borking
the source content.
parent c6eb657f
import renderHtml from './renderers/render_html'; import renderBlockHtml from './renderers/render_html_block';
import renderKramdownList from './renderers/render_kramdown_list'; import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text'; import renderKramdownText from './renderers/render_kramdown_text';
import renderIdentifierParagraph from './renderers/render_identifier_paragraph'; import renderIdentifierParagraph from './renderers/render_identifier_paragraph';
...@@ -6,7 +6,7 @@ import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text'; ...@@ -6,7 +6,7 @@ import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline'; import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline';
const htmlInlineRenderers = [renderFontAwesomeHtmlInline]; const htmlInlineRenderers = [renderFontAwesomeHtmlInline];
const htmlRenderers = [renderHtml]; const htmlBlockRenderers = [renderBlockHtml];
const listRenderers = [renderKramdownList]; const listRenderers = [renderKramdownList];
const paragraphRenderers = [renderIdentifierParagraph]; const paragraphRenderers = [renderIdentifierParagraph];
const textRenderers = [renderKramdownText, renderEmbeddedRubyText]; const textRenderers = [renderKramdownText, renderEmbeddedRubyText];
...@@ -32,9 +32,9 @@ const buildCustomHTMLRenderer = ( ...@@ -32,9 +32,9 @@ const buildCustomHTMLRenderer = (
) => { ) => {
const defaults = { const defaults = {
htmlBlock(node, context) { htmlBlock(node, context) {
const allHtmlRenderers = [...customRenderers.list, ...htmlRenderers]; const allHtmlBlockRenderers = [...customRenderers.htmlBlock, ...htmlBlockRenderers];
return executeRenderer(allHtmlRenderers, node, context); return executeRenderer(allHtmlBlockRenderers, node, context);
}, },
htmlInline(node, context) { htmlInline(node, context) {
const allHtmlInlineRenderers = [...customRenderers.htmlInline, ...htmlInlineRenderers]; const allHtmlInlineRenderers = [...customRenderers.htmlInline, ...htmlInlineRenderers];
...@@ -47,7 +47,7 @@ const buildCustomHTMLRenderer = ( ...@@ -47,7 +47,7 @@ const buildCustomHTMLRenderer = (
return executeRenderer(allListRenderers, node, context); return executeRenderer(allListRenderers, node, context);
}, },
paragraph(node, context) { paragraph(node, context) {
const allParagraphRenderers = [...customRenderers.list, ...paragraphRenderers]; const allParagraphRenderers = [...customRenderers.paragraph, ...paragraphRenderers];
return executeRenderer(allParagraphRenderers, node, context); return executeRenderer(allParagraphRenderers, node, context);
}, },
......
...@@ -7,24 +7,30 @@ const TAG_TYPES = { ...@@ -7,24 +7,30 @@ const TAG_TYPES = {
inline: 'span', inline: 'span',
}; };
export const buildUneditableOpenTokens = (token, type = TAG_TYPES.block) => { // Open helpers (singular and multiple)
return [
const buildUneditableOpenToken = (type = TAG_TYPES.block) =>
buildToken('openTag', type, { buildToken('openTag', type, {
attributes: { contenteditable: false }, attributes: { contenteditable: false },
classNames: [ classNames: [
'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed', 'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
], ],
}), });
token,
]; export const buildUneditableOpenTokens = (token, type = TAG_TYPES.block) => {
return [buildUneditableOpenToken(type), token];
}; };
// Close helpers (singular and multiple)
export const buildUneditableCloseToken = (type = TAG_TYPES.block) => buildToken('closeTag', type); export const buildUneditableCloseToken = (type = TAG_TYPES.block) => buildToken('closeTag', type);
export const buildUneditableCloseTokens = (token, type = TAG_TYPES.block) => { export const buildUneditableCloseTokens = (token, type = TAG_TYPES.block) => {
return [token, buildUneditableCloseToken(type)]; return [token, buildUneditableCloseToken(type)];
}; };
// Complete helpers (open plus close)
export const buildUneditableInlineTokens = token => { export const buildUneditableInlineTokens = token => {
return [ return [
...buildUneditableOpenTokens(token, TAG_TYPES.inline), ...buildUneditableOpenTokens(token, TAG_TYPES.inline),
...@@ -35,3 +41,20 @@ export const buildUneditableInlineTokens = token => { ...@@ -35,3 +41,20 @@ export const buildUneditableInlineTokens = token => {
export const buildUneditableTokens = token => { export const buildUneditableTokens = token => {
return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()]; return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()];
}; };
export const buildUneditableHtmlTokens = node => {
/*
Toast UI internally appends ' data-tomark-pass ' attribute flags so it can target certain
nested nodes for internal use during Markdown <=> WYSIWYG conversions. In our case, we want
to prevent HTML being rendered completely in WYSIWYG mode and thus we use a `text` vs. `html`
type when building the token. However, in doing so, we need to strip out the ` data-tomark-pass `
to prevent their persistence within the `text` content as the user did not intend these as edits.
https://github.com/nhn/tui.editor/blob/cc54ec224fc3a4b6e5a2b19a71650959f41adc0e/apps/editor/src/js/convertor.js#L72
*/
const regex = / data-tomark-pass /gm;
const content = node.literal.replace(regex, '');
const htmlAsTextToken = buildToken('text', null, { content });
return [buildUneditableOpenToken(), htmlAsTextToken, buildUneditableCloseToken()];
};
import { buildUneditableTokens } from './build_uneditable_token'; import { buildUneditableHtmlTokens } from './build_uneditable_token';
const canRender = ({ type }) => { const canRender = ({ type }) => {
return type === 'htmlBlock'; return type === 'htmlBlock';
}; };
const render = (_, { origin }) => buildUneditableTokens(origin()); const render = node => buildUneditableHtmlTokens(node);
export default { canRender, render }; export default { canRender, render };
---
title: Add a custom HTML renderer to the Static Site Editor for HTML block syntax
merge_request: 36330
author:
type: added
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html'; import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_html_block';
import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; import { buildUneditableHtmlTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
import { normalTextNode } from './mock_data'; import { normalTextNode } from './mock_data';
const htmlLiteral = '<div><h1>Heading</h1><p>Paragraph.</p></div>';
const htmlBlockNode = { const htmlBlockNode = {
firstChild: null, firstChild: null,
literal: htmlLiteral, literal: '<div><h1>Heading</h1><p>Paragraph.</p></div>',
type: 'htmlBlock', type: 'htmlBlock',
}; };
...@@ -22,13 +21,18 @@ describe('Render HTML renderer', () => { ...@@ -22,13 +21,18 @@ describe('Render HTML renderer', () => {
}); });
describe('render', () => { describe('render', () => {
it('should return uneditable tokens wrapping the origin token', () => { const htmlBlockNodeToMark = {
const origin = jest.fn(); firstChild: null,
const context = { origin }; literal: '<div data-to-mark ></div>',
type: 'htmlBlock',
};
expect(renderer.render(htmlBlockNode, context)).toStrictEqual( it.each`
buildUneditableTokens(origin()), node
); ${htmlBlockNode}
${htmlBlockNodeToMark}
`('should return uneditable tokens wrapping the $node as a token', ({ node }) => {
expect(renderer.render(node)).toStrictEqual(buildUneditableHtmlTokens(node));
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment