diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/rich_content_editor/services')
9 files changed, 103 insertions, 42 deletions
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js index a9c5d442f62..108c60c3edb 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js @@ -1,17 +1,19 @@ import { union, mapValues } from 'lodash'; import renderBlockHtml from './renderers/render_html_block'; -import renderKramdownList from './renderers/render_kramdown_list'; -import renderKramdownText from './renderers/render_kramdown_text'; +import renderHeading from './renderers/render_heading'; import renderIdentifierInstanceText from './renderers/render_identifier_instance_text'; import renderIdentifierParagraph from './renderers/render_identifier_paragraph'; import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline'; import renderSoftbreak from './renderers/render_softbreak'; +import renderAttributeDefinition from './renderers/render_attribute_definition'; +import renderListItem from './renderers/render_list_item'; const htmlInlineRenderers = [renderFontAwesomeHtmlInline]; const htmlBlockRenderers = [renderBlockHtml]; -const listRenderers = [renderKramdownList]; -const paragraphRenderers = [renderIdentifierParagraph]; -const textRenderers = [renderKramdownText, renderIdentifierInstanceText]; +const headingRenderers = [renderHeading]; +const paragraphRenderers = [renderIdentifierParagraph, renderBlockHtml]; +const textRenderers = [renderIdentifierInstanceText, renderAttributeDefinition]; +const listItemRenderers = [renderListItem]; const softbreakRenderers = [renderSoftbreak]; const executeRenderer = (renderers, node, context) => { @@ -25,7 +27,8 @@ const buildCustomHTMLRenderer = customRenderers => { ...customRenderers, htmlBlock: union(htmlBlockRenderers, customRenderers?.htmlBlock), htmlInline: union(htmlInlineRenderers, customRenderers?.htmlInline), - list: union(listRenderers, customRenderers?.list), + heading: union(headingRenderers, customRenderers?.heading), + item: union(listItemRenderers, customRenderers?.listItem), paragraph: union(paragraphRenderers, customRenderers?.paragraph), text: union(textRenderers, customRenderers?.text), softbreak: union(softbreakRenderers, customRenderers?.softbreak), diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js index 868ede9426e..2bce691e793 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js @@ -28,6 +28,8 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) => const orderedListItemNode = 'OL LI'; const emphasisNode = 'EM, I'; const strongNode = 'STRONG, B'; + const headingNode = 'H1, H2, H3, H4, H5, H6'; + const preCodeNode = 'PRE CODE'; return { TEXT_NODE(node) { @@ -63,8 +65,10 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) => }, [unorderedListItemNode](node, subContent) { const baseResult = baseRenderer.convert(node, subContent); + const formatted = baseResult.replace(/^(\s*)([*|-])/, `$1${unorderedListBulletChar}`); + const { attributeDefinition } = node.dataset; - return baseResult.replace(/^(\s*)([*|-])/, `$1${unorderedListBulletChar}`); + return attributeDefinition ? `${formatted.trimRight()}\n${attributeDefinition}\n` : formatted; }, [orderedListItemNode](node, subContent) { const baseResult = baseRenderer.convert(node, subContent); @@ -82,6 +86,19 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) => return result.replace(/^[*_]{2}/, strongSyntax).replace(/[*_]{2}$/, strongSyntax); }, + [headingNode](node, subContent) { + const result = baseRenderer.convert(node, subContent); + const { attributeDefinition } = node.dataset; + + return attributeDefinition ? `${result.trimRight()}\n${attributeDefinition}\n\n` : result; + }, + [preCodeNode](node, subContent) { + const isReferenceDefinition = Boolean(node.dataset.sseReferenceDefinition); + + return isReferenceDefinition + ? `\n\n${node.innerText}\n\n` + : baseRenderer.convert(node, subContent); + }, }; }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_attribute_definition.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_attribute_definition.js new file mode 100644 index 00000000000..bd419447a48 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_attribute_definition.js @@ -0,0 +1,7 @@ +import { isAttributeDefinition } from './render_utils'; + +const canRender = ({ literal }) => isAttributeDefinition(literal); + +const render = () => ({ type: 'html', content: '<!-- sse-attribute-definition -->' }); + +export default { canRender, render }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_heading.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_heading.js new file mode 100644 index 00000000000..71026fd0d65 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_heading.js @@ -0,0 +1,6 @@ +import { + renderWithAttributeDefinitions as render, + willAlwaysRender as canRender, +} from './render_utils'; + +export default { render, canRender }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js index 4ec45ecd3a7..3f9c6291d1b 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js @@ -1,5 +1,3 @@ -import { renderUneditableBranch as render } from './render_utils'; - const identifierRegex = /(^\[.+\]: .+)/; const isIdentifier = text => { @@ -10,4 +8,33 @@ const canRender = (node, context) => { return isIdentifier(context.getChildrenText(node)); }; +const getReferenceDefinitions = (node, definitions = '') => { + if (!node) { + return definitions; + } + + const definition = node.type === 'text' ? node.literal : '\n'; + + return getReferenceDefinitions(node.next, `${definitions}${definition}`); +}; + +const render = (node, { skipChildren }) => { + const content = getReferenceDefinitions(node.firstChild); + + skipChildren(); + + return [ + { + type: 'openTag', + tagName: 'pre', + classNames: ['code-block', 'language-markdown'], + attributes: { 'data-sse-reference-definition': true }, + }, + { type: 'openTag', tagName: 'code' }, + { type: 'text', content }, + { type: 'closeTag', tagName: 'code' }, + { type: 'closeTag', tagName: 'pre' }, + ]; +}; + export default { canRender, render }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js deleted file mode 100644 index 949ca0e5c2a..00000000000 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_list.js +++ /dev/null @@ -1,24 +0,0 @@ -import { renderUneditableBranch as render } from './render_utils'; - -const isKramdownTOC = ({ type, literal }) => type === 'text' && literal === 'TOC'; - -const canRender = node => { - let targetNode = node; - while (targetNode !== null) { - const { firstChild } = targetNode; - const isLeaf = firstChild === null; - if (isLeaf) { - if (isKramdownTOC(targetNode)) { - return true; - } - - break; - } - - targetNode = targetNode.firstChild; - } - - return false; -}; - -export default { canRender, render }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js deleted file mode 100644 index 0551894918c..00000000000 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text.js +++ /dev/null @@ -1,9 +0,0 @@ -import { renderUneditableLeaf as render } from './render_utils'; - -const kramdownRegex = /(^{:.+}$)/; - -const canRender = ({ literal }) => { - return kramdownRegex.test(literal); -}; - -export default { canRender, render }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_list_item.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_list_item.js new file mode 100644 index 00000000000..71026fd0d65 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_list_item.js @@ -0,0 +1,6 @@ +import { + renderWithAttributeDefinitions as render, + willAlwaysRender as canRender, +} from './render_utils'; + +export default { render, canRender }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js index cec6491557b..4cba2c70486 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js @@ -8,3 +8,31 @@ export const renderUneditableLeaf = (_, { origin }) => buildUneditableBlockToken export const renderUneditableBranch = (_, { entering, origin }) => entering ? buildUneditableOpenTokens(origin()) : buildUneditableCloseToken(); + +const attributeDefinitionRegexp = /(^{:.+}$)/; + +export const isAttributeDefinition = text => attributeDefinitionRegexp.test(text); + +const findAttributeDefinition = node => { + const literal = + node?.next?.firstChild?.literal || node?.firstChild?.firstChild?.next?.next?.literal; // for headings // for list items; + + return isAttributeDefinition(literal) ? literal : null; +}; + +export const renderWithAttributeDefinitions = (node, { origin }) => { + const attributes = findAttributeDefinition(node); + const token = origin(); + + if (token.type === 'openTag' && attributes) { + Object.assign(token, { + attributes: { + 'data-attribute-definition': attributes, + }, + }); + } + + return token; +}; + +export const willAlwaysRender = () => true; |