From e6e346e327b818f60e0c4b310c00efca220bbf57 Mon Sep 17 00:00:00 2001 From: Kevin Leung Date: Mon, 10 May 2021 19:55:38 +0800 Subject: [PATCH 1/3] Add tokenizedEach hook --- src/core/init/lifecycle.js | 1 + src/core/render/index.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index d695ed6e6..39ebb8f87 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -5,6 +5,7 @@ export function initLifecycle(vm) { 'init', 'mounted', 'beforeEach', + 'tokenizedEach', 'afterEach', 'doneEach', 'ready', diff --git a/src/core/render/index.js b/src/core/render/index.js index 9fb777baf..3ff8c3760 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -328,12 +328,14 @@ export function renderMixin(proto) { raw: result, }, tokens => { - html = this.compiler.compile(tokens); - html = this.isRemoteUrl - ? DOMPurify.sanitize(html, { ADD_TAGS: ['script'] }) - : html; - callback(); - next(); + callHook(this, 'tokenizedEach', tokens, updatedTokens => { + html = this.compiler.compile(updatedTokens); + html = this.isRemoteUrl + ? DOMPurify.sanitize(html, { ADD_TAGS: ['script'] }) + : html; + callback(); + next(); + }); } ); } From 4f69c604d4108b06b2e8708daa906352eee0e7e6 Mon Sep 17 00:00:00 2001 From: Kevin Leung Date: Mon, 10 May 2021 20:02:22 +0800 Subject: [PATCH 2/3] Add tests and docs --- docs/write-a-plugin.md | 10 ++++++++-- test/integration/docsify.test.js | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/write-a-plugin.md b/docs/write-a-plugin.md index baa894efe..bdbda55d2 100644 --- a/docs/write-a-plugin.md +++ b/docs/write-a-plugin.md @@ -18,9 +18,15 @@ window.$docsify = { return content; }); + hook.tokenizedEach(function(tokens, next) { + // Invoked each time after parsing the Markdown file, but before rendering the result HTML. + // ... + next(content); + }); + hook.afterEach(function(html, next) { - // Invoked each time after the Markdown file is parsed. - // beforeEach and afterEach support asynchronous。 + // Invoked each time after the Markdown file is parsed and the result HTML is generated. + // beforeEach, tokenizedEach and afterEach support asynchronous。 // ... // call `next(html)` when task is done. next(html); diff --git a/test/integration/docsify.test.js b/test/integration/docsify.test.js index 9aacebd4d..09f2508ed 100644 --- a/test/integration/docsify.test.js +++ b/test/integration/docsify.test.js @@ -33,6 +33,7 @@ describe('Docsify', function() { expect(hook.init).toBeInstanceOf(Function); expect(hook.beforeEach).toBeInstanceOf(Function); + expect(hook.tokenizedEach).toBeInstanceOf(Function); expect(hook.afterEach).toBeInstanceOf(Function); expect(hook.doneEach).toBeInstanceOf(Function); expect(hook.mounted).toBeInstanceOf(Function); From 5436ccd83937831efcb0463f42c44f320587bc82 Mon Sep 17 00:00:00 2001 From: Kevin Leung Date: Tue, 11 May 2021 09:06:41 +0800 Subject: [PATCH 3/3] Implement beforeEmbed and afterEmbed hooks --- docs/write-a-plugin.md | 14 +++-- src/core/init/lifecycle.js | 3 +- src/core/render/embed.js | 104 +++++++++++++++++-------------- src/core/render/index.js | 4 +- test/integration/docsify.test.js | 3 +- 5 files changed, 73 insertions(+), 55 deletions(-) diff --git a/docs/write-a-plugin.md b/docs/write-a-plugin.md index bdbda55d2..067aa2862 100644 --- a/docs/write-a-plugin.md +++ b/docs/write-a-plugin.md @@ -18,15 +18,21 @@ window.$docsify = { return content; }); - hook.tokenizedEach(function(tokens, next) { - // Invoked each time after parsing the Markdown file, but before rendering the result HTML. + hook.beforeEmbed(function(tokens, next) { + // Invoked each time after parsing the Markdown file, but before embedding is applied to the token list. // ... - next(content); + next(tokens); + }); + + hook.afterEmbed(function(tokens, next) { + // Invoked each time after parsing the Markdown file, and after embedding is applied to the token list. + // ... + next(tokens); }); hook.afterEach(function(html, next) { // Invoked each time after the Markdown file is parsed and the result HTML is generated. - // beforeEach, tokenizedEach and afterEach support asynchronous。 + // beforeEach, beforeEmbed, afterEmbed and afterEach support asynchronous。 // ... // call `next(html)` when task is done. next(html); diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index 39ebb8f87..61c1ec2da 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -5,7 +5,8 @@ export function initLifecycle(vm) { 'init', 'mounted', 'beforeEach', - 'tokenizedEach', + 'beforeEmbed', + 'afterEmbed', 'afterEach', 'doneEach', 'ready', diff --git a/src/core/render/embed.js b/src/core/render/embed.js index 7a5cf3b68..74f82758e 100644 --- a/src/core/render/embed.js +++ b/src/core/render/embed.js @@ -91,7 +91,10 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) { } } -export function prerenderEmbed({ compiler, raw = '', fetch }, done) { +export function prerenderEmbed( + { compiler, raw = '', fetch, beforeEmbed }, + done +) { let hit = cached[raw]; if (hit) { const copy = hit.slice(); @@ -100,56 +103,61 @@ export function prerenderEmbed({ compiler, raw = '', fetch }, done) { } const compile = compiler._marked; - let tokens = compile.lexer(raw); - const embedTokens = []; - const linkRE = compile.Lexer.rules.inline.link; - const links = tokens.links; - - tokens.forEach((token, index) => { - if (token.type === 'paragraph') { - token.text = token.text.replace( - new RegExp(linkRE.source, 'g'), - (src, filename, href, title) => { - const embed = compiler.compileEmbed(href, title); - - if (embed) { - embedTokens.push({ - index, - embed, - }); - } - return src; - } - ); - } - }); + if (!beforeEmbed) { + beforeEmbed = (tokens, done) => done(tokens); + } // noop + beforeEmbed(compile.lexer(raw), tokens => { + const embedTokens = []; + const linkRE = compile.Lexer.rules.inline.link; + const links = tokens.links; + + tokens.forEach((token, index) => { + if (token.type === 'paragraph') { + token.text = token.text.replace( + new RegExp(linkRE.source, 'g'), + (src, filename, href, title) => { + const embed = compiler.compileEmbed(href, title); + + if (embed) { + embedTokens.push({ + index, + embed, + }); + } - // keep track of which tokens have been embedded so far - // so that we know where to insert the embedded tokens as they - // are returned - const moves = []; - walkFetchEmbed({ compile, embedTokens, fetch }, ({ embedToken, token }) => { - if (token) { - // iterate through the array of previously inserted tokens - // to determine where the current embedded tokens should be inserted - let index = token.index; - moves.forEach(pos => { - if (index > pos.start) { - index += pos.length; - } - }); + return src; + } + ); + } + }); + + // keep track of which tokens have been embedded so far + // so that we know where to insert the embedded tokens as they + // are returned + const moves = []; + walkFetchEmbed({ compile, embedTokens, fetch }, ({ embedToken, token }) => { + if (token) { + // iterate through the array of previously inserted tokens + // to determine where the current embedded tokens should be inserted + let index = token.index; + moves.forEach(pos => { + if (index > pos.start) { + index += pos.length; + } + }); - merge(links, embedToken.links); + merge(links, embedToken.links); - tokens = tokens - .slice(0, index) - .concat(embedToken, tokens.slice(index + 1)); - moves.push({ start: index, length: embedToken.length - 1 }); - } else { - cached[raw] = tokens.concat(); - tokens.links = cached[raw].links = links; - done(tokens); - } + tokens = tokens + .slice(0, index) + .concat(embedToken, tokens.slice(index + 1)); + moves.push({ start: index, length: embedToken.length - 1 }); + } else { + cached[raw] = tokens.concat(); + tokens.links = cached[raw].links = links; + done(tokens); + } + }); }); } diff --git a/src/core/render/index.js b/src/core/render/index.js index 3ff8c3760..7937c59ae 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -326,9 +326,11 @@ export function renderMixin(proto) { { compiler: this.compiler, raw: result, + beforeEmbed: (tokens, done) => + callHook(this, 'beforeEmbed', tokens, done), }, tokens => { - callHook(this, 'tokenizedEach', tokens, updatedTokens => { + callHook(this, 'afterEmbed', tokens, updatedTokens => { html = this.compiler.compile(updatedTokens); html = this.isRemoteUrl ? DOMPurify.sanitize(html, { ADD_TAGS: ['script'] }) diff --git a/test/integration/docsify.test.js b/test/integration/docsify.test.js index 09f2508ed..25a1435c7 100644 --- a/test/integration/docsify.test.js +++ b/test/integration/docsify.test.js @@ -33,7 +33,8 @@ describe('Docsify', function() { expect(hook.init).toBeInstanceOf(Function); expect(hook.beforeEach).toBeInstanceOf(Function); - expect(hook.tokenizedEach).toBeInstanceOf(Function); + expect(hook.beforeEmbed).toBeInstanceOf(Function); + expect(hook.afterEmbed).toBeInstanceOf(Function); expect(hook.afterEach).toBeInstanceOf(Function); expect(hook.doneEach).toBeInstanceOf(Function); expect(hook.mounted).toBeInstanceOf(Function);