prism-keep-markup.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. (function () {
  2. if (typeof Prism === 'undefined' || typeof document === 'undefined' || !document.createRange) {
  3. return;
  4. }
  5. Prism.plugins.KeepMarkup = true;
  6. Prism.hooks.add('before-highlight', function (env) {
  7. if (!env.element.children.length) {
  8. return;
  9. }
  10. if (!Prism.util.isActive(env.element, 'keep-markup', true)) {
  11. return;
  12. }
  13. var dropTokens = Prism.util.isActive(env.element, 'drop-tokens', false);
  14. /**
  15. * Returns whether the given element should be kept.
  16. *
  17. * @param {HTMLElement} element
  18. * @returns {boolean}
  19. */
  20. function shouldKeep(element) {
  21. if (dropTokens && element.nodeName.toLowerCase() === 'span' && element.classList.contains('token')) {
  22. return false;
  23. }
  24. return true;
  25. }
  26. var pos = 0;
  27. var data = [];
  28. function processElement(element) {
  29. if (!shouldKeep(element)) {
  30. // don't keep this element and just process its children
  31. processChildren(element);
  32. return;
  33. }
  34. var o = {
  35. // Store original element so we can restore it after highlighting
  36. element: element,
  37. posOpen: pos
  38. };
  39. data.push(o);
  40. processChildren(element);
  41. o.posClose = pos;
  42. }
  43. function processChildren(element) {
  44. for (var i = 0, l = element.childNodes.length; i < l; i++) {
  45. var child = element.childNodes[i];
  46. if (child.nodeType === 1) { // element
  47. processElement(child);
  48. } else if (child.nodeType === 3) { // text
  49. pos += child.data.length;
  50. }
  51. }
  52. }
  53. processChildren(env.element);
  54. if (data.length) {
  55. // data is an array of all existing tags
  56. env.keepMarkup = data;
  57. }
  58. });
  59. Prism.hooks.add('after-highlight', function (env) {
  60. if (env.keepMarkup && env.keepMarkup.length) {
  61. var walk = function (elt, nodeState) {
  62. for (var i = 0, l = elt.childNodes.length; i < l; i++) {
  63. var child = elt.childNodes[i];
  64. if (child.nodeType === 1) { // element
  65. if (!walk(child, nodeState)) {
  66. return false;
  67. }
  68. } else if (child.nodeType === 3) { // text
  69. if (!nodeState.nodeStart && nodeState.pos + child.data.length > nodeState.node.posOpen) {
  70. // We found the start position
  71. nodeState.nodeStart = child;
  72. nodeState.nodeStartPos = nodeState.node.posOpen - nodeState.pos;
  73. }
  74. if (nodeState.nodeStart && nodeState.pos + child.data.length >= nodeState.node.posClose) {
  75. // We found the end position
  76. nodeState.nodeEnd = child;
  77. nodeState.nodeEndPos = nodeState.node.posClose - nodeState.pos;
  78. }
  79. nodeState.pos += child.data.length;
  80. }
  81. if (nodeState.nodeStart && nodeState.nodeEnd) {
  82. // Select the range and wrap it with the element
  83. var range = document.createRange();
  84. range.setStart(nodeState.nodeStart, nodeState.nodeStartPos);
  85. range.setEnd(nodeState.nodeEnd, nodeState.nodeEndPos);
  86. nodeState.node.element.innerHTML = '';
  87. nodeState.node.element.appendChild(range.extractContents());
  88. range.insertNode(nodeState.node.element);
  89. range.detach();
  90. // Process is over
  91. return false;
  92. }
  93. }
  94. return true;
  95. };
  96. // For each tag, we walk the DOM to reinsert it
  97. env.keepMarkup.forEach(function (node) {
  98. walk(env.element, {
  99. node: node,
  100. pos: 0
  101. });
  102. });
  103. // Store new highlightedCode for later hooks calls
  104. env.highlightedCode = env.element.innerHTML;
  105. }
  106. });
  107. }());