Skip to content

Commit

Permalink
chore(plugins) move useBR, tabReplace, and HTML merging into internal…
Browse files Browse the repository at this point in the history
… plugins (#2873)

- This begins the official deprecation of HTML merging and tabReplace.
- `useBR` was previously deprecated.
  • Loading branch information
joshgoebel authored Dec 15, 2020
1 parent 1846044 commit 03f2023
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 173 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
"init-declarations": ["error", "always"],
"array-callback-return": "error",
"block-scoped-var": "error",
"no-multiple-empty-lines": ["error", { max: 2 }],
// we like our semi-colons
semi: ["error", "always"],
// our codebase doesn't do this at all, so disabled for now
Expand Down Expand Up @@ -57,7 +58,7 @@ module.exports = {
// // languages are all over the map and we don't want to
// // do a mass edit so turn off the most egregious rule violations
// indent: "off",
"camelcase": 0,
camelcase: 0,
"no-control-regex": 0,
"no-useless-escape": 0,
"comma-dangle": 1,
Expand Down
27 changes: 27 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ Grammar improvements:
- enh(dart) Fix empty doc-comment eating next line [Jan Pilzer][]
- enh(asciidoc) Adds support for unconstrained bold syntax (#2869) [Guillaume Grossetie][]

Deprecations (*these will be mentioned in every release*):

- HTML "merging" is deprecated. (#2873) [Josh Goebel][]
- HTML inside `<pre>` blocks will no longer be magically merged back into the
highlighted code's HTML result - it will instead be silently removed.
- Consider [using a plugin][htmlPlugin] if you truly need this functionality
- Deprecated as of 10.5.0 - will be removed in v11.
- `tabReplace` option deprecated. (#2873) [Josh Goebel][]
- **Consider:** Use the CSS `tab-size` property, or simply pre-process the
text yourself before rendering the initial HTML
- otherwise, [use a plugin][tabPlugin]
- Deprecated as of 10.5.0 - will be removed in v11.
- `useBR` option deprecated. (#2559) [Josh Goebel][]
- **Recommended:** You really should just use the HTML `<pre>` tag
- or perhaps try CSS `white-space: pre;`
- otherwise, [use a plugin][brPlugin]
- Deprecated as of 10.3.0 - will be removed in v11.
- `requireLanguage` API is deprecated, will be removed in v11.0.
- **Consider:** Use `getLanguage` (with custom error handling) or built-time dependencies.
- See [Library API](https://highlightjs.readthedocs.io/en/latest/api.html#requirelanguage-name) for more information.
- Deprecated as of 10.4.0 - will be removed in v11.

[htmlPlugin]: https://github.com/highlightjs/highlight.js/issues/2889
[tabPlugin]: https://github.com/highlightjs/highlight.js/issues/2874
[brPlugin]: https://github.com/highlightjs/highlight.js/issues/2559

[Martin Dørum]: https://github.com/mortie
[Jan Pilzer]: https://github.com/Hirse
[Oldes Huhuman]: https://github.com/Oldes
Expand Down Expand Up @@ -101,6 +127,7 @@ Parser:
- enh(grammars) allow `classNameAliases` for more complex grammars [Josh Goebel][]
- fix(vue): Language name now appears in CSS class (#2807) [Michael Rush][]
- (chore) Clean up all regexs to be UTF-8 compliant/ready (#2759) [Josh Goebel][]
- enh(grammars) allow `classNameAliases` for more complex grammars [Josh Goebel][]

New Languages:

Expand Down
48 changes: 37 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ Highlight.js supports over 180 different languages in the core library. There a
language plugins available for additional languages. You can find the full list of supported languages
in [SUPPORTED_LANGUAGES.md][9].

## Custom Initialization
## Custom Scenarios

When you need a bit more control over the initialization of
highlight.js, you can use the [`highlightBlock`][3] and [`configure`][4]
functions. This allows you to control *what* to highlight and *when*.
functions. This allows you to better control *what* to highlight and *when*.

Here’s an equivalent way to calling [`initHighlightingOnLoad`][1] using
vanilla JS:
Here’s the equivalent of calling [`initHighlightingOnLoad`][1] using
only vanilla JS:

```js
document.addEventListener('DOMContentLoaded', (event) => {
Expand All @@ -104,19 +104,45 @@ document.addEventListener('DOMContentLoaded', (event) => {
});
```

You can use any tags instead of `<pre><code>` to mark up your code. If
you don't use a container that preserves line breaks you will need to
configure highlight.js to use the `<br>` tag:
Please refer to the documentation for [`configure`][4] options.

```js
hljs.configure({useBR: true});

document.querySelectorAll('div.code').forEach((block) => {
### Using custom HTML elements for code blocks

We strongly recommend `<pre><code>` wrapping for code blocks. It's quite
semantic and "just works" out of the box with zero fiddling. It is possible to
use other HTML elements (or combos), but you may need to pay special attention to
preserving linebreaks.

Let's say your markup for code blocks uses divs:

```html
<div class='code'>...</div>
```

To highlight such blocks manually:

```js
// first, find all the div.code blocks
document.querySelectorAll('div.code').forEach(block => {
// then highlight each
hljs.highlightBlock(block);
});
```

For other options refer to the documentation for [`configure`][4].
Without using a tag that preserves linebreaks (like `pre`) you'll need some
additional CSS to help preserve them. You could also [pre and post-process line
breaks with a plug-in][brPlugin], but *we recommend using CSS*.

[brPlugin]: https://github.com/highlightjs/highlight.js/issues/2559

To preserve linebreaks inside a `div` using CSS:

```css
div.code {
white-space: pre;
}
```


## Using with Vue.js
Expand Down
9 changes: 6 additions & 3 deletions docs/plugin-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ Note: This callback does not fire from highlighting resulting from auto-language
It returns nothing.


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

This callback function is passed an object with two keys:

Expand All @@ -112,11 +112,14 @@ block
result
The result object returned by `highlight` or `highlightAuto`.

text
The raw text that was to be highlighted.

It returns nothing.


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

This callback function is passed an object with two keys:

Expand Down
50 changes: 33 additions & 17 deletions src/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import * as MODES from './lib/modes.js';
import { compileLanguage } from './lib/mode_compiler.js';
import * as packageJSON from '../package.json';
import { BuildVuePlugin } from "./plugins/vue.js";
import { mergeHTMLPlugin } from "./plugins/merge_html.js";

const escape = utils.escapeHTML;
const inherit = utils.inherit;

const { nodeStream, mergeStreams } = utils;
const NO_MATCH = Symbol("nomatch");

/**
Expand Down Expand Up @@ -667,6 +666,32 @@ const HLJS = function(hljs) {
return result.join(' ').trim();
}

/** @type {HLJSPlugin} */
const brPlugin = {
"before:highlightBlock": ({ block }) => {
if (options.useBR) {
block.innerHTML = block.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
}
},
"after:highlightBlock": ({ result }) => {
if (options.useBR) {
result.value = result.value.replace(/\n/g, "<br>");
}
}
};

const TAB_REPLACE_RE = /^(<[^>]+>|\t)+/gm;
/** @type {HLJSPlugin} */
const tabReplacePlugin = {
"after:highlightBlock": ({ result }) => {
if (options.tabReplace) {
result.value = result.value.replace(TAB_REPLACE_RE, (m) =>
m.replace(/\t/g, options.tabReplace)
);
}
}
};

/**
* Applies highlighting to a DOM node containing code. Accepts a DOM node and
* two optional parameters for fixMarkup.
Expand All @@ -683,24 +708,11 @@ const HLJS = function(hljs) {
fire("before:highlightBlock",
{ block: element, language: language });

if (options.useBR) {
node = document.createElement('div');
node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
} else {
node = element;
}
node = element;
const text = node.textContent;
const result = language ? highlight(language, text, true) : highlightAuto(text);

const originalStream = nodeStream(node);
if (originalStream.length) {
const resultNode = document.createElement('div');
resultNode.innerHTML = result.value;
result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
}
result.value = fixMarkup(result.value);

fire("after:highlightBlock", { block: element, result: result });
fire("after:highlightBlock", { block: element, result, text });

element.innerHTML = result.value;
element.className = buildClassName(element.className, language, result.language);
Expand Down Expand Up @@ -909,6 +921,10 @@ const HLJS = function(hljs) {
// merge all the modes/regexs into our main object
Object.assign(hljs, MODES);

// built-in plugins, likely to be moved out of core in the future
hljs.addPlugin(brPlugin); // slated to be removed in v11
hljs.addPlugin(mergeHTMLPlugin);
hljs.addPlugin(tabReplacePlugin);
return hljs;
};

Expand Down
140 changes: 0 additions & 140 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,143 +34,3 @@ export function inherit(original, ...objects) {
return /** @type {T} */ (result);
}

/* Stream merging */

/**
* @typedef Event
* @property {'start'|'stop'} event
* @property {number} offset
* @property {Node} node
*/

/**
* @param {Node} node
*/
function tag(node) {
return node.nodeName.toLowerCase();
}

/**
* @param {Node} node
*/
export function nodeStream(node) {
/** @type Event[] */
const result = [];
(function _nodeStream(node, offset) {
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 3) {
offset += child.nodeValue.length;
} else if (child.nodeType === 1) {
result.push({
event: 'start',
offset: offset,
node: child
});
offset = _nodeStream(child, offset);
// Prevent void elements from having an end tag that would actually
// double them in the output. There are more void elements in HTML
// but we list only those realistically expected in code display.
if (!tag(child).match(/br|hr|img|input/)) {
result.push({
event: 'stop',
offset: offset,
node: child
});
}
}
}
return offset;
})(node, 0);
return result;
}

/**
* @param {any} original - the original stream
* @param {any} highlighted - stream of the highlighted source
* @param {string} value - the original source itself
*/
export function mergeStreams(original, highlighted, value) {
let processed = 0;
let result = '';
const nodeStack = [];

function selectStream() {
if (!original.length || !highlighted.length) {
return original.length ? original : highlighted;
}
if (original[0].offset !== highlighted[0].offset) {
return (original[0].offset < highlighted[0].offset) ? original : highlighted;
}

/*
To avoid starting the stream just before it should stop the order is
ensured that original always starts first and closes last:
if (event1 == 'start' && event2 == 'start')
return original;
if (event1 == 'start' && event2 == 'stop')
return highlighted;
if (event1 == 'stop' && event2 == 'start')
return original;
if (event1 == 'stop' && event2 == 'stop')
return highlighted;
... which is collapsed to:
*/
return highlighted[0].event === 'start' ? original : highlighted;
}

/**
* @param {Node} node
*/
function open(node) {
/** @param {Attr} attr */
function attributeString(attr) {
return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
}
// @ts-ignore
result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>';
}

/**
* @param {Node} node
*/
function close(node) {
result += '</' + tag(node) + '>';
}

/**
* @param {Event} event
*/
function render(event) {
(event.event === 'start' ? open : close)(event.node);
}

while (original.length || highlighted.length) {
let stream = selectStream();
result += escapeHTML(value.substring(processed, stream[0].offset));
processed = stream[0].offset;
if (stream === original) {
/*
On any opening or closing tag of the original markup we first close
the entire highlighted node stack, then render the original tag along
with all the following original tags at the same offset and then
reopen all the tags on the highlighted stack.
*/
nodeStack.reverse().forEach(close);
do {
render(stream.splice(0, 1)[0]);
stream = selectStream();
} while (stream === original && stream.length && stream[0].offset === processed);
nodeStack.reverse().forEach(open);
} else {
if (stream[0].event === 'start') {
nodeStack.push(stream[0].node);
} else {
nodeStack.pop();
}
render(stream.splice(0, 1)[0]);
}
}
return result + escapeHTML(value.substr(processed));
}
Loading

0 comments on commit 03f2023

Please sign in to comment.