Skip to content

enh(parser) add highlightElement, deprecate highlightBlock #3003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ Language grammar improvements:
- enh(php) Add support for Enums (#3004) [Ayesh][]
- enh(ecmascript) Add built-in types [Vaibhav Chanana][]

Deprecations:

- `highlightBlock(el)` deprecated as of 10.7.
- Please use `highlightElement(el)` instead.
- Plugin callbacks renamed `before/after:highlightBlock` => `before/after:highlightElement`
- Plugin callback now takes `el` vs `block` attribute
- The old API and callbacks will be supported until v12.

API:

- enh(api) add `unregisterLanguage` method (#3009) [Antoine du Hamel][]
Expand Down
22 changes: 16 additions & 6 deletions docs/plugin-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,24 @@ Note: This callback does not fire from highlighting resulting from auto-language

It returns nothing.


``after:highlightBlock({block, result, text})``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Deprecated as of 10.7. Please use ``after:highlightElement``.

``before:highlightBlock({block, language})``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Deprecated as of 10.7. Please use ``before:highlightElement``.


``after:highlightElement({el, result, text})``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This callback function is passed an object with two keys:

block
The HTML element of the block that's been highlighted.
el
The HTML element that's been highlighted.

result
The result object returned by `highlight` or `highlightAuto`.
Expand All @@ -119,13 +129,13 @@ text
It returns nothing.


``before:highlightBlock({block, language})``
``before:highlightElement({el, language})``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This callback function is passed an object with two keys:

block
The HTML element of the block that will be highlighted.
el
The HTML element that will be highlighted.

language
The language determined from the class attribute (or undefined).
Expand Down
61 changes: 50 additions & 11 deletions src/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -659,12 +659,12 @@ const HLJS = function(hljs) {

/** @type {HLJSPlugin} */
const brPlugin = {
"before:highlightBlock": ({ block }) => {
"before:highlightElement": ({ el }) => {
if (options.useBR) {
block.innerHTML = block.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
el.innerHTML = el.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
}
},
"after:highlightBlock": ({ result }) => {
"after:highlightElement": ({ result }) => {
if (options.useBR) {
result.value = result.value.replace(/\n/g, "<br>");
}
Expand All @@ -674,7 +674,7 @@ const HLJS = function(hljs) {
const TAB_REPLACE_RE = /^(<[^>]+>|\t)+/gm;
/** @type {HLJSPlugin} */
const tabReplacePlugin = {
"after:highlightBlock": ({ result }) => {
"after:highlightElement": ({ result }) => {
if (options.tabReplace) {
result.value = result.value.replace(TAB_REPLACE_RE, (m) =>
m.replace(/\t/g, options.tabReplace)
Expand All @@ -689,21 +689,23 @@ const HLJS = function(hljs) {
*
* @param {HighlightedHTMLElement} element - the HTML element to highlight
*/
function highlightBlock(element) {
function highlightElement(element) {
/** @type HTMLElement */
let node = null;
const language = blockLanguage(element);

if (shouldNotHighlight(language)) return;

fire("before:highlightBlock",
{ block: element, language: language });
// support for v10 API
fire("before:highlightElement",
{ el: element, language: language });

node = element;
const text = node.textContent;
const result = language ? highlight(language, text, true) : highlightAuto(text);

fire("after:highlightBlock", { block: element, result, text });
// support for v10 API
fire("after:highlightElement", { el: element, result, text });

element.innerHTML = result.value;
updateClassName(element, language, result.language);
Expand Down Expand Up @@ -749,7 +751,7 @@ const HLJS = function(hljs) {
logger.deprecated("10.6.0", "initHighlighting() is deprecated. Use highlightAll() instead.");

const blocks = document.querySelectorAll('pre code');
blocks.forEach(highlightBlock);
blocks.forEach(highlightElement);
};

// Higlights all when DOMContentLoaded fires
Expand All @@ -770,7 +772,7 @@ const HLJS = function(hljs) {
if (!domLoaded) { wantsHighlight = true; return; }

const blocks = document.querySelectorAll('pre code');
blocks.forEach(highlightBlock);
blocks.forEach(highlightElement);
}

function boot() {
Expand Down Expand Up @@ -885,10 +887,34 @@ const HLJS = function(hljs) {
return lang && !lang.disableAutodetect;
}

/**
* Upgrades the old highlightBlock plugins to the new
* highlightElement API
* @param {HLJSPlugin} plugin
*/
function upgradePluginAPI(plugin) {
// TODO: remove with v12
if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) {
plugin["before:highlightElement"] = (data) => {
plugin["before:highlightBlock"](
Object.assign({ block: data.el }, data)
);
};
}
if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) {
plugin["after:highlightElement"] = (data) => {
plugin["after:highlightBlock"](
Object.assign({ block: data.el }, data)
);
};
}
}

/**
* @param {HLJSPlugin} plugin
*/
function addPlugin(plugin) {
upgradePluginAPI(plugin);
plugins.push(plugin);
}

Expand Down Expand Up @@ -919,13 +945,26 @@ const HLJS = function(hljs) {
return fixMarkup(arg);
}

/**
*
* @param {HighlightedHTMLElement} el
*/
function deprecateHighlightBlock(el) {
logger.deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0");
logger.deprecated("10.7.0", "Please use highlightElement now.");

return highlightElement(el);
}

/* Interface definition */
Object.assign(hljs, {
highlight,
highlightAuto,
highlightAll,
fixMarkup: deprecateFixMarkup,
highlightBlock,
highlightElement,
// TODO: Remove with v12 API
highlightBlock: deprecateHighlightBlock,
configure,
initHighlighting,
initHighlightingOnLoad,
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/merge_html.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { escapeHTML } from "../lib/utils.js";

/** @type {HLJSPlugin} */
export const mergeHTMLPlugin = {
"after:highlightBlock": ({ block, result, text }) => {
const originalStream = nodeStream(block);
"after:highlightElement": ({ el, result, text }) => {
const originalStream = nodeStream(el);
if (!originalStream.length) return;

const resultNode = document.createElement('div');
Expand Down
60 changes: 41 additions & 19 deletions test/browser/highlight_block_callbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,35 @@ const {newTestCase, defaultCase, buildFakeDOM } = require('./test_case')

class ContentAdder {
constructor(params) {
this.content = params.content
this.content = params.content;
}
'before:highlightBlock'({block,language}) {
block.innerHTML += this.content;

'before:highlightElement'({ el, language}) {
el.innerHTML += this.content;
}
}

class OldPlugin {
'before:highlightBlock'({ block, language }) {}
'after:highlightBlock'({ block, result, text }) {}
}

describe('old highlightBlock plugin', function() {
it("is upgraded to new API automatically", async function() {
// we need a stub testcase juts for buildFakeDOM to work
const testCase = newTestCase({ html: "" });
await buildFakeDOM.bind(this)(testCase);

const old = new OldPlugin();
should(old["after:highlightElement"]).be.undefined();
should(old["before:highlightElement"]).be.undefined();
this.hljs.addPlugin(old);
should(old["after:highlightElement"]).not.be.undefined();
should(old["before:highlightElement"]).not.be.undefined();
});
}
);

describe('callback system', function() {
it("supports class based plugins", async function() {
const testCase = newTestCase({
Expand All @@ -23,23 +45,23 @@ describe('callback system', function() {
await buildFakeDOM.bind(this)(testCase);

this.hljs.addPlugin(new ContentAdder({content:" = 5;"}))
this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
const actual = this.block.innerHTML;
actual.should.equal(testCase.expect);

})
})

describe('before:highlightBlock', function() {
describe('before:highlightElement', function() {
it('is called', async function() {
await buildFakeDOM.bind(this)(defaultCase);
var called = false;
this.hljs.addPlugin({
'before:highlightBlock': ({block, result}) => {
'before:highlightElement': ({el, result}) => {
called = true;
}
});
this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
called.should.equal(true);
})
it('can modify block content before highlight', async function() {
Expand All @@ -50,43 +72,43 @@ describe('before:highlightBlock', function() {
await buildFakeDOM.bind(this)(testCase);

this.hljs.addPlugin({
'before:highlightBlock': ({block, language}) => {
'before:highlightElement': ({el, language}) => {
language.should.equal("javascript")
block.innerHTML = "var a;"
el.innerHTML = "var a;"
}
});

this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
const actual = this.block.innerHTML;
actual.should.equal(
`<span class="hljs-keyword">var</span> a;`);
});

})

describe('after:highlightBlock', function() {
describe('after:highlightElement', function() {
it('is called', async function() {
await buildFakeDOM.bind(this)(defaultCase);
var called = false;
this.hljs.addPlugin({
'after:highlightBlock': ({block, result}) => {
'after:highlightElement': ({el, result}) => {
called = true;
}
});
this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
called.should.equal(true);
})
it('receives result data', async function() {
await buildFakeDOM.bind(this)(defaultCase);

this.hljs.addPlugin({
'after:highlightBlock': ({block, result}) => {
'after:highlightElement': ({el, result}) => {
result.language.should.equal("javascript")
result.relevance.should.above(0)
}
});

this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
});
it('can override language if not originally provided (in class)', async function() {
var test = newTestCase({
Expand All @@ -95,12 +117,12 @@ describe('after:highlightBlock', function() {
});
await buildFakeDOM.bind(this)(test);
this.hljs.addPlugin({
'after:highlightBlock': ({block, result}) => {
'after:highlightElement': ({el, result}) => {
result.language="basic";
}
});

this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
should(this.block.outerHTML.includes(`class="hljs basic"`)).equal(true);

})
Expand All @@ -111,12 +133,12 @@ describe('after:highlightBlock', function() {
})
await buildFakeDOM.bind(this)(test);
this.hljs.addPlugin({
'after:highlightBlock': ({block, result}) => {
'after:highlightElement': ({el, result}) => {
result.value="redacted";
}
});

this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
this.block.outerHTML.should.equal(`<code class="javascript hljs">redacted</code>`);
})
})
2 changes: 1 addition & 1 deletion test/browser/test_case.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function newTestCase(opts) {
test.html = test.html || `<pre><code class='${test.language}'>${test.code}</code></pre>`;
test.runner = async function() {
await buildFakeDOM.bind(this, test)();
this.hljs.highlightBlock(this.block);
this.hljs.highlightElement(this.block);
const actual = this.block.innerHTML;
actual.should.equal(test.expect);
}
Expand Down
4 changes: 2 additions & 2 deletions test/special/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ describe('special cases tests', () => {
// Setup hljs environment
hljs.configure({ tabReplace: ' ' });
let blocks = document.querySelectorAll('pre code');
blocks.forEach(hljs.highlightBlock);
blocks.forEach(hljs.highlightElement);

// Setup hljs for non-`<pre><code>` tests
hljs.configure({ useBR: true });

blocks = document.querySelectorAll('.code');
blocks.forEach(hljs.highlightBlock);
blocks.forEach(hljs.highlightElement);
});

require('./explicitLanguage');
Expand Down
3 changes: 3 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ type PluginEvent = keyof HLJSPlugin;
type HLJSPlugin = {
'after:highlight'?: (result: HighlightResult) => void,
'before:highlight'?: (context: BeforeHighlightContext) => void,
'after:highlightElement'?: (data: { el: Element, result: HighlightResult, text: string}) => void,
'before:highlightElement'?: (data: { el: Element, language: string}) => void,
// TODO: Old API, remove with v12
'after:highlightBlock'?: (data: { block: Element, result: HighlightResult, text: string}) => void,
'before:highlightBlock'?: (data: { block: Element, language: string}) => void,
}
Expand Down