UNPKG

6.63 kBJavaScriptView Raw
1(function () {
2
3 if (typeof Prism === 'undefined' || typeof document === 'undefined') {
4 return;
5 }
6
7 /**
8 * Plugin name which is used as a class name for <pre> which is activating the plugin
9 *
10 * @type {string}
11 */
12 var PLUGIN_NAME = 'line-numbers';
13
14 /**
15 * Regular expression used for determining line breaks
16 *
17 * @type {RegExp}
18 */
19 var NEW_LINE_EXP = /\n(?!$)/g;
20
21
22 /**
23 * Global exports
24 */
25 var config = Prism.plugins.lineNumbers = {
26 /**
27 * Get node for provided line number
28 *
29 * @param {Element} element pre element
30 * @param {number} number line number
31 * @returns {Element|undefined}
32 */
33 getLine: function (element, number) {
34 if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
35 return;
36 }
37
38 var lineNumberRows = element.querySelector('.line-numbers-rows');
39 if (!lineNumberRows) {
40 return;
41 }
42 var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
43 var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
44
45 if (number < lineNumberStart) {
46 number = lineNumberStart;
47 }
48 if (number > lineNumberEnd) {
49 number = lineNumberEnd;
50 }
51
52 var lineIndex = number - lineNumberStart;
53
54 return lineNumberRows.children[lineIndex];
55 },
56
57 /**
58 * Resizes the line numbers of the given element.
59 *
60 * This function will not add line numbers. It will only resize existing ones.
61 *
62 * @param {HTMLElement} element A `<pre>` element with line numbers.
63 * @returns {void}
64 */
65 resize: function (element) {
66 resizeElements([element]);
67 },
68
69 /**
70 * Whether the plugin can assume that the units font sizes and margins are not depended on the size of
71 * the current viewport.
72 *
73 * Setting this to `true` will allow the plugin to do certain optimizations for better performance.
74 *
75 * Set this to `false` if you use any of the following CSS units: `vh`, `vw`, `vmin`, `vmax`.
76 *
77 * @type {boolean}
78 */
79 assumeViewportIndependence: true
80 };
81
82 /**
83 * Resizes the given elements.
84 *
85 * @param {HTMLElement[]} elements
86 */
87 function resizeElements(elements) {
88 elements = elements.filter(function (e) {
89 var codeStyles = getStyles(e);
90 var whiteSpace = codeStyles['white-space'];
91 return whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line';
92 });
93
94 if (elements.length == 0) {
95 return;
96 }
97
98 var infos = elements.map(function (element) {
99 var codeElement = element.querySelector('code');
100 var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
101 if (!codeElement || !lineNumbersWrapper) {
102 return undefined;
103 }
104
105 /** @type {HTMLElement} */
106 var lineNumberSizer = element.querySelector('.line-numbers-sizer');
107 var codeLines = codeElement.textContent.split(NEW_LINE_EXP);
108
109 if (!lineNumberSizer) {
110 lineNumberSizer = document.createElement('span');
111 lineNumberSizer.className = 'line-numbers-sizer';
112
113 codeElement.appendChild(lineNumberSizer);
114 }
115
116 lineNumberSizer.innerHTML = '0';
117 lineNumberSizer.style.display = 'block';
118
119 var oneLinerHeight = lineNumberSizer.getBoundingClientRect().height;
120 lineNumberSizer.innerHTML = '';
121
122 return {
123 element: element,
124 lines: codeLines,
125 lineHeights: [],
126 oneLinerHeight: oneLinerHeight,
127 sizer: lineNumberSizer,
128 };
129 }).filter(Boolean);
130
131 infos.forEach(function (info) {
132 var lineNumberSizer = info.sizer;
133 var lines = info.lines;
134 var lineHeights = info.lineHeights;
135 var oneLinerHeight = info.oneLinerHeight;
136
137 lineHeights[lines.length - 1] = undefined;
138 lines.forEach(function (line, index) {
139 if (line && line.length > 1) {
140 var e = lineNumberSizer.appendChild(document.createElement('span'));
141 e.style.display = 'block';
142 e.textContent = line;
143 } else {
144 lineHeights[index] = oneLinerHeight;
145 }
146 });
147 });
148
149 infos.forEach(function (info) {
150 var lineNumberSizer = info.sizer;
151 var lineHeights = info.lineHeights;
152
153 var childIndex = 0;
154 for (var i = 0; i < lineHeights.length; i++) {
155 if (lineHeights[i] === undefined) {
156 lineHeights[i] = lineNumberSizer.children[childIndex++].getBoundingClientRect().height;
157 }
158 }
159 });
160
161 infos.forEach(function (info) {
162 var lineNumberSizer = info.sizer;
163 var wrapper = info.element.querySelector('.line-numbers-rows');
164
165 lineNumberSizer.style.display = 'none';
166 lineNumberSizer.innerHTML = '';
167
168 info.lineHeights.forEach(function (height, lineNumber) {
169 wrapper.children[lineNumber].style.height = height + 'px';
170 });
171 });
172 }
173
174 /**
175 * Returns style declarations for the element
176 *
177 * @param {Element} element
178 */
179 function getStyles(element) {
180 if (!element) {
181 return null;
182 }
183
184 return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
185 }
186
187 var lastWidth = undefined;
188 window.addEventListener('resize', function () {
189 if (config.assumeViewportIndependence && lastWidth === window.innerWidth) {
190 return;
191 }
192 lastWidth = window.innerWidth;
193
194 resizeElements(Array.prototype.slice.call(document.querySelectorAll('pre.' + PLUGIN_NAME)));
195 });
196
197 Prism.hooks.add('complete', function (env) {
198 if (!env.code) {
199 return;
200 }
201
202 var code = /** @type {Element} */ (env.element);
203 var pre = /** @type {HTMLElement} */ (code.parentNode);
204
205 // works only for <code> wrapped inside <pre> (not inline)
206 if (!pre || !/pre/i.test(pre.nodeName)) {
207 return;
208 }
209
210 // Abort if line numbers already exists
211 if (code.querySelector('.line-numbers-rows')) {
212 return;
213 }
214
215 // only add line numbers if <code> or one of its ancestors has the `line-numbers` class
216 if (!Prism.util.isActive(code, PLUGIN_NAME)) {
217 return;
218 }
219
220 // Remove the class 'line-numbers' from the <code>
221 code.classList.remove(PLUGIN_NAME);
222 // Add the class 'line-numbers' to the <pre>
223 pre.classList.add(PLUGIN_NAME);
224
225 var match = env.code.match(NEW_LINE_EXP);
226 var linesNum = match ? match.length + 1 : 1;
227 var lineNumbersWrapper;
228
229 var lines = new Array(linesNum + 1).join('<span></span>');
230
231 lineNumbersWrapper = document.createElement('span');
232 lineNumbersWrapper.setAttribute('aria-hidden', 'true');
233 lineNumbersWrapper.className = 'line-numbers-rows';
234 lineNumbersWrapper.innerHTML = lines;
235
236 if (pre.hasAttribute('data-start')) {
237 pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
238 }
239
240 env.element.appendChild(lineNumbersWrapper);
241
242 resizeElements([pre]);
243
244 Prism.hooks.run('line-numbers', env);
245 });
246
247 Prism.hooks.add('line-numbers', function (env) {
248 env.plugins = env.plugins || {};
249 env.plugins.lineNumbers = true;
250 });
251
252}());