From 9bf89ef825d7f79c7bfacd77925a09b7b2e504ab Mon Sep 17 00:00:00 2001 From: v8tenko Date: Tue, 4 Jul 2023 17:40:06 +0300 Subject: [PATCH] feat(diplodoc/term): new terms BREAKING CHANGE New term plugin. Previous plugin implement deferred template render. Now, template renders inline. What this means: 1. Rendering term definition before titles breaks navigation. So far this is the only known issue, if you want secure yourself while working with term, you should place term definition at the end of file. --- package-lock.json | 2 +- src/transform/md.ts | 8 +- src/transform/plugins/term/termDefinitions.ts | 55 +++++-- test/term.test.ts | 144 +++++++++--------- 4 files changed, 113 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf7aeb5c..23052bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8956,7 +8956,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", diff --git a/src/transform/md.ts b/src/transform/md.ts index 17ae57b9..245d17c6 100644 --- a/src/transform/md.ts +++ b/src/transform/md.ts @@ -110,15 +110,11 @@ function initParser(md: MarkdownIt, options: OptionsType, env: EnvType) { }; } -function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType<{termTokens?: Token[]}>) { +function initCompiler(md: MarkdownIt, options: OptionsType, env: EnvType) { const {needToSanitizeHtml = false, sanitizeOptions} = options; return (tokens: Token[]) => { - // TODO: define postprocess step on term plugin - const {termTokens = []} = env; - delete env.termTokens; - - const html = md.renderer.render([...tokens, ...termTokens], md.options, env); + const html = md.renderer.render(tokens, md.options, env); return needToSanitizeHtml ? sanitizeHtml(html, sanitizeOptions) : html; }; diff --git a/src/transform/plugins/term/termDefinitions.ts b/src/transform/plugins/term/termDefinitions.ts index a8ab772b..25c127be 100644 --- a/src/transform/plugins/term/termDefinitions.ts +++ b/src/transform/plugins/term/termDefinitions.ts @@ -37,7 +37,6 @@ export function termDefinitions(md: MarkdownIt, options: MarkdownItPluginOpts) { const newLineReg = new RegExp(/^(\r\n|\r|\n)/); const termReg = new RegExp(/^\[\*(\w+)\]:/); - let currentLine = startLine; // Allow multiline term definition @@ -74,7 +73,16 @@ export function termDefinitions(md: MarkdownIt, options: MarkdownItPluginOpts) { return false; } - return processTermDefinition(md, options, state, currentLine, endLine, label, title); + return processTermDefinition( + md, + options, + state, + currentLine, + startLine, + endLine, + label, + title, + ); }; } @@ -83,6 +91,7 @@ function processTermDefinition( options: MarkdownItPluginOpts, state: StateBlock, currentLine: number, + startLine: number, endLine: number, label: string, title: string, @@ -121,32 +130,48 @@ function processTermDefinition( state.env.terms[':' + label] = title; } - const termNodes = []; - token = new state.Token('template_open', 'template', 1); + token.map = [startLine, currentLine + 1]; token.attrSet('id', ':' + label + '_template'); - termNodes.push(token); + token.attrSet('label', label); - token = new state.Token('term_open', 'dfn', 1); + state.tokens.push(token); + + token = new state.Token('dfn_open', 'dfn', 1); token.attrSet('class', 'yfm yfm-term_dfn'); token.attrSet('id', ':' + label + '_element'); token.attrSet('role', 'tooltip'); - termNodes.push(token); - termNodes.push(...md.parse(title, {})); + state.tokens.push(token); - token = new state.Token('term_close', 'dfn', -1); - termNodes.push(token); + const titleTokens = md.parse(title, state.env); - token = new state.Token('template_close', 'template', -1); - termNodes.push(token); + for (const titleToken of titleTokens) { + if (titleToken.children?.length) { + titleToken.content = ''; + } + + if (!titleToken.map) { + state.tokens.push(titleToken); + continue; + } - if (!state.env.termTokens) { - state.env.termTokens = []; + const [start, end] = titleToken.map; + + titleToken.map = [start + startLine, end + startLine]; + state.tokens.push(titleToken); } - state.env.termTokens.push(...termNodes); + token = new state.Token('dfn_close', 'dfn', -1); + + state.tokens.push(token); + + token = new state.Token('template_close', 'template', -1); + + state.tokens.push(token); + /** current line links to end of term definition */ state.line = currentLine + 1; + return true; } diff --git a/test/term.test.ts b/test/term.test.ts index 2699278f..6653365a 100644 --- a/test/term.test.ts +++ b/test/term.test.ts @@ -32,12 +32,11 @@ describe('Terms', () => { const input = readFileSync(inputPath, 'utf8'); const result = transformYfm(input, inputPath); - expect(clearRandomId(result)).toEqual( - '

Web

\n' + - '

The HTML specification

\n' + - '', - ); + expect(clearRandomId(result)).toEqual(`\ +

Web

+

The HTML specification

+`); }); test('Should create term in table with definition template', () => { @@ -45,25 +44,24 @@ describe('Terms', () => { const input = readFileSync(inputPath, 'utf8'); const result = transformYfm(input, inputPath); - expect(clearRandomId(result)).toEqual( - '

Web

\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '\n' + - '
LanguageInitial release
HTML1993
\n' + - '', - ); + expect(clearRandomId(result)).toEqual(`\ +

Web

+ + + + + + + + + + + + + +
LanguageInitial release
HTML1993
+`); }); test('Should create term in code with definition template', () => { @@ -71,49 +69,48 @@ describe('Terms', () => { const input = readFileSync(inputPath, 'utf8'); const result = transformYfm(input, inputPath); - expect(clearRandomId(result)).toEqual( - '

Web

\n' + - '\n' + - '
\n' + - '
HTML: Lorem\n' +
-                '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
\n' + - '', - ); + expect(clearRandomId(result)).toEqual(`\ +

Web

+ +
+
HTML: Lorem
+
+ + + + + + + + +
+`); }); test('Term should use content from include', () => { @@ -121,11 +118,10 @@ describe('Terms', () => { const input = readFileSync(inputPath, 'utf8'); const result = transformYfm(input, inputPath); - expect(clearRandomId(result)).toEqual( - '

Web

\n' + - '

The HTML specification

\n' + - '', - ); + expect(clearRandomId(result)).toEqual(`\ +

Web

+

The HTML specification

+`); }); });