-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
enh(parser) pre/post-highlightBlock callbacks via plugin (#2285)
* adds simple plugin API * adds `before:highlightBlock` plugin hook * adds `after:highlightBlock` plugin hook * add plugin documentation * add plugin recipes for documentation common plugin examples/patterns * refactor browser tests to avoid global index being necessary
- Loading branch information
1 parent
ff0fb39
commit 07a93c5
Showing
9 changed files
with
329 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
.. highlight:: javascript | ||
|
||
Plugins | ||
======= | ||
|
||
Highlight.js supports plugins. | ||
|
||
API | ||
--- | ||
|
||
You can add a plugin via the ``addPlugin`` API. | ||
|
||
:: | ||
|
||
// a plugin can be a class | ||
addPlugin(new SimplePlugin()) | ||
addPlugin(new MoreComplexPlugin(options)) | ||
|
||
// or simply a keyed object of functions | ||
addPlugin({ | ||
'after:highlightBlock': (args) => { | ||
... | ||
} | ||
}) | ||
|
||
Class based plugins | ||
^^^^^^^^^^^^^^^^^^^ | ||
|
||
This approach is useful for more complex plugins that need to deal with | ||
configuration options or managing state. Highlight.js will instantiate | ||
a single instance of | ||
your class and execute it's callbacks as necessary. | ||
|
||
:: | ||
|
||
class DataLanguagePlugin { | ||
constructor(options) { | ||
self.prefix = options.dataPrefix; | ||
} | ||
|
||
'after:highlightBlock'({block, result}) { | ||
// ... | ||
} | ||
} | ||
|
||
hljs.addPlugin(new DataLanguagePlugin({dataPrefix: "hljs"})) | ||
|
||
Function based plugins | ||
^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
This approach is best for simpler plugins. | ||
|
||
:: | ||
|
||
hljs.addPlugin( { | ||
'after:highlightBlock': ({block, result}) => { | ||
// move the language from the result into the dataset | ||
block.dataset.language = result.language } | ||
}) | ||
|
||
Callbacks | ||
--------- | ||
|
||
after:highlightBlock({block, result}) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
This callback function is passed an object with two keys: | ||
|
||
block | ||
The HTML element of the block that's been highlighted | ||
|
||
result | ||
The result object returned by `highlight` or `highlightAuto`. | ||
|
||
It returns nothing. | ||
|
||
|
||
before:highlightBlock({block, language}) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
This callback function is passed an object with two keys: | ||
|
||
block | ||
The HTML element of the block that will be highlighted | ||
|
||
language | ||
The language determined from the class attribute (or undefined). | ||
|
||
It returns nothing. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
.. highlight:: javascript | ||
|
||
Recipes | ||
============== | ||
|
||
Below is a collection of useful plugin "recipes" that you might find helpful. | ||
|
||
|
||
data-language | ||
------------- | ||
|
||
Let's say you'd like to track the language that was auto-detected via a | ||
`data attribute <https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes>`_. | ||
This might prove useful if you desired to add a dynamic label | ||
via CSS with ``:before``, etc. | ||
|
||
:: | ||
|
||
hljs.addPlugin( { | ||
afterHighlightBlock: ({block, result}) => { | ||
// move the language from the result into the dataset | ||
block.dataset.language = result.language } | ||
}) | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
'use strict'; | ||
|
||
const {promisify} = require('util'); | ||
|
||
const {newTestCase, defaultCase, buildFakeDOM } = require('./test_case') | ||
|
||
class ContentAdder { | ||
constructor(params) { | ||
this.content = params.content | ||
} | ||
'before:highlightBlock'({block,language}) { | ||
block.innerHTML += this.content; | ||
} | ||
} | ||
|
||
describe('callback system', function() { | ||
it("supports class based plugins", async function() { | ||
const testCase = newTestCase({ | ||
code: "var b", | ||
language: "javascript", | ||
expect: `<span class="hljs-keyword">var</span> b = <span class="hljs-number">5</span>;` | ||
}); | ||
await buildFakeDOM.bind(this)(testCase); | ||
|
||
this.hljs.addPlugin(new ContentAdder({content:" = 5;"})) | ||
this.hljs.highlightBlock(this.block); | ||
const actual = this.block.innerHTML; | ||
actual.should.equal(testCase.expect); | ||
|
||
}) | ||
}) | ||
|
||
describe('before:highlightBlock', function() { | ||
it('is called', async function() { | ||
await buildFakeDOM.bind(this)(defaultCase); | ||
var called = false; | ||
this.hljs.addPlugin({ | ||
'before:highlightBlock': ({block, result}) => { | ||
called = true; | ||
} | ||
}); | ||
this.hljs.highlightBlock(this.block); | ||
called.should.equal(true); | ||
}) | ||
it('can modify block content before highlight', async function() { | ||
const testCase = newTestCase({ | ||
code: "This is the original content.", | ||
language: "javascript" | ||
}) | ||
await buildFakeDOM.bind(this)(testCase); | ||
|
||
this.hljs.addPlugin({ | ||
'before:highlightBlock': ({block, language}) => { | ||
language.should.equal("javascript") | ||
block.innerHTML = "var a;" | ||
} | ||
}); | ||
|
||
this.hljs.highlightBlock(this.block); | ||
const actual = this.block.innerHTML; | ||
actual.should.equal( | ||
`<span class="hljs-keyword">var</span> a;`); | ||
}); | ||
|
||
}) | ||
|
||
describe('after:highlightBlock', function() { | ||
it('is called', async function() { | ||
await buildFakeDOM.bind(this)(defaultCase); | ||
var called = false; | ||
this.hljs.addPlugin({ | ||
'after:highlightBlock': ({block, result}) => { | ||
called = true; | ||
} | ||
}); | ||
this.hljs.highlightBlock(this.block); | ||
called.should.equal(true); | ||
}) | ||
it('receives result data', async function() { | ||
await buildFakeDOM.bind(this)(defaultCase); | ||
|
||
this.hljs.addPlugin({ | ||
'after:highlightBlock': ({block, result}) => { | ||
result.language.should.equal("javascript") | ||
result.relevance.should.above(0) | ||
} | ||
}); | ||
|
||
this.hljs.highlightBlock(this.block); | ||
}); | ||
it('can override language if not originally provided (in class)', async function() { | ||
var test = newTestCase({ | ||
code: "anothingstring", | ||
language: "" | ||
}); | ||
await buildFakeDOM.bind(this)(test); | ||
this.hljs.addPlugin({ | ||
'after:highlightBlock': ({block, result}) => { | ||
result.language="basic"; | ||
} | ||
}); | ||
|
||
this.hljs.highlightBlock(this.block); | ||
should(this.block.outerHTML.includes(`class="hljs basic"`)).equal(true); | ||
|
||
}) | ||
it('can modify result and affect the render output', async function() { | ||
var test = newTestCase({ | ||
code: "var a = 4;", | ||
language: "javascript" | ||
}) | ||
await buildFakeDOM.bind(this)(test); | ||
this.hljs.addPlugin({ | ||
'after:highlightBlock': ({block, result}) => { | ||
result.value="redacted"; | ||
} | ||
}); | ||
|
||
this.hljs.highlightBlock(this.block); | ||
this.block.outerHTML.should.equal(`<code class="javascript hljs">redacted</code>`); | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,5 @@ | ||
'use strict'; | ||
|
||
describe('browser build', function() { | ||
before(function() { | ||
this.text = 'var say = "Hello";'; | ||
this.html = `<pre><code class="javascript">${this.text}</code></pre>`; | ||
this.expect = '<span class="hljs-keyword">' + | ||
'var</span> say = <span class="hljs-string">' + | ||
'"Hello"</span>;'; | ||
}); | ||
|
||
require('./plain'); | ||
require('./worker'); | ||
}); | ||
require('./plain'); | ||
require('./worker'); | ||
require('./highlight_block_callbacks'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,24 @@ | ||
'use strict'; | ||
|
||
const { JSDOM } = require('jsdom'); | ||
const utility = require('../utility'); | ||
const {promisify} = require('util'); | ||
const glob = promisify(require('glob')); | ||
const fs = require('fs'); | ||
|
||
const buildFakeDOM = async function() { | ||
// Will match both `highlight.pack.js` and `highlight.min.js` | ||
const filePath = utility.buildPath('..', 'build', 'highlight.*.js'); | ||
const hljsPath = await glob(filePath) | ||
const hljsFiles = await hljsPath.map(path => fs.readFileSync(path, 'utf8')) | ||
const hljsScript = await hljsFiles.map(file => `<script>${file}</script>`).join("") | ||
const { window} = await new JSDOM(hljsScript + this.html, { runScripts: "dangerously" }) | ||
|
||
this.block = window.document.querySelector('pre code'); | ||
this.hljs = window.hljs; | ||
}; | ||
const {newTestCase, defaultCase, buildFakeDOM } = require('./test_case') | ||
|
||
describe('browser with html with quotes in attributes', function() { | ||
it('should property escape all quotes', async function() { | ||
this.text = "const oops = pick(employee, <span data-title=\" Type '"height"' is not assignable to type '"name" | "age'" | "profession"'.\">['name', 'height']</span>)\n" | ||
this.html = `<pre><code class="javascript">${this.text}</code></pre>`; | ||
|
||
// can't use before because we need to do setup first | ||
await buildFakeDOM.bind(this)(); | ||
|
||
this.hljs.highlightBlock(this.block); | ||
const actual = this.block.innerHTML; | ||
actual.should.equal( | ||
`<span class="hljs-keyword">const</span> oops = pick(employee, <span data-title=" Type '"height"' is not assignable to type '"name" | "age'" | "profession"'.">[<span class="hljs-string">'name'</span>, <span class="hljs-string">'height'</span>]</span>)\n`); | ||
}); | ||
it('should property escape all quotes', | ||
newTestCase({ | ||
code: "const oops = pick(employee, <span data-title=\" Type '"height"' is not assignable to type '"name" | "age'" | "profession"'.\">['name', 'height']</span>)\n", | ||
language: "javascript", | ||
expect: `<span class="hljs-keyword">const</span> oops = pick(employee, <span data-title=" Type '"height"' is not assignable to type '"name" | "age'" | "profession"'.">[<span class="hljs-string">'name'</span>, <span class="hljs-string">'height'</span>]</span>)\n` | ||
}).runner | ||
); | ||
}) | ||
|
||
describe('plain browser', function() { | ||
before(async function() { await buildFakeDOM.bind(this)(); }); | ||
|
||
it('should return relevance key', function() { | ||
it('should return relevance key', async function() { | ||
await buildFakeDOM.bind(this, defaultCase)(); | ||
var out = this.hljs.highlight("javascript",""); | ||
out.relevance.should.equal(0); | ||
}) | ||
|
||
it('should highlight block', function() { | ||
this.hljs.highlightBlock(this.block); | ||
|
||
const actual = this.block.innerHTML; | ||
|
||
actual.should.equal(this.expect); | ||
}); | ||
|
||
it('should highlight block', defaultCase.runner); | ||
}); |
Oops, something went wrong.