UNPKG

4.55 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 * Resizes line numbers spans according to height of line of code
21 * @param {Element} element <pre> element
22 */
23 var _resizeElement = function (element) {
24 var codeStyles = getStyles(element);
25 var whiteSpace = codeStyles['white-space'];
26
27 if (whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line') {
28 var codeElement = element.querySelector('code');
29 var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
30 var lineNumberSizer = element.querySelector('.line-numbers-sizer');
31 var codeLines = codeElement.textContent.split(NEW_LINE_EXP);
32
33 if (!lineNumberSizer) {
34 lineNumberSizer = document.createElement('span');
35 lineNumberSizer.className = 'line-numbers-sizer';
36
37 codeElement.appendChild(lineNumberSizer);
38 }
39
40 lineNumberSizer.style.display = 'block';
41
42 codeLines.forEach(function (line, lineNumber) {
43 lineNumberSizer.textContent = line || '\n';
44 var lineSize = lineNumberSizer.getBoundingClientRect().height;
45 lineNumbersWrapper.children[lineNumber].style.height = lineSize + 'px';
46 });
47
48 lineNumberSizer.textContent = '';
49 lineNumberSizer.style.display = 'none';
50 }
51 };
52
53 /**
54 * Returns style declarations for the element
55 * @param {Element} element
56 */
57 var getStyles = function (element) {
58 if (!element) {
59 return null;
60 }
61
62 return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
63 };
64
65 window.addEventListener('resize', function () {
66 Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_NAME), _resizeElement);
67 });
68
69 Prism.hooks.add('complete', function (env) {
70 if (!env.code) {
71 return;
72 }
73
74 var code = env.element;
75 var pre = code.parentNode;
76
77 // works only for <code> wrapped inside <pre> (not inline)
78 if (!pre || !/pre/i.test(pre.nodeName)) {
79 return;
80 }
81
82 // Abort if line numbers already exists
83 if (code.querySelector('.line-numbers-rows')) {
84 return;
85 }
86
87 var addLineNumbers = false;
88 var lineNumbersRegex = /(?:^|\s)line-numbers(?:\s|$)/;
89
90 for (var element = code; element; element = element.parentNode) {
91 if (lineNumbersRegex.test(element.className)) {
92 addLineNumbers = true;
93 break;
94 }
95 }
96
97 // only add line numbers if <code> or one of its ancestors has the `line-numbers` class
98 if (!addLineNumbers) {
99 return;
100 }
101
102 // Remove the class 'line-numbers' from the <code>
103 code.className = code.className.replace(lineNumbersRegex, ' ');
104 // Add the class 'line-numbers' to the <pre>
105 if (!lineNumbersRegex.test(pre.className)) {
106 pre.className += ' line-numbers';
107 }
108
109 var match = env.code.match(NEW_LINE_EXP);
110 var linesNum = match ? match.length + 1 : 1;
111 var lineNumbersWrapper;
112
113 var lines = new Array(linesNum + 1).join('<span></span>');
114
115 lineNumbersWrapper = document.createElement('span');
116 lineNumbersWrapper.setAttribute('aria-hidden', 'true');
117 lineNumbersWrapper.className = 'line-numbers-rows';
118 lineNumbersWrapper.innerHTML = lines;
119
120 if (pre.hasAttribute('data-start')) {
121 pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
122 }
123
124 env.element.appendChild(lineNumbersWrapper);
125
126 _resizeElement(pre);
127
128 Prism.hooks.run('line-numbers', env);
129 });
130
131 Prism.hooks.add('line-numbers', function (env) {
132 env.plugins = env.plugins || {};
133 env.plugins.lineNumbers = true;
134 });
135
136 /**
137 * Global exports
138 */
139 Prism.plugins.lineNumbers = {
140 /**
141 * Get node for provided line number
142 * @param {Element} element pre element
143 * @param {Number} number line number
144 * @return {Element|undefined}
145 */
146 getLine: function (element, number) {
147 if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
148 return;
149 }
150
151 var lineNumberRows = element.querySelector('.line-numbers-rows');
152 var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
153 var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
154
155 if (number < lineNumberStart) {
156 number = lineNumberStart;
157 }
158 if (number > lineNumberEnd) {
159 number = lineNumberEnd;
160 }
161
162 var lineIndex = number - lineNumberStart;
163
164 return lineNumberRows.children[lineIndex];
165 }
166 };
167
168}());