Commit 46a02f9b authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '243760-fix-link-reference-definitions' into 'master'

Render reference definitions as code blocks

See merge request gitlab-org/gitlab!41186
parents 97ef952c d76c1861
......@@ -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