Commit d76c1861 authored by Enrique Alcántara's avatar Enrique Alcántara Committed by Natalia Tepluhina

Render reference definitions as code blocks

Display reference definitions as code blocks
in the SSE. In that way, the editor preserves
the blank lines that surround the reference
definitions
parent 81ea1795
......@@ -29,6 +29,7 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) =>
const emphasisNode = 'EM, I';
const strongNode = 'STRONG, B';
const headingNode = 'H1, H2, H3, H4, H5, H6';
const preCodeNode = 'PRE CODE';
return {
TEXT_NODE(node) {
......@@ -91,6 +92,13 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) =>
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`
: baseRenderer.convert(node, subContent);
},
};
};
......
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 };
---
title: Render reference definitions as code blocks
merge_request: 41186
author:
type: fixed
......@@ -17,6 +17,10 @@ describe('rich_content_editor/services/html_to_markdown_renderer', () => {
fakeNode = { nodeValue: 'mock_node', dataset: {} };
});
afterEach(() => {
htmlToMarkdownRenderer = null;
});
describe('TEXT_NODE visitor', () => {
it('composes getSpaceControlled, getSpaceCollapsedText, and trim services', () => {
htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer);
......@@ -157,4 +161,30 @@ describe('rich_content_editor/services/html_to_markdown_renderer', () => {
expect(htmlToMarkdownRenderer['H1, H2, H3, H4, H5, H6'](fakeNode, heading)).toBe(result);
});
});
describe('PRE CODE', () => {
let node;
const subContent = 'sub content';
const originalConverterResult = 'base result';
beforeEach(() => {
node = document.createElement('PRE');
node.innerText = 'reference definition content';
node.dataset.sseReferenceDefinition = true;
baseRenderer.convert.mockReturnValueOnce(originalConverterResult);
htmlToMarkdownRenderer = buildHTMLToMarkdownRenderer(baseRenderer);
});
it('returns raw text when pre node has sse-reference-definitions class', () => {
expect(htmlToMarkdownRenderer['PRE CODE'](node, subContent)).toBe(`\n\n${node.innerText}\n`);
});
it('returns base result when pre node does not have sse-reference-definitions class', () => {
delete node.dataset.sseReferenceDefinition;
expect(htmlToMarkdownRenderer['PRE CODE'](node, subContent)).toBe(originalConverterResult);
});
});
});
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph';
import { renderUneditableBranch } from '~/vue_shared/components/rich_content_editor/services/renderers/render_utils';
import { buildMockTextNode } from './mock_data';
......@@ -17,7 +16,7 @@ const identifierParagraphNode = buildMockParagraphNode(
`[another-identifier]: https://example.com "This example has a title" [identifier]: http://example1.com [this link]: http://example2.com`,
);
describe('Render Identifier Paragraph renderer', () => {
describe('rich_content_editor/renderers_render_identifier_paragraph', () => {
describe('canRender', () => {
it.each`
node | paragraph | target
......@@ -37,8 +36,49 @@ describe('Render Identifier Paragraph renderer', () => {
});
describe('render', () => {
it('should delegate rendering to the renderUneditableBranch util', () => {
expect(renderer.render).toBe(renderUneditableBranch);
let context;
let result;
beforeEach(() => {
const node = {
firstChild: {
type: 'text',
literal: '[Some text]: https://link.com',
next: {
type: 'linebreak',
next: {
type: 'text',
literal: '[identifier]: http://example1.com "title"',
},
},
},
};
context = { skipChildren: jest.fn() };
result = renderer.render(node, context);
});
it('renders the reference definitions as a code block', () => {
expect(result).toEqual([
{
type: 'openTag',
tagName: 'pre',
classNames: ['code-block', 'language-markdown'],
attributes: {
'data-sse-reference-definition': true,
},
},
{ type: 'openTag', tagName: 'code' },
{
type: 'text',
content: '[Some text]: https://link.com\n[identifier]: http://example1.com "title"',
},
{ type: 'closeTag', tagName: 'code' },
{ type: 'closeTag', tagName: 'pre' },
]);
});
it('skips the reference definition node children from rendering', () => {
expect(context.skipChildren).toHaveBeenCalled();
});
});
});
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