diff --git a/plugins/keep-markup/index.html b/plugins/keep-markup/index.html index 01d52692d2..542ef4e4c4 100644 --- a/plugins/keep-markup/index.html +++ b/plugins/keep-markup/index.html @@ -27,7 +27,7 @@ - +
@@ -39,6 +39,12 @@

How to use

However, you can deactivate the plugin for certain code element by adding the no-keep-markup class to it. You can also deactivate the plugin for the whole page by adding the no-keep-markup class to the body of the page and then selectively activate it again by adding the keep-markup class to code elements.

+

Double highlighting

+ +

Some plugins (e.g. Autoloader) need to re-highlight code blocks. This is a problem for Keep Markup because it will keep the markup of the first highlighting pass resulting in a lot of unnecessary DOM nodes and causing problems for themes and other plugins.

+ +

This problem can be fixed by adding a drop-tokens class to a code block or any of its ancestors. If drop-tokens is present, Keep Markup will ignore all span.token elements created by Prism.

+

Examples

The following source code

diff --git a/plugins/keep-markup/prism-keep-markup.js b/plugins/keep-markup/prism-keep-markup.js index 267d9d9433..c160faa88f 100644 --- a/plugins/keep-markup/prism-keep-markup.js +++ b/plugins/keep-markup/prism-keep-markup.js @@ -15,31 +15,53 @@ return; } + var dropTokens = Prism.util.isActive(env.element, 'drop-tokens', false); + /** + * Returns whether the given element should be kept. + * + * @param {HTMLElement} element + * @returns {boolean} + */ + function shouldKeep(element) { + if (dropTokens && element.nodeName.toLowerCase() === 'span' && element.classList.contains('token')) { + return false; + } + return true; + } + var pos = 0; var data = []; - var f = function (elt, baseNode) { - var o = {}; - if (!baseNode) { - // Clone the original tag to keep all attributes - o.clone = elt.cloneNode(false); - o.posOpen = pos; - data.push(o); + function processElement(element) { + if (!shouldKeep(element)) { + // don't keep this element and just process its children + processChildren(element); + return; } - for (var i = 0, l = elt.childNodes.length; i < l; i++) { - var child = elt.childNodes[i]; + + var o = { + // Clone the original tag to keep all attributes + clone: element.cloneNode(false), + posOpen: pos + }; + data.push(o); + + processChildren(element); + + o.posClose = pos; + } + function processChildren(element) { + for (var i = 0, l = element.childNodes.length; i < l; i++) { + var child = element.childNodes[i]; if (child.nodeType === 1) { // element - f(child); + processElement(child); } else if (child.nodeType === 3) { // text pos += child.data.length; } } - if (!baseNode) { - o.posClose = pos; - } - }; - f(env.element, true); + } + processChildren(env.element); - if (data && data.length) { + if (data.length) { // data is an array of all existing tags env.keepMarkup = data; } diff --git a/plugins/keep-markup/prism-keep-markup.min.js b/plugins/keep-markup/prism-keep-markup.min.js index 229c510218..6bea9d2efd 100644 --- a/plugins/keep-markup/prism-keep-markup.min.js +++ b/plugins/keep-markup/prism-keep-markup.min.js @@ -1 +1 @@ -"undefined"!=typeof Prism&&"undefined"!=typeof document&&document.createRange&&(Prism.plugins.KeepMarkup=!0,Prism.hooks.add("before-highlight",function(e){if(e.element.children.length&&Prism.util.isActive(e.element,"keep-markup",!0)){var a=0,s=[],p=function(e,n){var o={};n||(o.clone=e.cloneNode(!1),o.posOpen=a,s.push(o));for(var t=0,d=e.childNodes.length;tn.node.posOpen&&(n.nodeStart=d,n.nodeStartPos=n.node.posOpen-n.pos),n.nodeStart&&n.pos+d.data.length>=n.node.posClose&&(n.nodeEnd=d,n.nodeEndPos=n.node.posClose-n.pos),n.pos+=d.data.length);if(n.nodeStart&&n.nodeEnd){var r=document.createRange();return r.setStart(n.nodeStart,n.nodeStartPos),r.setEnd(n.nodeEnd,n.nodeEndPos),n.node.clone.appendChild(r.extractContents()),r.insertNode(n.node.clone),r.detach(),!1}}return!0};n.keepMarkup.forEach(function(e){a(n.element,{node:e,pos:0})}),n.highlightedCode=n.element.innerHTML}})); \ No newline at end of file +"undefined"!=typeof Prism&&"undefined"!=typeof document&&document.createRange&&(Prism.plugins.KeepMarkup=!0,Prism.hooks.add("before-highlight",function(e){if(e.element.children.length&&Prism.util.isActive(e.element,"keep-markup",!0)){var o=Prism.util.isActive(e.element,"drop-tokens",!1),d=0,t=[];s(e.element),t.length&&(e.keepMarkup=t)}function r(e){if(function(e){return!o||"span"!==e.nodeName.toLowerCase()||!e.classList.contains("token")}(e)){var n={clone:e.cloneNode(!1),posOpen:d};t.push(n),s(e),n.posClose=d}else s(e)}function s(e){for(var n=0,o=e.childNodes.length;nn.node.posOpen&&(n.nodeStart=d,n.nodeStartPos=n.node.posOpen-n.pos),n.nodeStart&&n.pos+d.data.length>=n.node.posClose&&(n.nodeEnd=d,n.nodeEndPos=n.node.posClose-n.pos),n.pos+=d.data.length);if(n.nodeStart&&n.nodeEnd){var r=document.createRange();return r.setStart(n.nodeStart,n.nodeStartPos),r.setEnd(n.nodeEnd,n.nodeEndPos),n.node.clone.appendChild(r.extractContents()),r.insertNode(n.node.clone),r.detach(),!1}}return!0};n.keepMarkup.forEach(function(e){s(n.element,{node:e,pos:0})}),n.highlightedCode=n.element.innerHTML}})); \ No newline at end of file diff --git a/tests/plugins/keep-markup/test.js b/tests/plugins/keep-markup/test.js index 006ce3c11a..9f5776842f 100644 --- a/tests/plugins/keep-markup/test.js +++ b/tests/plugins/keep-markup/test.js @@ -4,6 +4,7 @@ const { createScopedPrismDom } = require('../../helper/prism-dom-util'); describe('Keep Markup', function () { const { Prism, document } = createScopedPrismDom(this, { + languages: 'javascript', plugins: 'keep-markup' }); @@ -42,6 +43,25 @@ describe('Keep Markup', function () { keepMarkup(`xya`); }); + it('should support double highlighting', function () { + const pre = document.createElement('pre'); + pre.className = 'language-javascript drop-tokens'; + pre.innerHTML = 'var a = 42;'; + const code = pre.childNodes[0]; + const initial = code.innerHTML; + + Prism.highlightElement(code); + const firstPass = code.innerHTML; + + Prism.highlightElement(code); + const secondPass = code.innerHTML; + + // check that we actually did some highlighting + assert.notStrictEqual(initial, firstPass); + // check that the highlighting persists + assert.strictEqual(firstPass, secondPass); + }); + // The markup is removed if it's the last element and the element's name is a single letter: a(nchor), b(old), i(talic)... // https://github.com/PrismJS/prism/issues/1618 /*