pantsdown 2.2.7 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -2
- package/package.json +10 -8
- package/src/css/styles.css +79 -0
- package/src/javascript.ts +356 -1
- package/src/lexer.ts +42 -51
- package/src/pantsdown.ts +22 -3
- package/src/parser.ts +51 -125
- package/src/renderer.ts +139 -62
- package/src/rules/block.ts +41 -34
- package/src/rules/inline.ts +93 -29
- package/src/rules/other.ts +79 -0
- package/src/rules/utils.ts +2 -2
- package/src/text-renderer.ts +57 -0
- package/src/tokenizer.ts +347 -165
- package/src/types.ts +45 -1
- package/src/utils.ts +65 -34
package/src/renderer.ts
CHANGED
|
@@ -2,7 +2,9 @@ import GithubSlugger from "github-slugger";
|
|
|
2
2
|
import hljs from "highlight.js";
|
|
3
3
|
import { renderToString } from "katex";
|
|
4
4
|
import { type Pantsdown } from "./pantsdown.ts";
|
|
5
|
+
import { type Parser } from "./parser.ts";
|
|
5
6
|
import { inline } from "./rules/inline.ts";
|
|
7
|
+
import { other } from "./rules/other.ts";
|
|
6
8
|
import { type HTMLAttrs, type SourceMap, type Tokens } from "./types.ts";
|
|
7
9
|
import {
|
|
8
10
|
addGithubImageStyles,
|
|
@@ -11,6 +13,7 @@ import {
|
|
|
11
13
|
fixHtmlLocalImageHref,
|
|
12
14
|
fixLocalImageHref,
|
|
13
15
|
getHtmlElementText,
|
|
16
|
+
hashString,
|
|
14
17
|
injectHtmlAttributes,
|
|
15
18
|
} from "./utils.ts";
|
|
16
19
|
|
|
@@ -19,20 +22,33 @@ import {
|
|
|
19
22
|
*/
|
|
20
23
|
export class Renderer {
|
|
21
24
|
private pantsdown: Pantsdown;
|
|
25
|
+
parser!: Parser; // set by the parser
|
|
22
26
|
slugger = new GithubSlugger();
|
|
27
|
+
mermaidCounts = new Map<string, number>();
|
|
23
28
|
|
|
24
29
|
constructor(pantsdown: Pantsdown) {
|
|
25
30
|
this.pantsdown = pantsdown;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
space(_token: Tokens["Space"]): string {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
code({ text, lang: infostring, sourceMap }: Tokens["Code"]): string {
|
|
38
|
+
const lang = other.notSpaceStart.exec(infostring ?? "")?.[0];
|
|
39
|
+
let code = text.replace(other.endingNewline, "");
|
|
31
40
|
|
|
32
41
|
const attrs: HTMLAttrs = [];
|
|
33
42
|
|
|
34
43
|
if (lang === "mermaid") {
|
|
35
44
|
attrs.push(["class", "mermaid-container mermaid"]);
|
|
45
|
+
// stable identity across re-renders — the pan/zoom script keys
|
|
46
|
+
// stashed transforms on it; the occurrence index disambiguates
|
|
47
|
+
// byte-identical diagrams in the same document
|
|
48
|
+
const hash = hashString(code);
|
|
49
|
+
const count = this.mermaidCounts.get(hash) ?? 0;
|
|
50
|
+
this.mermaidCounts.set(hash, count + 1);
|
|
51
|
+
attrs.push(["data-mermaid-key", `${hash}-${count}`]);
|
|
36
52
|
// escape so diagram source can't be parsed as HTML;
|
|
37
53
|
// mermaid reads textContent, which undoes the escaping
|
|
38
54
|
const result = `<pre style="position: relative;">` + escape(code, true) + "\n" + `</pre>`;
|
|
@@ -40,7 +56,7 @@ export class Renderer {
|
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
if (lang === "math") {
|
|
43
|
-
return this.latexBlock(code, sourceMap);
|
|
59
|
+
return this.latexBlock({ text: code, sourceMap });
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
const language = lang && hljs.getLanguage(lang) ? lang : "plaintext";
|
|
@@ -51,51 +67,70 @@ export class Renderer {
|
|
|
51
67
|
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
52
68
|
}
|
|
53
69
|
|
|
54
|
-
alert(
|
|
70
|
+
alert(token: Tokens["Alert"]): string {
|
|
71
|
+
const body = this.parser.parse(token.tokens);
|
|
55
72
|
const result = `<div><span>${token.icon + token.variant}</span>${body}</div>\n`;
|
|
56
73
|
return injectHtmlAttributes(result, [
|
|
57
74
|
["class", `markdown-alert markdown-alert-${token.variant.toLowerCase()}`],
|
|
58
75
|
]);
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
blockquote(
|
|
62
|
-
|
|
78
|
+
blockquote({ tokens }: Tokens["Blockquote"]): string {
|
|
79
|
+
const body = this.parser.parse(tokens);
|
|
80
|
+
return `<blockquote>\n${body}</blockquote>\n`;
|
|
63
81
|
}
|
|
64
82
|
|
|
65
|
-
html(
|
|
66
|
-
let result = fixHtmlLocalImageHref(
|
|
83
|
+
html(token: Tokens["HTML"] | Tokens["Tag"]): string {
|
|
84
|
+
let result = fixHtmlLocalImageHref(token.text, this.pantsdown.config);
|
|
67
85
|
result = addGithubImageStyles(result);
|
|
68
86
|
|
|
69
87
|
const attrs: HTMLAttrs = [];
|
|
70
88
|
|
|
71
89
|
if (this.pantsdown.config.renderer.detailsTagDefaultOpen) {
|
|
72
|
-
const tag = inline.tag.exec(
|
|
90
|
+
const tag = inline.tag.exec(token.text);
|
|
73
91
|
if (tag?.[0] === "<details>") {
|
|
74
92
|
attrs.push(["open", ""]);
|
|
75
93
|
}
|
|
76
94
|
}
|
|
77
95
|
|
|
78
|
-
return injectHtmlAttributes(
|
|
96
|
+
return injectHtmlAttributes(
|
|
97
|
+
result,
|
|
98
|
+
attrs,
|
|
99
|
+
"sourceMap" in token ? token.sourceMap : undefined,
|
|
100
|
+
);
|
|
79
101
|
}
|
|
80
102
|
|
|
81
|
-
heading(
|
|
103
|
+
heading({ tokens, depth, sourceMap }: Tokens["Heading"]): string {
|
|
104
|
+
const text = this.parser.parseInline(tokens);
|
|
82
105
|
const elementText = getHtmlElementText(text);
|
|
83
106
|
const slug = this.slugger.slug(elementText);
|
|
84
|
-
let result = `<h${
|
|
107
|
+
let result = `<h${depth}>`;
|
|
85
108
|
// span with negative top to add some offset when scrolling to #slug
|
|
86
109
|
result += `<span style="position: absolute; top: -50px;" id="${slug}"></span>`;
|
|
87
110
|
result += `${text}<a class="anchor octicon-link" href="#${slug}"></a>`;
|
|
88
|
-
result += `</h${
|
|
111
|
+
result += `</h${depth}>\n`;
|
|
89
112
|
return injectHtmlAttributes(result, [["style", "position: relative;"]], sourceMap);
|
|
90
113
|
}
|
|
91
114
|
|
|
92
|
-
hr(sourceMap:
|
|
115
|
+
hr({ sourceMap }: Tokens["Hr"]): string {
|
|
93
116
|
return injectHtmlAttributes(`<hr>\n`, [], sourceMap);
|
|
94
117
|
}
|
|
95
118
|
|
|
96
|
-
list(
|
|
119
|
+
list(token: Tokens["List"]): string {
|
|
120
|
+
const ordered = token.ordered;
|
|
121
|
+
const start = token.start;
|
|
122
|
+
|
|
123
|
+
let body = "";
|
|
124
|
+
let containsTaskList = false;
|
|
125
|
+
for (const item of token.items) {
|
|
126
|
+
if (item.task) containsTaskList = true;
|
|
127
|
+
body += this.listitem(item);
|
|
128
|
+
}
|
|
129
|
+
|
|
97
130
|
const type = ordered ? "ol" : "ul";
|
|
98
|
-
const
|
|
131
|
+
const listClasses: string[] = [];
|
|
132
|
+
if (containsTaskList) listClasses.push("contains-task-list");
|
|
133
|
+
const attrs: HTMLAttrs = [["class", listClasses.join(" ")]];
|
|
99
134
|
if (ordered && start && start !== 1) {
|
|
100
135
|
attrs.push(["start", String(start)]);
|
|
101
136
|
}
|
|
@@ -103,49 +138,75 @@ export class Renderer {
|
|
|
103
138
|
return injectHtmlAttributes(result, attrs);
|
|
104
139
|
}
|
|
105
140
|
|
|
106
|
-
listitem(
|
|
107
|
-
const attrs:
|
|
108
|
-
if (task) attrs.push(["class", "task-list-item"]);
|
|
141
|
+
listitem(item: Tokens["ListItem"]): string {
|
|
142
|
+
const attrs: HTMLAttrs = [];
|
|
143
|
+
if (item.task) attrs.push(["class", "task-list-item"]);
|
|
144
|
+
const text = this.parser.parse(item.tokens, false);
|
|
109
145
|
const result = `<li>${text}</li>\n`;
|
|
110
|
-
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
146
|
+
return injectHtmlAttributes(result, attrs, item.sourceMap);
|
|
111
147
|
}
|
|
112
148
|
|
|
113
|
-
checkbox(checked
|
|
149
|
+
checkbox({ checked }: Tokens["Checkbox"]): string {
|
|
114
150
|
const result = "<input>";
|
|
115
151
|
const attrs: HTMLAttrs = [
|
|
116
152
|
["disabled", ""],
|
|
117
153
|
["type", "checkbox"],
|
|
118
|
-
["class",
|
|
154
|
+
["class", "task-list-item-checkbox"],
|
|
119
155
|
];
|
|
120
156
|
if (checked) attrs.push(["checked", ""]);
|
|
121
|
-
return injectHtmlAttributes(result, attrs);
|
|
157
|
+
return injectHtmlAttributes(result, attrs) + " ";
|
|
122
158
|
}
|
|
123
159
|
|
|
124
|
-
paragraph(
|
|
125
|
-
const result = `<p>${
|
|
160
|
+
paragraph({ tokens, sourceMap }: Tokens["Paragraph"]): string {
|
|
161
|
+
const result = `<p>${this.parser.parseInline(tokens)}</p>\n`;
|
|
126
162
|
return injectHtmlAttributes(result, [], sourceMap);
|
|
127
163
|
}
|
|
128
164
|
|
|
129
|
-
table(
|
|
165
|
+
table(token: Tokens["Table"]): string {
|
|
166
|
+
const sourceMapLineStart = token.sourceMap?.[0];
|
|
167
|
+
|
|
168
|
+
let header = "";
|
|
169
|
+
let cell = "";
|
|
170
|
+
for (const headerCell of token.header) {
|
|
171
|
+
cell += this.tablecell(headerCell);
|
|
172
|
+
}
|
|
173
|
+
header += this.tablerow({ text: cell, sourceMapStart: sourceMapLineStart });
|
|
174
|
+
|
|
175
|
+
let body = "";
|
|
176
|
+
for (let j = 0, rowsLen = token.rows.length; j < rowsLen; j++) {
|
|
177
|
+
const row = token.rows[j]!;
|
|
178
|
+
|
|
179
|
+
cell = "";
|
|
180
|
+
for (const rowCell of row) {
|
|
181
|
+
cell += this.tablecell(rowCell);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
body += this.tablerow({
|
|
185
|
+
text: cell,
|
|
186
|
+
sourceMapStart: sourceMapLineStart ? sourceMapLineStart + 2 + j : undefined,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
130
189
|
if (body) body = `<tbody>${body}</tbody>`;
|
|
190
|
+
|
|
131
191
|
return "<table>\n" + "<thead>\n" + header + "</thead>\n" + body + "</table>\n";
|
|
132
192
|
}
|
|
133
193
|
|
|
134
|
-
tablerow(
|
|
194
|
+
tablerow({
|
|
195
|
+
text,
|
|
196
|
+
sourceMapStart,
|
|
197
|
+
}: {
|
|
198
|
+
text: string;
|
|
199
|
+
sourceMapStart?: number | undefined;
|
|
200
|
+
}): string {
|
|
135
201
|
const sourceMap: SourceMap = sourceMapStart ? [sourceMapStart, sourceMapStart] : undefined;
|
|
136
|
-
return injectHtmlAttributes(`<tr>\n${
|
|
202
|
+
return injectHtmlAttributes(`<tr>\n${text}</tr>\n`, [], sourceMap);
|
|
137
203
|
}
|
|
138
204
|
|
|
139
|
-
tablecell(
|
|
140
|
-
content
|
|
141
|
-
|
|
142
|
-
header: boolean;
|
|
143
|
-
align: "center" | "left" | "right" | null;
|
|
144
|
-
},
|
|
145
|
-
): string {
|
|
146
|
-
const type = flags.header ? "th" : "td";
|
|
205
|
+
tablecell(token: Tokens["TableCell"]): string {
|
|
206
|
+
const content = this.parser.parseInline(token.tokens);
|
|
207
|
+
const type = token.header ? "th" : "td";
|
|
147
208
|
const attrs: HTMLAttrs = [];
|
|
148
|
-
if (
|
|
209
|
+
if (token.align) attrs.push(["align", token.align]);
|
|
149
210
|
const result = `<${type}>` + content + `</${type}>\n`;
|
|
150
211
|
return injectHtmlAttributes(result, attrs);
|
|
151
212
|
}
|
|
@@ -153,46 +214,48 @@ export class Renderer {
|
|
|
153
214
|
/**
|
|
154
215
|
* span level renderer
|
|
155
216
|
*/
|
|
156
|
-
strong(
|
|
157
|
-
return `<strong>${
|
|
217
|
+
strong({ tokens }: Tokens["Strong"]): string {
|
|
218
|
+
return `<strong>${this.parser.parseInline(tokens)}</strong>`;
|
|
158
219
|
}
|
|
159
220
|
|
|
160
|
-
em(
|
|
161
|
-
return `<em>${
|
|
221
|
+
em({ tokens }: Tokens["Em"]): string {
|
|
222
|
+
return `<em>${this.parser.parseInline(tokens)}</em>`;
|
|
162
223
|
}
|
|
163
224
|
|
|
164
|
-
codespan(text:
|
|
165
|
-
return `<code>${text}</code>`;
|
|
225
|
+
codespan({ text }: Tokens["Codespan"]): string {
|
|
226
|
+
return `<code>${escape(text, true)}</code>`;
|
|
166
227
|
}
|
|
167
228
|
|
|
168
|
-
br(): string {
|
|
229
|
+
br(_token: Tokens["Br"]): string {
|
|
169
230
|
return "<br>";
|
|
170
231
|
}
|
|
171
232
|
|
|
172
|
-
del(
|
|
173
|
-
return `<del>${
|
|
233
|
+
del({ tokens }: Tokens["Del"]): string {
|
|
234
|
+
return `<del>${this.parser.parseInline(tokens)}</del>`;
|
|
174
235
|
}
|
|
175
236
|
|
|
176
|
-
link(href
|
|
237
|
+
link({ href, title, tokens }: Tokens["Link"]): string {
|
|
238
|
+
const text = this.parser.parseInline(tokens);
|
|
177
239
|
const cleanHref = cleanUrl(href);
|
|
178
240
|
if (cleanHref === null) {
|
|
179
241
|
return text;
|
|
180
242
|
}
|
|
181
243
|
const attrs: HTMLAttrs = [["href", cleanHref]];
|
|
182
|
-
if (title) attrs.push(["title", title]);
|
|
244
|
+
if (title) attrs.push(["title", escape(title)]);
|
|
183
245
|
return injectHtmlAttributes(`<a>${text}</a>`, attrs);
|
|
184
246
|
}
|
|
185
247
|
|
|
186
|
-
image(href
|
|
248
|
+
image({ href, title, tokens }: Tokens["Image"]): string {
|
|
249
|
+
const text = this.parser.parseInline(tokens, this.parser.textRenderer);
|
|
187
250
|
const cleanHref = cleanUrl(href);
|
|
188
251
|
if (cleanHref === null) {
|
|
189
|
-
return text;
|
|
252
|
+
return escape(text);
|
|
190
253
|
}
|
|
191
254
|
const attrs: HTMLAttrs = [
|
|
192
255
|
["src", fixLocalImageHref(cleanHref, this.pantsdown.config)],
|
|
193
|
-
["alt", text],
|
|
256
|
+
["alt", escape(text)],
|
|
194
257
|
];
|
|
195
|
-
if (title) attrs.push(["title", title]);
|
|
258
|
+
if (title) attrs.push(["title", escape(title)]);
|
|
196
259
|
return injectHtmlAttributes("<img>", attrs);
|
|
197
260
|
}
|
|
198
261
|
|
|
@@ -204,9 +267,19 @@ export class Renderer {
|
|
|
204
267
|
}" data-footnote-ref aria-describedby="footnote-label">${token.label}</a></sup>`;
|
|
205
268
|
}
|
|
206
269
|
|
|
207
|
-
footnotes(token: Tokens["Footnotes"]
|
|
270
|
+
footnotes(token: Tokens["Footnotes"]) {
|
|
208
271
|
if (!token.items.length) return "";
|
|
209
272
|
|
|
273
|
+
const body = token.items.reduce((acc, { label, content, sourceMap }) => {
|
|
274
|
+
let footnoteItem = `<li id="footnote-${encodeURIComponent(label)}">\n`;
|
|
275
|
+
footnoteItem += this.parser.parse(content);
|
|
276
|
+
footnoteItem += "</li>\n";
|
|
277
|
+
|
|
278
|
+
footnoteItem = injectHtmlAttributes(footnoteItem, [], sourceMap);
|
|
279
|
+
|
|
280
|
+
return acc + footnoteItem;
|
|
281
|
+
}, "");
|
|
282
|
+
|
|
210
283
|
let footnotesHTML = '<section class="footnotes" data-footnotes>\n';
|
|
211
284
|
footnotesHTML += `<ol>\n${body}\n</ol>\n`;
|
|
212
285
|
footnotesHTML += "</section>\n";
|
|
@@ -214,13 +287,17 @@ export class Renderer {
|
|
|
214
287
|
return footnotesHTML;
|
|
215
288
|
}
|
|
216
289
|
|
|
217
|
-
text(
|
|
218
|
-
return
|
|
290
|
+
text(token: Tokens["Text"] | Tokens["Escape"] | Tokens["Tag"]): string {
|
|
291
|
+
return "tokens" in token
|
|
292
|
+
? this.parser.parseInline(token.tokens)
|
|
293
|
+
: "escaped" in token && token.escaped
|
|
294
|
+
? token.text
|
|
295
|
+
: escape(token.text);
|
|
219
296
|
}
|
|
220
297
|
|
|
221
|
-
latexBlock(
|
|
298
|
+
latexBlock({ text, sourceMap }: Pick<Tokens["LatexBlock"], "text" | "sourceMap">): string {
|
|
222
299
|
try {
|
|
223
|
-
const rendered = renderToString(
|
|
300
|
+
const rendered = renderToString(text, {
|
|
224
301
|
displayMode: true,
|
|
225
302
|
throwOnError: false,
|
|
226
303
|
output: "html",
|
|
@@ -229,16 +306,16 @@ export class Renderer {
|
|
|
229
306
|
return injectHtmlAttributes(`<div class="katex-block">${rendered}</div>\n`, [], sourceMap);
|
|
230
307
|
} catch {
|
|
231
308
|
return injectHtmlAttributes(
|
|
232
|
-
`<div class="katex-block katex-error"><code>${escape(
|
|
309
|
+
`<div class="katex-block katex-error"><code>${escape(text)}</code></div>\n`,
|
|
233
310
|
[],
|
|
234
311
|
sourceMap,
|
|
235
312
|
);
|
|
236
313
|
}
|
|
237
314
|
}
|
|
238
315
|
|
|
239
|
-
latexInline(
|
|
316
|
+
latexInline({ text }: Tokens["LatexInline"]): string {
|
|
240
317
|
try {
|
|
241
|
-
const rendered = renderToString(
|
|
318
|
+
const rendered = renderToString(text, {
|
|
242
319
|
displayMode: false,
|
|
243
320
|
throwOnError: false,
|
|
244
321
|
output: "html",
|
|
@@ -246,7 +323,7 @@ export class Renderer {
|
|
|
246
323
|
});
|
|
247
324
|
return `<span class="katex-inline">${rendered}</span>`;
|
|
248
325
|
} catch {
|
|
249
|
-
return `<span class="katex-inline katex-error"><code>${escape(
|
|
326
|
+
return `<span class="katex-inline katex-error"><code>${escape(text)}</code></span>`;
|
|
250
327
|
}
|
|
251
328
|
}
|
|
252
329
|
}
|
package/src/rules/block.ts
CHANGED
|
@@ -19,7 +19,7 @@ type BlockRuleNames =
|
|
|
19
19
|
| "paragraph"
|
|
20
20
|
| "latexBlock";
|
|
21
21
|
|
|
22
|
-
export const label = /(?!\s*\])(
|
|
22
|
+
export const label = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
|
|
23
23
|
|
|
24
24
|
const tag =
|
|
25
25
|
"address|article|aside|base|basefont|blockquote|body|caption" +
|
|
@@ -32,19 +32,19 @@ const tag =
|
|
|
32
32
|
const title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
|
|
33
33
|
|
|
34
34
|
const block_def = edit(
|
|
35
|
-
/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/,
|
|
35
|
+
/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/,
|
|
36
36
|
)
|
|
37
37
|
.replace("label", label)
|
|
38
38
|
.replace("title", title)
|
|
39
39
|
.getRegex();
|
|
40
40
|
|
|
41
|
-
const block_bullet = /(?:[*+-]|\d{1,9}[.)])/;
|
|
41
|
+
const block_bullet = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
|
|
42
42
|
|
|
43
43
|
const block_listItemStart = edit(/^( *)(bull) */)
|
|
44
44
|
.replace("bull", block_bullet)
|
|
45
45
|
.getRegex();
|
|
46
46
|
|
|
47
|
-
const block_list = edit(/^(
|
|
47
|
+
const block_list = edit(/^(bull)([ \t][^\n]*?)?(?:\n|$)/)
|
|
48
48
|
.replace(/bull/g, block_bullet)
|
|
49
49
|
.replace("hr", "\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))")
|
|
50
50
|
.replace("def", "\\n+(?=" + block_def.source + ")")
|
|
@@ -54,14 +54,14 @@ export const comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
|
54
54
|
|
|
55
55
|
const block_html = edit(
|
|
56
56
|
"^ {0,3}(?:" + // optional indentation
|
|
57
|
-
"<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n
|
|
57
|
+
"<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n*|$)" + // (1)
|
|
58
58
|
"|comment[^\\n]*(\\n+|$)" + // (2)
|
|
59
|
-
"|<\\?[\\s\\S]*?(
|
|
60
|
-
"|<![A-Z][\\s\\S]*?(
|
|
61
|
-
"|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]
|
|
62
|
-
"|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)" + // (6)
|
|
63
|
-
"|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)" + // (7) open tag
|
|
64
|
-
"|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)" + // (7) closing tag
|
|
59
|
+
"|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)" + // (3)
|
|
60
|
+
"|<![A-Z][\\s\\S]*?(?:>[^\\n]*\\n*|$)" + // (4)
|
|
61
|
+
"|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>[^\\n]*\\n*|$)" + // (5)
|
|
62
|
+
"|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \\t]*)+\\n|$)" + // (6)
|
|
63
|
+
"|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \\t]*)+\\n|$)" + // (7) open tag
|
|
64
|
+
"|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \\t]*)+\\n|$)" + // (7) closing tag
|
|
65
65
|
")",
|
|
66
66
|
"i",
|
|
67
67
|
)
|
|
@@ -70,16 +70,17 @@ const block_html = edit(
|
|
|
70
70
|
.replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
|
|
71
71
|
.getRegex();
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// upstream's lheadingGfm variant (we are GFM-only; the commonmark variant drops |table)
|
|
74
74
|
const block_lheading = edit(
|
|
75
|
-
/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
|
|
75
|
+
/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
|
|
76
76
|
)
|
|
77
77
|
.replace(/bull/g, block_bullet) // lists can interrupt
|
|
78
|
-
.replace(/blockCode/g, / {4}/) // indented code blocks can interrupt
|
|
78
|
+
.replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code blocks can interrupt
|
|
79
79
|
.replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt
|
|
80
80
|
.replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt
|
|
81
|
-
.replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt
|
|
81
|
+
.replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/) // ATX heading can interrupt
|
|
82
82
|
.replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt
|
|
83
|
+
.replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/) // table can interrupt
|
|
83
84
|
.getRegex();
|
|
84
85
|
|
|
85
86
|
const block_hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
|
|
@@ -92,34 +93,40 @@ const block_table = edit(
|
|
|
92
93
|
.replace("hr", block_hr)
|
|
93
94
|
.replace("heading", " {0,3}#{1,6}(?:\\s|$)")
|
|
94
95
|
.replace("blockquote", " {0,3}>")
|
|
95
|
-
.replace("code", " {4}[^\\n]")
|
|
96
|
-
.replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)
|
|
97
|
-
.replace("list", " {0,3}(?:[*+-]|1[.)]) ") //
|
|
96
|
+
.replace("code", "(?: {4}| {0,3}\\t)[^\\n]")
|
|
97
|
+
.replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~~~)[^\\n]*\\n")
|
|
98
|
+
.replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]") // any bullet ends the table rows
|
|
98
99
|
.replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)")
|
|
99
100
|
.replace("tag", tag) // tables can be interrupted by type (6) html blocks
|
|
100
101
|
.getRegex();
|
|
101
102
|
|
|
102
|
-
const
|
|
103
|
-
/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
const createParagraph = (listInterrupt: string) =>
|
|
104
|
+
edit(/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/)
|
|
105
|
+
.replace("hr", block_hr)
|
|
106
|
+
.replace("heading", " {0,3}#{1,6}(?:\\s|$)")
|
|
107
|
+
.replace("|lheading", "") // setext headings don't interrupt commonmark paragraphs
|
|
108
|
+
.replace("table", block_table) // interrupt paragraphs with table
|
|
109
|
+
.replace("blockquote", " {0,3}>")
|
|
110
|
+
.replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~~~)[^\\n]*\\n")
|
|
111
|
+
.replace("list", listInterrupt)
|
|
112
|
+
.replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)")
|
|
113
|
+
.replace("tag", tag) // pars can be interrupted by type (6) html blocks
|
|
114
|
+
.getRegex();
|
|
115
|
+
|
|
116
|
+
// only non-empty lists starting from 1 can interrupt paragraphs
|
|
117
|
+
const block_paragraph = createParagraph(" {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]");
|
|
118
|
+
// inside a blockquote a bare list marker (any number) starts a sibling list,
|
|
119
|
+
// so it must not be lazily continued as paragraph text (unlike a top level
|
|
120
|
+
// paragraph, where an empty list cannot interrupt)
|
|
121
|
+
const block_blockquoteParagraph = createParagraph(" {0,3}(?:[*+-]|\\d{1,9}[.)])(?:[ \\t]|\\n|$)");
|
|
115
122
|
|
|
116
123
|
const block_blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/)
|
|
117
|
-
.replace("paragraph",
|
|
124
|
+
.replace("paragraph", block_blockquoteParagraph)
|
|
118
125
|
.getRegex();
|
|
119
126
|
|
|
120
127
|
export const block: Record<BlockRuleNames, RegExp> = {
|
|
121
|
-
newline: /^(?: *(?:\n|$))+/,
|
|
122
|
-
code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,
|
|
128
|
+
newline: /^(?:[ \t]*(?:\n|$))+/,
|
|
129
|
+
code: /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,
|
|
123
130
|
fences:
|
|
124
131
|
/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,
|
|
125
132
|
hr: block_hr,
|