From cf1abaa4b4149d7eeab9930e1d82ad6c620b4d69 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Sun, 19 Apr 2020 00:03:50 -0500 Subject: [PATCH 1/6] add marked.use --- src/marked.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/marked.js b/src/marked.js index 8a77ddcef6..1592aefdbd 100644 --- a/src/marked.js +++ b/src/marked.js @@ -127,6 +127,29 @@ marked.getDefaults = getDefaults; marked.defaults = defaults; +/** + * Use Extension + */ + +marked.use = function(extension) { + const opts = merge({}, extension); + if (extension.renderer) { + const renderer = marked.defaults.renderer || new Renderer(); + for (const prop in extension.renderer) { + renderer[prop] = extension.renderer[prop]; + } + opts.renderer = renderer; + } + if (extension.tokenizer) { + const tokenizer = marked.defaults.tokenizer || new Tokenizer(); + for (const prop in extension.tokenizer) { + tokenizer[prop] = extension.tokenizer[prop]; + } + opts.tokenizer = tokenizer; + } + marked.setOptions(opts); +}; + /** * Expose */ From dd86b714a300b36df77312ec65cd3fee7ffd881f Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Sun, 19 Apr 2020 00:04:06 -0500 Subject: [PATCH 2/6] add marked.use tests --- test/unit/marked-spec.js | 100 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js index cc84d16191..bf6e6ed0bb 100644 --- a/test/unit/marked-spec.js +++ b/test/unit/marked-spec.js @@ -96,3 +96,103 @@ describe('inlineLexer', () => { expect(renderer.html).toHaveBeenCalledWith('MY IMAGE'); }); }); + +describe('use extension', () => { + it('should use renderer', () => { + const extension = { + renderer: { + paragraph(text) { + return 'extension'; + } + } + }; + spyOn(extension.renderer, 'paragraph').and.callThrough(); + marked.use(extension); + const html = marked('text'); + expect(extension.renderer.paragraph).toHaveBeenCalledWith('text'); + expect(html).toBe('extension'); + }); + + it('should use tokenizer', () => { + const extension = { + tokenizer: { + paragraph(text) { + return { + type: 'paragraph', + raw: text, + text: 'extension' + }; + } + } + }; + spyOn(extension.tokenizer, 'paragraph').and.callThrough(); + marked.use(extension); + const html = marked('text'); + expect(extension.tokenizer.paragraph).toHaveBeenCalledWith('text'); + expect(html).toBe('

extension

\n'); + }); + + it('should use options from extension', () => { + const extension = { + headerIds: false + }; + marked.use(extension); + const html = marked('# heading'); + expect(html).toBe('

heading

\n'); + }); + + it('should use last extension function and not override others', () => { + const extension1 = { + renderer: { + paragraph(text) { + return 'extension1 paragraph\n'; + }, + html(html) { + return 'extension1 html\n'; + } + } + }; + const extension2 = { + renderer: { + paragraph(text) { + return 'extension2 paragraph\n'; + } + } + }; + marked.use(extension1); + marked.use(extension2); + const html = marked(` +paragraph + + + +# heading +`); + expect(html).toBe('extension2 paragraph\nextension1 html\n

heading

\n'); + }); + + it('should get options with this.options', () => { + const extension = { + renderer: { + heading: () => { + return this.options ? 'arrow options\n' : 'arrow no options\n'; + }, + html: function() { + return this.options ? 'function options\n' : 'function no options\n'; + }, + paragraph() { + return this.options ? 'shorthand options\n' : 'shorthand no options\n'; + } + } + }; + marked.use(extension); + const html = marked(` +# heading + + + +paragraph +`); + expect(html).toBe('arrow no options\nfunction options\nshorthand options\n'); + }); +}); From 07510c9301251893ea0718ee813501c4d3e24bbe Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Sun, 19 Apr 2020 00:38:19 -0500 Subject: [PATCH 3/6] update docs with marked.use --- docs/USING_PRO.md | 60 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/USING_PRO.md b/docs/USING_PRO.md index 0b2aea001a..2d73cf97e1 100644 --- a/docs/USING_PRO.md +++ b/docs/USING_PRO.md @@ -12,24 +12,25 @@ The renderer defines the output of the parser. // Create reference instance const marked = require('marked'); -// Get reference -const renderer = new marked.Renderer(); - // Override function -renderer.heading = function(text, level) { - const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-'); - - return ` - - - - - ${text} - `; +const renderer = { + heading(text, level) { + const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-'); + + return ` + + + + + ${text} + `; + } }; +marked.use({ renderer }); + // Run marked -console.log(marked('# heading+', { renderer })); +console.log(marked('# heading+')); ``` **Output:** @@ -99,30 +100,33 @@ The tokenizer defines how to turn markdown text into tokens. // Create reference instance const marked = require('marked'); -// Get reference -const tokenizer = new marked.Tokenizer(); -const originalCodespan = tokenizer.codespan; // Override function -tokenizer.codespan = function(src) { - const match = src.match(/\$+([^\$\n]+?)\$+/); - if (match) { - return { - type: 'codespan', - raw: match[0], - text: match[1].trim() - }; +const tokenizer = { + codespan(src) { + const match = src.match(/\$+([^\$\n]+?)\$+/); + if (match) { + return { + type: 'codespan', + raw: match[0], + text: match[1].trim() + }; + } + const originalTokenizer = new marked.Tokenizer(this.options); + return originalTokenizer.codespan.apply(this, arguments); } - return originalCodespan.apply(this, arguments); }; +marked.use({ tokenizer }); + // Run marked -console.log(marked('$ latex code $', { tokenizer })); +console.log(marked('$ latex code $\n\n` other code `')); ``` **Output:** ```html -

latext code

+

latex code

+

other code

``` ### Block level tokenizer methods From 07f2b334f058b2db1bd4b0d4881228bf2ca3378f Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Sun, 19 Apr 2020 01:08:54 -0500 Subject: [PATCH 4/6] return false to use last renderer/tokenizer --- docs/USING_PRO.md | 5 +++-- src/marked.js | 18 ++++++++++++++++-- test/unit/marked-spec.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/docs/USING_PRO.md b/docs/USING_PRO.md index 2d73cf97e1..3766ee60ba 100644 --- a/docs/USING_PRO.md +++ b/docs/USING_PRO.md @@ -111,8 +111,9 @@ const tokenizer = { text: match[1].trim() }; } - const originalTokenizer = new marked.Tokenizer(this.options); - return originalTokenizer.codespan.apply(this, arguments); + + // return false to use original codespan renderer + return false; } }; diff --git a/src/marked.js b/src/marked.js index 1592aefdbd..8488e79f03 100644 --- a/src/marked.js +++ b/src/marked.js @@ -136,14 +136,28 @@ marked.use = function(extension) { if (extension.renderer) { const renderer = marked.defaults.renderer || new Renderer(); for (const prop in extension.renderer) { - renderer[prop] = extension.renderer[prop]; + const prevRenderer = renderer[prop]; + renderer[prop] = (...args) => { + let ret = extension.renderer[prop].apply(renderer, args); + if (ret === false) { + ret = prevRenderer.apply(renderer, args); + } + return ret; + }; } opts.renderer = renderer; } if (extension.tokenizer) { const tokenizer = marked.defaults.tokenizer || new Tokenizer(); for (const prop in extension.tokenizer) { - tokenizer[prop] = extension.tokenizer[prop]; + const prevTokenizer = tokenizer[prop]; + tokenizer[prop] = (...args) => { + let ret = extension.tokenizer[prop].apply(tokenizer, args); + if (ret === false) { + ret = prevTokenizer.apply(tokenizer, args); + } + return ret; + }; } opts.tokenizer = tokenizer; } diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js index bf6e6ed0bb..1b6beafdd6 100644 --- a/test/unit/marked-spec.js +++ b/test/unit/marked-spec.js @@ -171,6 +171,39 @@ paragraph expect(html).toBe('extension2 paragraph\nextension1 html\n

heading

\n'); }); + it('should use previous extension when returning false', () => { + const extension1 = { + renderer: { + paragraph(text) { + if (text !== 'original') { + return 'extension1 paragraph\n'; + } + return false; + } + } + }; + const extension2 = { + renderer: { + paragraph(text) { + if (text !== 'extension1' && text !== 'original') { + return 'extension2 paragraph\n'; + } + return false; + } + } + }; + marked.use(extension1); + marked.use(extension2); + const html = marked(` +paragraph + +extension1 + +original +`); + expect(html).toBe('extension2 paragraph\nextension1 paragraph\n

original

\n'); + }); + it('should get options with this.options', () => { const extension = { renderer: { From 10504607370b84fc985df10eae1486b201f3c7c5 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Sun, 19 Apr 2020 01:38:41 -0500 Subject: [PATCH 5/6] Fix docs --- docs/USING_PRO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/USING_PRO.md b/docs/USING_PRO.md index 3766ee60ba..193c61ce9a 100644 --- a/docs/USING_PRO.md +++ b/docs/USING_PRO.md @@ -112,7 +112,7 @@ const tokenizer = { }; } - // return false to use original codespan renderer + // return false to use original codespan tokenizer return false; } }; From d5de6af1f664e51672bf385017aaf612374f3293 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Mon, 20 Apr 2020 10:50:09 -0500 Subject: [PATCH 6/6] add marked.use section to docs --- docs/USING_PRO.md | 10 ++++++++++ docs/index.html | 1 + 2 files changed, 11 insertions(+) diff --git a/docs/USING_PRO.md b/docs/USING_PRO.md index 193c61ce9a..40aadea661 100644 --- a/docs/USING_PRO.md +++ b/docs/USING_PRO.md @@ -2,6 +2,16 @@ To champion the single-responsibility and open/closed principles, we have tried to make it relatively painless to extend marked. If you are looking to add custom functionality, this is the place to start. +

marked.use()

+ +`marked.use(options)` is the recommended way to extend marked. The options object can contain any [option](#/USING_ADVANCED.md#options) available in marked. + +The `renderer` and `tokenizer` options can be an object with functions that will be merged into the `renderer` and `tokenizer` respectively. + +The `renderer` and `tokenizer` functions can return false to fallback to the previous function. + +All other options will overwrite previously set options. +

The renderer

The renderer defines the output of the parser. diff --git a/docs/index.html b/docs/index.html index 9d68e7bb41..a7de1818c0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -154,6 +154,7 @@

Marked.js Documentation

  • Extensibility