UNPKG

10.7 kBJavaScriptView Raw
1(function (Prism) {
2
3 // Allow only one line break
4 var inner = /(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;
5
6 /**
7 * This function is intended for the creation of the bold or italic pattern.
8 *
9 * This also adds a lookbehind group to the given pattern to ensure that the pattern is not backslash-escaped.
10 *
11 * _Note:_ Keep in mind that this adds a capturing group.
12 *
13 * @param {string} pattern
14 * @returns {RegExp}
15 */
16 function createInline(pattern) {
17 pattern = pattern.replace(/<inner>/g, function () { return inner; });
18 return RegExp(/((?:^|[^\\])(?:\\{2})*)/.source + '(?:' + pattern + ')');
19 }
20
21
22 var tableCell = /(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source;
23 var tableRow = /\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g, function () { return tableCell; });
24 var tableLine = /\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;
25
26
27 Prism.languages.markdown = Prism.languages.extend('markup', {});
28 Prism.languages.insertBefore('markdown', 'prolog', {
29 'front-matter-block': {
30 pattern: /(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,
31 lookbehind: true,
32 greedy: true,
33 inside: {
34 'punctuation': /^---|---$/,
35 'front-matter': {
36 pattern: /\S+(?:\s+\S+)*/,
37 alias: ['yaml', 'language-yaml'],
38 inside: Prism.languages.yaml
39 }
40 }
41 },
42 'blockquote': {
43 // > ...
44 pattern: /^>(?:[\t ]*>)*/m,
45 alias: 'punctuation'
46 },
47 'table': {
48 pattern: RegExp('^' + tableRow + tableLine + '(?:' + tableRow + ')*', 'm'),
49 inside: {
50 'table-data-rows': {
51 pattern: RegExp('^(' + tableRow + tableLine + ')(?:' + tableRow + ')*$'),
52 lookbehind: true,
53 inside: {
54 'table-data': {
55 pattern: RegExp(tableCell),
56 inside: Prism.languages.markdown
57 },
58 'punctuation': /\|/
59 }
60 },
61 'table-line': {
62 pattern: RegExp('^(' + tableRow + ')' + tableLine + '$'),
63 lookbehind: true,
64 inside: {
65 'punctuation': /\||:?-{3,}:?/
66 }
67 },
68 'table-header-row': {
69 pattern: RegExp('^' + tableRow + '$'),
70 inside: {
71 'table-header': {
72 pattern: RegExp(tableCell),
73 alias: 'important',
74 inside: Prism.languages.markdown
75 },
76 'punctuation': /\|/
77 }
78 }
79 }
80 },
81 'code': [
82 {
83 // Prefixed by 4 spaces or 1 tab and preceded by an empty line
84 pattern: /((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,
85 lookbehind: true,
86 alias: 'keyword'
87 },
88 {
89 // ```optional language
90 // code block
91 // ```
92 pattern: /^```[\s\S]*?^```$/m,
93 greedy: true,
94 inside: {
95 'code-block': {
96 pattern: /^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,
97 lookbehind: true
98 },
99 'code-language': {
100 pattern: /^(```).+/,
101 lookbehind: true
102 },
103 'punctuation': /```/
104 }
105 }
106 ],
107 'title': [
108 {
109 // title 1
110 // =======
111
112 // title 2
113 // -------
114 pattern: /\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,
115 alias: 'important',
116 inside: {
117 punctuation: /==+$|--+$/
118 }
119 },
120 {
121 // # title 1
122 // ###### title 6
123 pattern: /(^\s*)#.+/m,
124 lookbehind: true,
125 alias: 'important',
126 inside: {
127 punctuation: /^#+|#+$/
128 }
129 }
130 ],
131 'hr': {
132 // ***
133 // ---
134 // * * *
135 // -----------
136 pattern: /(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,
137 lookbehind: true,
138 alias: 'punctuation'
139 },
140 'list': {
141 // * item
142 // + item
143 // - item
144 // 1. item
145 pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,
146 lookbehind: true,
147 alias: 'punctuation'
148 },
149 'url-reference': {
150 // [id]: http://example.com "Optional title"
151 // [id]: http://example.com 'Optional title'
152 // [id]: http://example.com (Optional title)
153 // [id]: <http://example.com> "Optional title"
154 pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,
155 inside: {
156 'variable': {
157 pattern: /^(!?\[)[^\]]+/,
158 lookbehind: true
159 },
160 'string': /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,
161 'punctuation': /^[\[\]!:]|[<>]/
162 },
163 alias: 'url'
164 },
165 'bold': {
166 // **strong**
167 // __strong__
168
169 // allow one nested instance of italic text using the same delimiter
170 pattern: createInline(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),
171 lookbehind: true,
172 greedy: true,
173 inside: {
174 'content': {
175 pattern: /(^..)[\s\S]+(?=..$)/,
176 lookbehind: true,
177 inside: {} // see below
178 },
179 'punctuation': /\*\*|__/
180 }
181 },
182 'italic': {
183 // *em*
184 // _em_
185
186 // allow one nested instance of bold text using the same delimiter
187 pattern: createInline(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),
188 lookbehind: true,
189 greedy: true,
190 inside: {
191 'content': {
192 pattern: /(^.)[\s\S]+(?=.$)/,
193 lookbehind: true,
194 inside: {} // see below
195 },
196 'punctuation': /[*_]/
197 }
198 },
199 'strike': {
200 // ~~strike through~~
201 // ~strike~
202 // eslint-disable-next-line regexp/strict
203 pattern: createInline(/(~~?)(?:(?!~)<inner>)+\2/.source),
204 lookbehind: true,
205 greedy: true,
206 inside: {
207 'content': {
208 pattern: /(^~~?)[\s\S]+(?=\1$)/,
209 lookbehind: true,
210 inside: {} // see below
211 },
212 'punctuation': /~~?/
213 }
214 },
215 'code-snippet': {
216 // `code`
217 // ``code``
218 pattern: /(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,
219 lookbehind: true,
220 greedy: true,
221 alias: ['code', 'keyword']
222 },
223 'url': {
224 // [example](http://example.com "Optional title")
225 // [example][id]
226 // [example] [id]
227 pattern: createInline(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),
228 lookbehind: true,
229 greedy: true,
230 inside: {
231 'operator': /^!/,
232 'content': {
233 pattern: /(^\[)[^\]]+(?=\])/,
234 lookbehind: true,
235 inside: {} // see below
236 },
237 'variable': {
238 pattern: /(^\][ \t]?\[)[^\]]+(?=\]$)/,
239 lookbehind: true
240 },
241 'url': {
242 pattern: /(^\]\()[^\s)]+/,
243 lookbehind: true
244 },
245 'string': {
246 pattern: /(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,
247 lookbehind: true
248 }
249 }
250 }
251 });
252
253 ['url', 'bold', 'italic', 'strike'].forEach(function (token) {
254 ['url', 'bold', 'italic', 'strike', 'code-snippet'].forEach(function (inside) {
255 if (token !== inside) {
256 Prism.languages.markdown[token].inside.content.inside[inside] = Prism.languages.markdown[inside];
257 }
258 });
259 });
260
261 Prism.hooks.add('after-tokenize', function (env) {
262 if (env.language !== 'markdown' && env.language !== 'md') {
263 return;
264 }
265
266 function walkTokens(tokens) {
267 if (!tokens || typeof tokens === 'string') {
268 return;
269 }
270
271 for (var i = 0, l = tokens.length; i < l; i++) {
272 var token = tokens[i];
273
274 if (token.type !== 'code') {
275 walkTokens(token.content);
276 continue;
277 }
278
279 /*
280 * Add the correct `language-xxxx` class to this code block. Keep in mind that the `code-language` token
281 * is optional. But the grammar is defined so that there is only one case we have to handle:
282 *
283 * token.content = [
284 * <span class="punctuation">```</span>,
285 * <span class="code-language">xxxx</span>,
286 * '\n', // exactly one new lines (\r or \n or \r\n)
287 * <span class="code-block">...</span>,
288 * '\n', // exactly one new lines again
289 * <span class="punctuation">```</span>
290 * ];
291 */
292
293 var codeLang = token.content[1];
294 var codeBlock = token.content[3];
295
296 if (codeLang && codeBlock &&
297 codeLang.type === 'code-language' && codeBlock.type === 'code-block' &&
298 typeof codeLang.content === 'string') {
299
300 // this might be a language that Prism does not support
301
302 // do some replacements to support C++, C#, and F#
303 var lang = codeLang.content.replace(/\b#/g, 'sharp').replace(/\b\+\+/g, 'pp');
304 // only use the first word
305 lang = (/[a-z][\w-]*/i.exec(lang) || [''])[0].toLowerCase();
306 var alias = 'language-' + lang;
307
308 // add alias
309 if (!codeBlock.alias) {
310 codeBlock.alias = [alias];
311 } else if (typeof codeBlock.alias === 'string') {
312 codeBlock.alias = [codeBlock.alias, alias];
313 } else {
314 codeBlock.alias.push(alias);
315 }
316 }
317 }
318 }
319
320 walkTokens(env.tokens);
321 });
322
323 Prism.hooks.add('wrap', function (env) {
324 if (env.type !== 'code-block') {
325 return;
326 }
327
328 var codeLang = '';
329 for (var i = 0, l = env.classes.length; i < l; i++) {
330 var cls = env.classes[i];
331 var match = /language-(.+)/.exec(cls);
332 if (match) {
333 codeLang = match[1];
334 break;
335 }
336 }
337
338 var grammar = Prism.languages[codeLang];
339
340 if (!grammar) {
341 if (codeLang && codeLang !== 'none' && Prism.plugins.autoloader) {
342 var id = 'md-' + new Date().valueOf() + '-' + Math.floor(Math.random() * 1e16);
343 env.attributes['id'] = id;
344
345 Prism.plugins.autoloader.loadLanguages(codeLang, function () {
346 var ele = document.getElementById(id);
347 if (ele) {
348 ele.innerHTML = Prism.highlight(ele.textContent, Prism.languages[codeLang], codeLang);
349 }
350 });
351 }
352 } else {
353 env.content = Prism.highlight(textContent(env.content), grammar, codeLang);
354 }
355 });
356
357 var tagPattern = RegExp(Prism.languages.markup.tag.pattern.source, 'gi');
358
359 /**
360 * A list of known entity names.
361 *
362 * This will always be incomplete to save space. The current list is the one used by lowdash's unescape function.
363 *
364 * @see {@link https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/unescape.js#L2}
365 */
366 var KNOWN_ENTITY_NAMES = {
367 'amp': '&',
368 'lt': '<',
369 'gt': '>',
370 'quot': '"',
371 };
372
373 // IE 11 doesn't support `String.fromCodePoint`
374 var fromCodePoint = String.fromCodePoint || String.fromCharCode;
375
376 /**
377 * Returns the text content of a given HTML source code string.
378 *
379 * @param {string} html
380 * @returns {string}
381 */
382 function textContent(html) {
383 // remove all tags
384 var text = html.replace(tagPattern, '');
385
386 // decode known entities
387 text = text.replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi, function (m, code) {
388 code = code.toLowerCase();
389
390 if (code[0] === '#') {
391 var value;
392 if (code[1] === 'x') {
393 value = parseInt(code.slice(2), 16);
394 } else {
395 value = Number(code.slice(1));
396 }
397
398 return fromCodePoint(value);
399 } else {
400 var known = KNOWN_ENTITY_NAMES[code];
401 if (known) {
402 return known;
403 }
404
405 // unable to decode
406 return m;
407 }
408 });
409
410 return text;
411 }
412
413 Prism.languages.md = Prism.languages.markdown;
414
415}(Prism));