UNPKG

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