-
-
Notifications
You must be signed in to change notification settings - Fork 51
/
Copy pathmarkdeep.js
2272 lines (1859 loc) · 131 KB
/
markdeep.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
markdeep.js
Version 0.05
Copyright 2015, Morgan McGuire, http://casual-effects.com
All rights reserved.
-------------------------------------------------------------
See http://casual-effects.com/markdeep for documentation on how to
use this script make your plain text documents render beautifully
in web browsers.
Markdeep was created by Morgan McGuire. It extends the work of:
- John Gruber's original Markdown
- Ben Hollis' Maruku Markdown dialect
- Michel Fortin's Markdown Extras dialect
- Ivan Sagalaev's highlight.js
- Contributors to the above open source projects
-------------------------------------------------------------
You may use, extend, and redistribute this code under the terms of
the BSD license at https://opensource.org/licenses/BSD-2-Clause.
and highlight.js(https://github.com/isagalaev/highlight.js) by Ivan
Sagalaev, which is used for code highlighting. Each has their
respective license with them.
*/
/**
See http://casual-effects.com/markdeep for @license and documentation.
markdeep.min.js version 0.05
Copyright 2015, Morgan McGuire
All rights reserved.
(BSD 2-clause license)
highlight.min.js 8.8.0 from https://highlightjs.org/
Copyright 2006, Ivan Sagalaev
All rights reserved.
(BSD 3-clause license)
*/
(function() {
'use strict';
// For minification. This is admittedly scary.
var _ = String.prototype;
_.rp = _.replace;
_.ss = _.substring;
/** Enable for debugging to view character bounds in diagrams */
var DEBUG_SHOW_GRID = false;
/** Overlay the non-empty characters of the original source in diagrams */
var DEBUG_SHOW_SOURCE = DEBUG_SHOW_GRID;
/** Use to suppress passing through text in diagrams */
var DEBUG_HIDE_PASSTHROUGH = DEBUG_SHOW_SOURCE;
/** In pixels of lines in diagrams */
var STROKE_WIDTH = 2;
/** A box of these denotes a diagram */
var DIAGRAM_MARKER = '*';
// http://stackoverflow.com/questions/1877475/repeat-character-n-times
var DIAGRAM_START = Array(5 + 1).join(DIAGRAM_MARKER);
function entag(tag, content) {
return '<' + tag + '>' + content + '</' + tag + '>';
}
var BODY_STYLESHEET = entag('style', 'body { max-width: 680px;' +
'margin:auto;' +
'padding:20px;' +
'text-align:justify;' +
'line-height:139%; ' +
'color:#222;' +
'font-family: Palatino,Georgia,"Times New Roman",serif;}');
/** You can embed your own stylesheet AFTER the <script> tags in your
file to override these defaults. */
var STYLESHEET = entag('style',
'body{' +
'counter-reset: h1 h2 h3 h4 h5 h6;' +
'}' +
'.md div.title{' +
'font-size:26px;' +
'font-weight:800;' +
'padding-bottom:0px;' +
'line-height:120%;' +
'text-align:center;' +
'}' +
'.md div.afterTitles{height:0px;}' +
'.md div.subtitle{' +
'text-align:center;' +
'}' +
'.md div.title, h1, h2, h3, h4, h5, h6, .md .shortTOC, .md .longTOC {' +
'font-family:Verdana,Helvetica,Arial,sans-serif;' +
'}' +
'.md svg.diagram{' +
'display:block;' +
'font-family:Menlo,\'Lucida Console\',monospace;'+
'font-size:12px;' +
'text-align:center;' +
'stroke-linecap:round;' +
'stroke-width:' + STROKE_WIDTH + 'px;'+
'}' +
'h1{' +
'padding-bottom:3px;' +
'padding-top:15px;' +
'border-bottom:3px solid;' +
'border-top:none;' +
'font-size:20px;' +
'counter-reset: h2 h3 h4 h5 h6;' +
'clear:both;' +
'}' +
'h2{' +
'counter-reset: h3 h4 h5 h6;' +
'font-family:Helvetica,Arial,sans-serif;' +
'padding-bottom:3px;' +
'padding-top:15px;' +
'border-bottom:2px solid #999;' +
'border-top:none;' +
'color:#555;' +
'font-size:18px;' +
'clear:both;' +
'}' +
'h3, h4, h5, h6{' +
'font-family:Helvetica,Arial,sans-serif;' +
'padding-bottom:3px;' +
'padding-top:15px;' +
'border-top:none;' +
'color:#555;' +
'font-size:16px;' +
'clear:both;' +
'}' +
'h3{counter-reset: h4 h5 h6;}' +
'h4{counter-reset: h5 h6;}' +
'h5{counter-reset: h6;}' +
'.md table{' +
'margin:auto;' +
'border-collapse:collapse;' +
'}' +
'.md th{' +
'color:#FFF;' +
'background-color:#AAA;' +
'border:1px solid #888;' +
// top right bottom left
'padding:8px 15px 8px 15px;' +
'}' +
'.md td{' +
// top right bottom left
'padding:5px 15px 5px 15px;' +
'border:1px solid #888;' +
'}' +
'.md tr:nth-child(even){'+
'background:#EEE;' +
'}' +
'.md a:link, .md a:visited{color:#38A;text-decoration:none;}' +
'.md a:hover{text-decoration:underline}' +
'.md dt{' +
'font-weight:700;' +
'}' +
'.md dd{' +
'padding-bottom:18px;' +
'}' +
'.md code{' +
'white-space:pre;' +
'}' +
'.markdeepFooter{font-size:9px;text-align:right;padding-top:80px;color:#999;}' +
'.md .longTOC{float:right;font-size:12px;line-height:15px;border-left:1px solid #CCC;padding-left:15px;margin:15px 0px 15px 25px;}' +
'.md .shortTOC{text-align:center;font-weight:bold;margin-top:15px;font-size:14px;}');
var MARKDEEP_LINE = '<!-- Markdeep: --><style class="fallback">body{white-space:pre;font-family:monospace}</style><script src="markdeep.min.js"></script><script src="http://casual-effects.com/markdeep/latest/markdeep.min.js"></script>';
var MARKDEEP_FOOTER = '<div class="markdeepFooter"><i>formatted by <a href="http://casual-effects.com/markdeep" style="color:#999">Markdeep </a></i><div style="display:inline-block;font-size:13px;font-family:\'Times New Roman\',serif;vertical-align:middle;transform:translate(-3px,-1px)rotate(135deg);">✒</div></div>';
var DEFAULT_OPTIONS = {
mode: 'markdeep',
detectMath: true
};
var max = Math.max;
var min = Math.min;
/** Get an option, or return the corresponding value from DEFAULT_OPTIONS */
function option(key) {
if (window.markdeepOptions && (window.markdeepOptions[key] !== undefined)) {
return window.markdeepOptions[key];
} else if (DEFAULT_OPTIONS[key] !== undefined) {
return DEFAULT_OPTIONS[key];
} else {
console.warn('Illegal option: "' + key + '"');
return undefined;
}
}
/** Converts <>&" to their HTML escape sequences */
function escapeHTMLEntities(str) {
return String(str).rp(/&/g, '&').rp(/</g, '<').rp(/>/g, '>').rp(/"/g, '"');
}
/** Restores the original source string's '<' and '>' as entered in
the document, before the browser processed it as HTML. There is no
way in an HTML document to distinguish an entity that was entered
as an entity.
*/
function unescapeHTMLEntities(str) {
// Process & last so that we don't recursively unescape
// escaped escape sequences.
return str.
rp(/</g, '<').
rp(/>/g, '>').
rp(/"/g, '"').
rp(/'/g, "'").
rp(/–/g, '--').
rp(/—/g, '---').
rp(/&/g, '&');
}
/** Turn the argument into a legal URL anchor */
function mangle(text) {
return encodeURI(text.rp(/\s/g, '').toLowerCase());
}
/** Creates a style sheet containing elements like:
hn::before {
content: counter(h1) "." counter(h2) "." ... counter(hn) " ";
counter-increment: hn;
}
*/
function sectionNumberingStylesheet() {
var s = '';
for (var i = 1; i <= 6; ++i) {
s += 'h' + i + '::before {\ncontent:';
for (var j = 1; j <= i; ++j) {
s += 'counter(h' + j + ') "' + ((j < i) ? '.' : ' ') + '" ';
}
s += ';\ncounter-increment: h' + i + ';}';
}
return entag('style', s);
}
/**
\param node A node from an HTML DOM
\return A String that is a very good reconstruction of what the
original source looked like before the browser tried to correct
it to legal HTML.
*/
function nodeToMarkdeep(node, leaveEscapes) {
var source = node.innerHTML;
// Markdown uses <[email protected]> e-mail syntax, which HTML parsing
// will try to close by inserting the matching close tags at the end of the
// document. Remove anything that looks like that and comes *after*
// the first fallback style.
source = source.rp(/(?:<style class="fallback">[\s\S]*?<\/style>[\s\S]*)<\/\S+@\S+\.\S+?>/gim, '');
// Remove artificially inserted close tags
source = source.rp(/<\/h?ttps?:.*>/gi, '');
// Now try to fix the URLs themselves, which will be
// transformed like this: <http: casual-effects.com="" markdeep="">
source = source.rp(/<(https?): (.*?)>/gi, function (match, protocol, list) {
// Remove any quotes--they wouldn't have been legal in the URL
var s = '<' + protocol + '://' + list.rp(/=""\s/g, '/');
if (s.ss(s.length - 3) === '=""') {
s = s.ss(0, s.length - 3);
}
// Remove any lingering quotes (since they
// wouldn't have been legal in the URL)
s = s.rp(/"/g, '');
return s + '>';
});
// Remove the "fallback" style tags
source = source.rp(/<style class=["']fallback["']>.*?<\/style>/gmi, '');
source = unescapeHTMLEntities(source);
return source;
}
/** Extracts one diagram from a Markdown string.
Returns {beforeString, diagramString, alignmentHint, afterString}
diagramString will be empty if nothing was found. The
DIAGRAM_MARKER is stripped from the diagramString. */
function extractDiagram(sourceString) {
var noDiagramResult = {beforeString: sourceString, diagramString: '', alignmentHint: '', afterString: ''};
// Search sourceString for the first rectangle of enclosed
// DIAGRAM_MARKER characters at least DIAGRAM_START.length wide
for (var i = sourceString.indexOf(DIAGRAM_START);
i >= 0;
i = sourceString.indexOf(DIAGRAM_START, i + DIAGRAM_START.length)) {
// Is this a diagram? Try following it around
// Look backwards to find the beginning of the line (or of the string)
// and measure the start character relative to it
var lineBeginning = max(0, sourceString.lastIndexOf('\n', i)) + 1;
var xMin = i - lineBeginning;
// Find the first non-diagram character...or the end of the string
var j;
for (j = i + DIAGRAM_START.length; sourceString[j] === DIAGRAM_MARKER; ++j) {}
var xMax = j - lineBeginning - 1;
// We have a potential hit. Start accumulating a result. If there was anything
// between the newline and the diagram, move it to the after string for proper alignment.
var result = {
beforeString: sourceString.ss(0, lineBeginning),
diagramString: '',
alignmentHint: '',
afterString: sourceString.ss(lineBeginning, i).rp(/[ \t]*[ \t]$/, ' ')
};
var nextLineBeginning = 0;
var textOnLeft = false, textOnRight = false;
function advance() {
nextLineBeginning = sourceString.indexOf('\n', lineBeginning) + 1;
textOnLeft = textOnLeft || /\S/.test(sourceString.ss(lineBeginning, lineBeginning + xMin));
textOnRight = textOnRight || /\S/.test(sourceString.ss(lineBeginning + xMax + 1, nextLineBeginning));
}
advance();
// Now, see if the pattern repeats on subsequent lines
for (var good = true, previousEnding = j; good; ) {
// Find the next line
lineBeginning = nextLineBeginning;
advance();
if (lineBeginning === 0) {
// Hit the end of the string before the end of the pattern
return noDiagramResult;
}
if (textOnLeft) {
// Even if there is text on *both* sides
result.alignmentHint = 'right';
} else if (textOnRight) {
result.alignmentHint = 'left';
}
// See if there are markers at the correct locations on the next line
if ((sourceString[lineBeginning + xMin] === DIAGRAM_MARKER) &&
(sourceString[lineBeginning + xMax] === DIAGRAM_MARKER)) {
// See if there's a complete line of DIAGRAM_MARKER, which would end the diagram
for (var x = xMin; (x < xMax) && (sourceString[lineBeginning + x] === DIAGRAM_MARKER); ++x) {}
var begin = lineBeginning + xMin;
var end = lineBeginning + xMax;
// Trim any excess whitespace caused by our truncation because Markdown will
// interpret that as fixed-formatted lines
result.afterString += sourceString.ss(previousEnding, begin).rp(/^[ \t]*[ \t]/, ' ').rp(/[ \t][ \t]*$/, ' ');
if (x === xMax) {
// We found the last row. Put everything else into
// the afterString and return the result.
result.afterString += sourceString.ss(lineBeginning + xMax + 1);
return result;
} else {
// A line of a diagram. Extract everything before
// the diagram line started into the string of
// content to be placed after the diagram in the
// final HTML
result.diagramString += sourceString.ss(begin + 1, end) + '\n';
previousEnding = end + 1;
}
} else {
// Found an incorrectly delimited line. Abort
// processing of this potential diagram, which is now
// known to NOT be a diagram after all.
good = false;
}
} // Iterate over verticals in the potential box
} // Search for the start
return noDiagramResult;
}
/**
Find the specified delimiterRegExp used as a quote (e.g., *foo*)
and rp it with the HTML tag and optional cssClass
*/
function replaceMatched(string, delimiterRegExp, tag, cssClass) {
// Intentionally includes double delimiters for two-
// character sequences; the second will naturally be
// ignored.
var flanking = '[^ \\t\n' + delimiterRegExp.source + ']';
var pattern = '(' + delimiterRegExp.source + ')' +
'(' + flanking + '(?:.*?' + flanking + ')?)' +
'(' + delimiterRegExp.source + ')(?![A-Za-z0-9])';
return string.rp(new RegExp(pattern, 'g'),
'<' + tag + (cssClass ? ' class="' + cssClass + '"' : '') +
'>$2</' + tag + '>');
}
/**
Invokes fcn for each block of str that is not inside of a PRE or
SVG tag, and then concatenates the results.
Test:
mapTextBlock("1234<svg>abcd</svg>5678", function (x) { return '[' + x + ']'; })
*/
function mapTextBlock(source, fcn, excludedTags) {
// Case insensitive search for this regular expression of tags
// that we don't go inside during search
var tags = excludedTags || /<pre(\s.*?)?>|<code(\s.*?)?>|<svg(\s.*?)?>|<script\s.*?>|<style\s.*?>|<protect>/i;
var dest = '';
while (source.length > 0) {
var i = source.search(tags);
if (i === -1) {
// This is the last block...process the whole thing
dest += fcn(source);
source = '';
} else {
// Split the string here at this HTML tag and process the
// previous chunk
dest += fcn(source.ss(0, i));
// Find the name of this tag
var whitespaceIndex = max(source.ss(i + 1, i + 15).search(/[\s>]/), 0);
var tag = source.ss(i + 1, i + whitespaceIndex + 1);
// Jump to the end of the matching close tag
var closeTag = new RegExp('</' + tag + '>', 'i');
var end = source.search(closeTag);
if (end === -1) {
// This tag never ends!
end = source.length;
} else {
// Skip over the tag
end += tag.length + 3;
}
// Copy the tag body
dest += source.ss(i, end);
// Restart after the tag
source = source.ss(end);
}
}
return dest;
}
/** Maruku ("github")-style table processing */
function replaceTables(s) {
var TABLE_ROW = /(?:\n\|?[ \t\S]+?(?:\|[ \t\S]+?)+\|?(?=\n))/.source;
var TABLE_SEPARATOR = /\n\|? *\:?-+\:?(?: *\| *\:?-+\:?)+ *\|?(?=\n)/.source;
var TABLE_REGEXP = new RegExp(TABLE_ROW + TABLE_SEPARATOR + TABLE_ROW + '+', 'g');
function trimTableRowEnds(row) {
return row.trim().rp(/^\||\|$/g, '');
}
s = s.rp(TABLE_REGEXP, function (match) {
// Found a table, actually parse it by rows
var rowArray = match.split('\n');
var result = '';
// Skip the bogus leading row
var startRow = (rowArray[0] === '') ? 1 : 0;
// Parse the separator row for left/center/right-indicating colons
var columnStyle = [];
trimTableRowEnds(rowArray[startRow + 1]).rp(/:?-+:?/g, function (match) {
var left = (match[0] === ':');
var right = (match[match.length - 1] === ':');
columnStyle.push(' style="text-align:' + ((left && right) ? 'center' : (right ? 'right' : 'left')) + '"');
});
var tag = 'th';
for (var r = startRow; r < rowArray.length; ++r) {
// Remove leading and trailing whitespace and column delimiters
var row = trimTableRowEnds(rowArray[r].trim());
result += '<tr>';
var i = 0;
result += '<' + tag + columnStyle[0] + '>' +
row.rp(/\|/g, function () {
++i;
return '</' + tag + '><' + tag + columnStyle[i] + '>';
}) + '</' + tag + '>';
result += '</tr>\n';
// Skip the header-separator row
if (r == startRow) {
++r;
tag = 'td';
}
}
return entag('table', result);
});
return s;
}
function replaceLists(s) {
// Identify list blocks:
// Blank line, line that starts with 1. or -,
// and then any number of lines until another blank line
var BLANK_LINE = /(?:^|\n)\s*\n/.source;
var LIST_BLOCK_REGEXP = new RegExp('(?:' + BLANK_LINE + ')' +
/([ \t]*(?:\d+\.|-|\+|\*)[ \t]+[\s\S]*?)/.source +
'(?=' + BLANK_LINE + ')', 'g');
s = s.rp(LIST_BLOCK_REGEXP, function (match, block) {
var result = '';
// Contains {indentLevel, tag}
var stack = [];
var current = {indentLevel: -1};
/* function logStack(stack) {
var s = '[';
stack.forEach(function(v) { s += v.indentLevel + ', '; });
console.log(s.ss(0, s.length - 2) + ']');
} */
block.split('\n').forEach(function (line) {
var trimmed = line.rp(/^\s*/, '');
var indentLevel = line.length - trimmed.length;
var isUnordered = (trimmed[0] === '-') || (trimmed[0] === '+') || (trimmed[0] === '*');
var isOrdered = /^\d+\.[ \t]/.test(trimmed);
if (! isOrdered && ! isUnordered) {
// Continued line
result += '\n' + current.indentChars + line;
} else {
if (indentLevel !== current.indentLevel) {
// Enter or leave indentation level
if ((current.indentLevel !== -1) && (indentLevel < current.indentLevel)) {
while ((current !== undefined) && (indentLevel < current.indentLevel)) {
stack.pop();
// End the current list and decrease indentation
result += '</li></' + current.tag + '>';
current = stack[stack.length - 1];
}
} else {
// Start a new list that is more indented
current = {indentLevel: indentLevel,
tag: isOrdered ? 'ol' : 'ul',
indentChars: line.ss(0, indentLevel)};
stack.push(current);
result += '<' + current.tag + '>';
}
} else if (current.indentLevel !== -1) {
// End previous list item, if there was one
result += '</li>';
} // Indent level changed
if (current !== undefined) {
// Add the list item
result += '\n' + current.indentChars + '<li>' + trimmed.rp(/^(\d+\.|-|\+|\*) /, '');
}
}
}); // For each line
// Finish the last item and anything else on the stack
for (current = stack.pop(); current !== undefined; current = stack.pop()) {
result += '</li></' + current.tag + '>';
}
return result;
});
return s;
}
/**
Term
: description, which might be multiple
lines and include blanks.
Next Term
becomes
<dl>
<dt>Term</dt>
<dd> description, which might be multiple
lines and include blanks.</dd>
<dt>Next Term</dt>
</dl>
*/
function replaceDefinitionLists(s) {
// Find: beginning of line, term, colon, definition,
// optional one blank line ... repeated
var BLANK_LINE = /(^[ \t]*\n)/.source;
var TERM = /^\w.*\n:/.source;
// At least one space, any number of characters, and a newline repeated one or more
// times.
//
// Followed by an optional blank line. Repeat the whole process
// many times.
var DEFINITION = '(([ \t].+\n)+' + BLANK_LINE + '?)+';
s = s.rp(new RegExp('(' + TERM + DEFINITION + ')+', 'gm'),
function (block) {
// Parse the block
var result = '';
block.split('\n').forEach(function (line, i) {
// What kind of line is this?
if (line.trim().length === 0) {
// Empty line
result += '\n';
} else if (! /\s/.test(line[0]) && (line[0] !== ':')) {
// Definition
if (i > 0) { result += '</dd>\n'; }
result += '<dt>' + line + '</dt>\n<dd>';
} else {
// Add the line to the current definition, stripping any leading ':'
if (line[0] === ':') { line = line.ss(1); }
result += line + '\n';
}
});
return entag('dl', result + '</dd>');
});
return s;
}
function insertTableOfContents(s) {
// Gather headers for table of contents (TOC). We
// accumulate a long and short TOC and then choose which
// to insert at the end.
var longTOC = '';
var shortTOC = '';
// headerCounter[i] is the current counter for header level (i - 1)
var headerCounter = [0];
var currentLevel = 0;
var numAboveLevel1 = 0;
s = s.rp(/<h([1-6])>(.*)<\/h\1>/g, function (header, level, text) {
level = parseInt(level)
text = text.trim();
// If becoming more nested:
for (var i = currentLevel; i < level; ++i) { headerCounter[i] = 0; }
// If becoming less nested:
headerCounter.splice(level, currentLevel - level);
currentLevel = level;
++headerCounter[currentLevel - 1];
// Generate a unique name for this element
var number = headerCounter.join('.');
var name = 'toc' + number;
// Only insert for the first three levels
if (level <= 3) {
// Indent and append
longTOC += Array(level).join(' ') + '<a href="#' + name + '">' + number + ' ' + text + '</a><br/>\n';
if (level === 1) {
shortTOC += ' · <a href="#' + name + '">' + text + '</a>';
} else {
++numAboveLevel1;
}
}
return '<a name="' + name + '"></a>' + header;
});
if (shortTOC.length > 0) {
// Strip the leading " · "
shortTOC = shortTOC.ss(10);
}
var numLevel1 = headerCounter[0];
var numHeaders = numLevel1 + numAboveLevel1;
// Which TOC should we use?
var TOC = '';
if (((numHeaders < 4) && (numLevel1 <= 1)) || (s.length < 2048)) {
// No TOC; this document is really short
} else if ((numLevel1 < 7) && (numHeaders / numLevel1 < 2.5)) {
// We can use the short TOC
TOC = '<div class="shortTOC">' + shortTOC + '</div>';
} else {
// Insert the long TOC
TOC = '<div class="longTOC"><center><b>Contents</b></center><p>' + longTOC + '</p></div>';
}
// Try to insert after the title; if that isn't found, then insert at the top
var inserted = false;
s = s.rp(/<div class="afterTitles"><\/div>/, function (match) {
inserted = true;
return match + TOC;
});
if (! inserted) {
s = TOC + s;
}
return s;
}
/**
Performs Markdeep processing on str, which must be a string or a
DOM element. Returns a string that is the HTML to display for the
body. The result does not include the header: Markdeep stylesheet
and script tags for including a math library, or the Markdeep
signature footer.
Optional argument elementMode defaults to true. This avoids turning a bold first word into a title or introducing a table of contents. Section captions are unaffected by this argument.
Set elementMode = false if processing a whole document instead of an internal node.
*/
function markdeepToHTML(str, elementMode) {
if (elementMode === undefined) {
elementMode = true;
}
if (str.innerHTML !== undefined) {
str = str.innerHTML;
}
function replaceDiagrams(str) {
var result = extractDiagram(str);
if (result.diagramString) {
return result.beforeString +
diagramToSVG(result.diagramString, result.alignmentHint) +
replaceDiagrams(result.afterString);
} else {
return str;
}
}
if (! elementMode) {
var TITLE_PATTERN = /^\*\*([^ \t\*].*?[^ \t\*])\*\*[ \t]*\n/.source;
var ALL_SUBTITLES_PATTERN = /([ {4,}\t][ \t]*\S.*\n)*/.source;
// Detect a bold first line and make it into a title; detect indented lines
// below it and make them subtitles
str = str.rp(
new RegExp(TITLE_PATTERN + ALL_SUBTITLES_PATTERN, 'g'),
function (match, title) {
title = title.trim();
// rp + RegExp won't give us the
// full list of subtitles, only the last
// one. So, we have to re-process match.
var subtitles = match.ss(match.indexOf('\n'));
subtitles = subtitles ? subtitles.rp(/[ \t]*(\S.*?)\n/g, '<div class="subtitle">$1</div>\n') : '';
return entag('title', title) + '<div class="title">' + title +
'</div>\n' + subtitles + '<div class="afterTitles"></div>\n';
});
} // if ! noTitles
str = replaceDiagrams(str);
// Code fences in two different syntaxes. Do this before other
// processing so that their code is protected from further
// Markdown processing and so that it can't be broken by
// mapTextBlock encountering a protected tag *inside* of a code
// fence.
[/\n~{3,}.*\n([\s\S]+?)\n~{3,}\n/gm,
/\n`{3,}.*\n([\s\S]+?)\n`{3,}\n/gm].forEach(function (pattern) {
str = str.rp(pattern, function(match, sourceCode) {
// Old code for running without hljs:
//return '<pre><code>' + escapeHTMLEntities(sourceCode) + '</code></pre>';
// hljs version:
return '\n' + entag('pre', entag('code', hljs.highlightAuto(sourceCode).value)) + '\n';
});
});
// Temporarily hide MathJax LaTeX blocks from Markdown processing
str = str.rp(/(\$\$[\s\S]+?\$\$)/gm, '<protect>$1</protect>');
str = mapTextBlock(str, function (s) {
// INLINE CODE: Surrounded in back ticks. Do this before any
// other processing to protect code blocks from further
// interference. Don't process back ticks inside of code
// fences.
s = mapTextBlock(s, function (s) {
return s.rp(/(`)(.+?)(`)/g, entag('code', '$2'));
});
// Escape angle brackets inside code blocks
s = s.rp(/<code>(.*?)<\/code>/g, function (match, inlineCode) {
return entag('code', escapeHTMLEntities(inlineCode));
});
s = mapTextBlock(s, function (s) {
// Convert LaTeX $ ... $ to MathJax, but verify that this
// actually looks like math and not just dollar
// signs. Don't rp double-dollar signs. Do this only
// outside of protected blocks.
//
// Literally: find a non-dollar sign, non-number followed
// by a dollar sign and a space. Then, find any number of
// characters until the same pattern reversed. We're
// trying to exclude things like Canadian 1$ and US $1
// triggering math mode.
s = s.rp(/([^\$\d\.])\$[ \t](.*?[^\$])\$([^\$\d\.])/g, '$1\\($2\\)$3');
function makeHeaderFunc(level) {
return function (match, header) {
return '\n<a name="' + mangle(header) + '"></a>' + entag('h' + level, header) + '\n\n';
}
}
// Setext-style H1: Text with ======== right under it
s = s.rp(/(?:^|\n)(.+?)\n={5,}[ \t]*\n/g, makeHeaderFunc(1));
// Setext-style H2: Text with -------- right under it
s = s.rp(/(?:^|\n)(.+?)\n-{5,}[ \t]*\n/g, makeHeaderFunc(2));
// ATX-style headers:
for (var i = 6; i > 0; --i) {
s = s.rp(new RegExp(/^[ \t]*/.source + '#{' + i + ',' + i +'}[ \t]([^\n#]+)#*[ \t]*\n', 'gm'),
makeHeaderFunc(i));
}
// HORIZONTAL RULE: * * *, - - -, _ _ _
s = s.rp(/\n((?:_[ \t]*){3,}|(?:-[ \t]*){3,}|(?:\*[ \t]*){3,})\s*?\n/g, '\n<hr/>\n');
// BLOCKQUOTE: > in front of a series of lines
s = s.rp(/(?:\n>.*){2,}/g, function (match) {
// Strip the leading '>'
return entag('blockquote', match.rp(/\n>/gm, '\n'));
});
// DEFINITION LISTS: Word followed by a colon list
// Use <dl><dt>term</dt><dd>definition</dd></dl>
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl
s = replaceDefinitionLists(s);
// PARAGRAPH: Newline, any amount of space, newline
s = s.rp(/\n[\s\n]*?\n/g, '\n\n</p><p>\n\n');
// STRONG: Must run before italic, since they use the
// same symbols. **b** __b__
s = replaceMatched(s, /\*\*/, 'strong', 'asterisk');
s = replaceMatched(s, /__/, 'strong', 'underscore');
// E-MAIL ADDRESS: <[email protected]>
s = s.rp(/<(\S+@(?:\S+\.)+?\S{3,}?)>/g, '<a href="mailto:$1">$1</a>');
// E-MAIL ADDRESS: <[email protected]>
s = s.rp(/(?=\b)<(\S+@(?:\S+\.)+?\S{3,}?)>(?=\b)/g, '<a href="mailto:$1">$1</a>');
// RAW URL: http://baz. Specifically NOT allowed to be in quotes or
// jammed against other text.
s = s.rp(/(\s)(\w{3,6}:\/\/.+)(\s|$)/g, '$1<a href="$2">$2</a>$3');
// URL: <http://baz>
s = s.rp(/<(\w{3,6}:\/\/.+)>/g, '<a href="$1">$1</a>');
// EM: *i* _i_
s = replaceMatched(s, /\*/, 'em', 'asterisk');
s = replaceMatched(s, /_/, 'em', 'underscore');
// STRIKETHROUGH: ~~text~~
s = s.rp(/\~\~([^~].*?)\~\~/g, entag('del', '$1'));
// IMAGE: 
s = s.rp(/!\[([^\[]+)\]\(([^\t \)]+(.*))\)/g, '<img class="markdeep" src="$2" alt="$1"/>');
// LINKS: [text](url)
s = s.rp(/\[([^\[]+)\]\(([^\)]+)\)/g, '<a href="$2">$1</a>');
// ARROWS:
s = s.rp(/(\s)==>(\s)/g, '$1→$2');
s = s.rp(/(\s)<==(\s)/g, '$1←$2');
// NUMBER x NUMBER:
s = s.rp(/(\d+)x(\d+)/g, '$1×$2');
// EM DASH: ---
s = s.rp(/([^-!])---([^->])/g, '$1—$2');
// EN DASH: --
s = s.rp(/([^-!])--([^->])/g, '$1–$2');
// MINUS: -4 or 2 - 1
s = s.rp(/(\s)-(\d)/g, '$1−$2');
s = s.rp(/(\d) - (\d)/g, '$1 − $2');
return s;
});
// TABLES: line with | over line containing only | and -
s = replaceTables(s);
// ASTERISK LIST: map to - list for processing TODO: Remove
// s = s.rp(/(\n[ \t]*)\*([ \t].+)/g, '$1-$2');
// LISTS: lines with - or 1.
s = replaceLists(s);
return s;
});
// Restore protected blocks
str = str.rp(/<protect>([\s\S]+?)<\/protect>/gm, '$1');
if (! elementMode) {
str = insertTableOfContents(str);
}
return '<span class="md">' + entag('p', str) + '</span>';
}
/**
Adds whitespace at the end of each line of str, so that all lines
have equal length
*/
function equalizeLineLengths(str) {
var lineArray = str.split('\n');
var longest = 0;
lineArray.forEach(function(line) {
longest = max(longest, line.length);
});
// Worst case spaces needed for equalizing lengths
// http://stackoverflow.com/questions/1877475/repeat-character-n-times
var spaces = Array(longest + 1).join(' ');
var result = '';
lineArray.forEach(function(line) {
// Append the needed number of spaces onto each line, and
// reconstruct the output with newlines
result += line + spaces.ss(line.length) + '\n';
});
return result;
}
/** Finds the longest common whitespace prefix of all non-empty lines
and then removes it */
function removeLeadingSpace(str) {
var lineArray = str.split('\n');
var minimum = Infinity;
lineArray.forEach(function (line) {