UNPKG

6.17 kBJavaScriptView Raw
1(function () {
2
3 if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) {
4 return;
5 }
6
7 function $$(expr, con) {
8 return Array.prototype.slice.call((con || document).querySelectorAll(expr));
9 }
10
11 function hasClass(element, className) {
12 className = " " + className + " ";
13 return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1
14 }
15
16 function callFunction(func) {
17 func();
18 }
19
20 // Some browsers round the line-height, others don't.
21 // We need to test for it to position the elements properly.
22 var isLineHeightRounded = (function () {
23 var res;
24 return function () {
25 if (typeof res === 'undefined') {
26 var d = document.createElement('div');
27 d.style.fontSize = '13px';
28 d.style.lineHeight = '1.5';
29 d.style.padding = 0;
30 d.style.border = 0;
31 d.innerHTML = '&nbsp;<br />&nbsp;';
32 document.body.appendChild(d);
33 // Browsers that round the line-height should have offsetHeight === 38
34 // The others should have 39.
35 res = d.offsetHeight === 38;
36 document.body.removeChild(d);
37 }
38 return res;
39 }
40 }());
41
42 /**
43 * Highlights the lines of the given pre.
44 *
45 * This function is split into a DOM measuring and mutate phase to improve performance.
46 * The returned function mutates the DOM when called.
47 *
48 * @param {HTMLElement} pre
49 * @param {string} [lines]
50 * @param {string} [classes='']
51 * @returns {() => void}
52 */
53 function highlightLines(pre, lines, classes) {
54 lines = typeof lines === 'string' ? lines : pre.getAttribute('data-line');
55
56 var ranges = lines.replace(/\s+/g, '').split(',');
57 var offset = +pre.getAttribute('data-line-offset') || 0;
58
59 var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
60 var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
61 var hasLineNumbers = hasClass(pre, 'line-numbers');
62 var parentElement = hasLineNumbers ? pre : pre.querySelector('code') || pre;
63 var mutateActions = /** @type {(() => void)[]} */ ([]);
64
65 ranges.forEach(function (currentRange) {
66 var range = currentRange.split('-');
67
68 var start = +range[0];
69 var end = +range[1] || start;
70
71 var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
72
73 mutateActions.push(function () {
74 line.setAttribute('aria-hidden', 'true');
75 line.setAttribute('data-range', currentRange);
76 line.className = (classes || '') + ' line-highlight';
77 });
78
79 // if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
80 if (hasLineNumbers && Prism.plugins.lineNumbers) {
81 var startNode = Prism.plugins.lineNumbers.getLine(pre, start);
82 var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
83
84 if (startNode) {
85 var top = startNode.offsetTop + 'px';
86 mutateActions.push(function () {
87 line.style.top = top;
88 });
89 }
90
91 if (endNode) {
92 var height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
93 mutateActions.push(function () {
94 line.style.height = height;
95 });
96 }
97 } else {
98 mutateActions.push(function () {
99 line.setAttribute('data-start', start);
100
101 if (end > start) {
102 line.setAttribute('data-end', end);
103 }
104
105 line.style.top = (start - offset - 1) * lineHeight + 'px';
106
107 line.textContent = new Array(end - start + 2).join(' \n');
108 });
109 }
110
111 mutateActions.push(function () {
112 // allow this to play nicely with the line-numbers plugin
113 // need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
114 parentElement.appendChild(line);
115 });
116 });
117
118 return function () {
119 mutateActions.forEach(callFunction);
120 };
121 }
122
123 function applyHash() {
124 var hash = location.hash.slice(1);
125
126 // Remove pre-existing temporary lines
127 $$('.temporary.line-highlight').forEach(function (line) {
128 line.parentNode.removeChild(line);
129 });
130
131 var range = (hash.match(/\.([\d,-]+)$/) || [, ''])[1];
132
133 if (!range || document.getElementById(hash)) {
134 return;
135 }
136
137 var id = hash.slice(0, hash.lastIndexOf('.')),
138 pre = document.getElementById(id);
139
140 if (!pre) {
141 return;
142 }
143
144 if (!pre.hasAttribute('data-line')) {
145 pre.setAttribute('data-line', '');
146 }
147
148 var mutateDom = highlightLines(pre, range, 'temporary ');
149 mutateDom();
150
151 document.querySelector('.temporary.line-highlight').scrollIntoView();
152 }
153
154 var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
155
156 Prism.hooks.add('before-sanity-check', function (env) {
157 var pre = env.element.parentNode;
158 var lines = pre && pre.getAttribute('data-line');
159
160 if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
161 return;
162 }
163
164 /*
165 * Cleanup for other plugins (e.g. autoloader).
166 *
167 * Sometimes <code> blocks are highlighted multiple times. It is necessary
168 * to cleanup any left-over tags, because the whitespace inside of the <div>
169 * tags change the content of the <code> tag.
170 */
171 var num = 0;
172 $$('.line-highlight', pre).forEach(function (line) {
173 num += line.textContent.length;
174 line.parentNode.removeChild(line);
175 });
176 // Remove extra whitespace
177 if (num && /^( \n)+$/.test(env.code.slice(-num))) {
178 env.code = env.code.slice(0, -num);
179 }
180 });
181
182 Prism.hooks.add('complete', function completeHook(env) {
183 var pre = env.element.parentNode;
184 var lines = pre && pre.getAttribute('data-line');
185
186 if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
187 return;
188 }
189
190 clearTimeout(fakeTimer);
191
192 var hasLineNumbers = Prism.plugins.lineNumbers;
193 var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
194
195 if (hasClass(pre, 'line-numbers') && hasLineNumbers && !isLineNumbersLoaded) {
196 Prism.hooks.add('line-numbers', completeHook);
197 } else {
198 var mutateDom = highlightLines(pre, lines);
199 mutateDom();
200 fakeTimer = setTimeout(applyHash, 1);
201 }
202 });
203
204 window.addEventListener('hashchange', applyHash);
205 window.addEventListener('resize', function () {
206 var actions = [];
207 $$('pre[data-line]').forEach(function (pre) {
208 actions.push(highlightLines(pre));
209 });
210 actions.forEach(callFunction);
211 });
212
213})();