pantsdown 2.2.6 → 2.2.8
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 +2 -0
- package/package.json +20 -18
- package/src/lexer.ts +47 -51
- package/src/parser.ts +51 -125
- package/src/renderer.ts +133 -63
- 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 +352 -175
- package/src/types.ts +12 -2
- package/src/utils.ts +62 -40
- package/.oxfmtrc.json +0 -17
- package/CLAUDE.md +0 -73
- package/bun.lock +0 -1504
- package/eslint.config.ts +0 -46
- package/tsconfig.json +0 -32
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,
|
|
@@ -19,26 +21,33 @@ import {
|
|
|
19
21
|
*/
|
|
20
22
|
export class Renderer {
|
|
21
23
|
private pantsdown: Pantsdown;
|
|
24
|
+
parser!: Parser; // set by the parser
|
|
22
25
|
slugger = new GithubSlugger();
|
|
23
26
|
|
|
24
27
|
constructor(pantsdown: Pantsdown) {
|
|
25
28
|
this.pantsdown = pantsdown;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
space(_token: Tokens["Space"]): string {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
code({ text, lang: infostring, sourceMap }: Tokens["Code"]): string {
|
|
36
|
+
const lang = other.notSpaceStart.exec(infostring ?? "")?.[0];
|
|
37
|
+
let code = text.replace(other.endingNewline, "");
|
|
31
38
|
|
|
32
39
|
const attrs: HTMLAttrs = [];
|
|
33
40
|
|
|
34
41
|
if (lang === "mermaid") {
|
|
35
42
|
attrs.push(["class", "mermaid-container mermaid"]);
|
|
36
|
-
|
|
43
|
+
// escape so diagram source can't be parsed as HTML;
|
|
44
|
+
// mermaid reads textContent, which undoes the escaping
|
|
45
|
+
const result = `<pre style="position: relative;">` + escape(code, true) + "\n" + `</pre>`;
|
|
37
46
|
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
if (lang === "math") {
|
|
41
|
-
return this.latexBlock(code, sourceMap);
|
|
50
|
+
return this.latexBlock({ text: code, sourceMap });
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
const language = lang && hljs.getLanguage(lang) ? lang : "plaintext";
|
|
@@ -49,51 +58,70 @@ export class Renderer {
|
|
|
49
58
|
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
alert(
|
|
61
|
+
alert(token: Tokens["Alert"]): string {
|
|
62
|
+
const body = this.parser.parse(token.tokens);
|
|
53
63
|
const result = `<div><span>${token.icon + token.variant}</span>${body}</div>\n`;
|
|
54
64
|
return injectHtmlAttributes(result, [
|
|
55
65
|
["class", `markdown-alert markdown-alert-${token.variant.toLowerCase()}`],
|
|
56
66
|
]);
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
blockquote(
|
|
60
|
-
|
|
69
|
+
blockquote({ tokens }: Tokens["Blockquote"]): string {
|
|
70
|
+
const body = this.parser.parse(tokens);
|
|
71
|
+
return `<blockquote>\n${body}</blockquote>\n`;
|
|
61
72
|
}
|
|
62
73
|
|
|
63
|
-
html(
|
|
64
|
-
let result = fixHtmlLocalImageHref(
|
|
74
|
+
html(token: Tokens["HTML"] | Tokens["Tag"]): string {
|
|
75
|
+
let result = fixHtmlLocalImageHref(token.text, this.pantsdown.config);
|
|
65
76
|
result = addGithubImageStyles(result);
|
|
66
77
|
|
|
67
78
|
const attrs: HTMLAttrs = [];
|
|
68
79
|
|
|
69
80
|
if (this.pantsdown.config.renderer.detailsTagDefaultOpen) {
|
|
70
|
-
const tag = inline.tag.exec(
|
|
81
|
+
const tag = inline.tag.exec(token.text);
|
|
71
82
|
if (tag?.[0] === "<details>") {
|
|
72
83
|
attrs.push(["open", ""]);
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
|
|
76
|
-
return injectHtmlAttributes(
|
|
87
|
+
return injectHtmlAttributes(
|
|
88
|
+
result,
|
|
89
|
+
attrs,
|
|
90
|
+
"sourceMap" in token ? token.sourceMap : undefined,
|
|
91
|
+
);
|
|
77
92
|
}
|
|
78
93
|
|
|
79
|
-
heading(
|
|
94
|
+
heading({ tokens, depth, sourceMap }: Tokens["Heading"]): string {
|
|
95
|
+
const text = this.parser.parseInline(tokens);
|
|
80
96
|
const elementText = getHtmlElementText(text);
|
|
81
97
|
const slug = this.slugger.slug(elementText);
|
|
82
|
-
let result = `<h${
|
|
98
|
+
let result = `<h${depth}>`;
|
|
83
99
|
// span with negative top to add some offset when scrolling to #slug
|
|
84
100
|
result += `<span style="position: absolute; top: -50px;" id="${slug}"></span>`;
|
|
85
101
|
result += `${text}<a class="anchor octicon-link" href="#${slug}"></a>`;
|
|
86
|
-
result += `</h${
|
|
102
|
+
result += `</h${depth}>\n`;
|
|
87
103
|
return injectHtmlAttributes(result, [["style", "position: relative;"]], sourceMap);
|
|
88
104
|
}
|
|
89
105
|
|
|
90
|
-
hr(sourceMap:
|
|
106
|
+
hr({ sourceMap }: Tokens["Hr"]): string {
|
|
91
107
|
return injectHtmlAttributes(`<hr>\n`, [], sourceMap);
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
list(
|
|
110
|
+
list(token: Tokens["List"]): string {
|
|
111
|
+
const ordered = token.ordered;
|
|
112
|
+
const start = token.start;
|
|
113
|
+
|
|
114
|
+
let body = "";
|
|
115
|
+
let containsTaskList = false;
|
|
116
|
+
for (const item of token.items) {
|
|
117
|
+
if (item.task) containsTaskList = true;
|
|
118
|
+
body += this.listitem(item);
|
|
119
|
+
}
|
|
120
|
+
|
|
95
121
|
const type = ordered ? "ol" : "ul";
|
|
96
|
-
const
|
|
122
|
+
const listClasses: string[] = [];
|
|
123
|
+
if (containsTaskList) listClasses.push("contains-task-list");
|
|
124
|
+
const attrs: HTMLAttrs = [["class", listClasses.join(" ")]];
|
|
97
125
|
if (ordered && start && start !== 1) {
|
|
98
126
|
attrs.push(["start", String(start)]);
|
|
99
127
|
}
|
|
@@ -101,49 +129,75 @@ export class Renderer {
|
|
|
101
129
|
return injectHtmlAttributes(result, attrs);
|
|
102
130
|
}
|
|
103
131
|
|
|
104
|
-
listitem(
|
|
105
|
-
const attrs:
|
|
106
|
-
if (task) attrs.push(["class", "task-list-item"]);
|
|
132
|
+
listitem(item: Tokens["ListItem"]): string {
|
|
133
|
+
const attrs: HTMLAttrs = [];
|
|
134
|
+
if (item.task) attrs.push(["class", "task-list-item"]);
|
|
135
|
+
const text = this.parser.parse(item.tokens, false);
|
|
107
136
|
const result = `<li>${text}</li>\n`;
|
|
108
|
-
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
137
|
+
return injectHtmlAttributes(result, attrs, item.sourceMap);
|
|
109
138
|
}
|
|
110
139
|
|
|
111
|
-
checkbox(checked
|
|
140
|
+
checkbox({ checked }: Tokens["Checkbox"]): string {
|
|
112
141
|
const result = "<input>";
|
|
113
142
|
const attrs: HTMLAttrs = [
|
|
114
143
|
["disabled", ""],
|
|
115
144
|
["type", "checkbox"],
|
|
116
|
-
["class",
|
|
145
|
+
["class", "task-list-item-checkbox"],
|
|
117
146
|
];
|
|
118
147
|
if (checked) attrs.push(["checked", ""]);
|
|
119
|
-
return injectHtmlAttributes(result, attrs);
|
|
148
|
+
return injectHtmlAttributes(result, attrs) + " ";
|
|
120
149
|
}
|
|
121
150
|
|
|
122
|
-
paragraph(
|
|
123
|
-
const result = `<p>${
|
|
151
|
+
paragraph({ tokens, sourceMap }: Tokens["Paragraph"]): string {
|
|
152
|
+
const result = `<p>${this.parser.parseInline(tokens)}</p>\n`;
|
|
124
153
|
return injectHtmlAttributes(result, [], sourceMap);
|
|
125
154
|
}
|
|
126
155
|
|
|
127
|
-
table(
|
|
156
|
+
table(token: Tokens["Table"]): string {
|
|
157
|
+
const sourceMapLineStart = token.sourceMap?.[0];
|
|
158
|
+
|
|
159
|
+
let header = "";
|
|
160
|
+
let cell = "";
|
|
161
|
+
for (const headerCell of token.header) {
|
|
162
|
+
cell += this.tablecell(headerCell);
|
|
163
|
+
}
|
|
164
|
+
header += this.tablerow({ text: cell, sourceMapStart: sourceMapLineStart });
|
|
165
|
+
|
|
166
|
+
let body = "";
|
|
167
|
+
for (let j = 0, rowsLen = token.rows.length; j < rowsLen; j++) {
|
|
168
|
+
const row = token.rows[j]!;
|
|
169
|
+
|
|
170
|
+
cell = "";
|
|
171
|
+
for (const rowCell of row) {
|
|
172
|
+
cell += this.tablecell(rowCell);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
body += this.tablerow({
|
|
176
|
+
text: cell,
|
|
177
|
+
sourceMapStart: sourceMapLineStart ? sourceMapLineStart + 2 + j : undefined,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
128
180
|
if (body) body = `<tbody>${body}</tbody>`;
|
|
181
|
+
|
|
129
182
|
return "<table>\n" + "<thead>\n" + header + "</thead>\n" + body + "</table>\n";
|
|
130
183
|
}
|
|
131
184
|
|
|
132
|
-
tablerow(
|
|
185
|
+
tablerow({
|
|
186
|
+
text,
|
|
187
|
+
sourceMapStart,
|
|
188
|
+
}: {
|
|
189
|
+
text: string;
|
|
190
|
+
sourceMapStart?: number | undefined;
|
|
191
|
+
}): string {
|
|
133
192
|
const sourceMap: SourceMap = sourceMapStart ? [sourceMapStart, sourceMapStart] : undefined;
|
|
134
|
-
return injectHtmlAttributes(`<tr>\n${
|
|
193
|
+
return injectHtmlAttributes(`<tr>\n${text}</tr>\n`, [], sourceMap);
|
|
135
194
|
}
|
|
136
195
|
|
|
137
|
-
tablecell(
|
|
138
|
-
content
|
|
139
|
-
|
|
140
|
-
header: boolean;
|
|
141
|
-
align: "center" | "left" | "right" | null;
|
|
142
|
-
},
|
|
143
|
-
): string {
|
|
144
|
-
const type = flags.header ? "th" : "td";
|
|
196
|
+
tablecell(token: Tokens["TableCell"]): string {
|
|
197
|
+
const content = this.parser.parseInline(token.tokens);
|
|
198
|
+
const type = token.header ? "th" : "td";
|
|
145
199
|
const attrs: HTMLAttrs = [];
|
|
146
|
-
if (
|
|
200
|
+
if (token.align) attrs.push(["align", token.align]);
|
|
147
201
|
const result = `<${type}>` + content + `</${type}>\n`;
|
|
148
202
|
return injectHtmlAttributes(result, attrs);
|
|
149
203
|
}
|
|
@@ -151,46 +205,48 @@ export class Renderer {
|
|
|
151
205
|
/**
|
|
152
206
|
* span level renderer
|
|
153
207
|
*/
|
|
154
|
-
strong(
|
|
155
|
-
return `<strong>${
|
|
208
|
+
strong({ tokens }: Tokens["Strong"]): string {
|
|
209
|
+
return `<strong>${this.parser.parseInline(tokens)}</strong>`;
|
|
156
210
|
}
|
|
157
211
|
|
|
158
|
-
em(
|
|
159
|
-
return `<em>${
|
|
212
|
+
em({ tokens }: Tokens["Em"]): string {
|
|
213
|
+
return `<em>${this.parser.parseInline(tokens)}</em>`;
|
|
160
214
|
}
|
|
161
215
|
|
|
162
|
-
codespan(text:
|
|
163
|
-
return `<code>${text}</code>`;
|
|
216
|
+
codespan({ text }: Tokens["Codespan"]): string {
|
|
217
|
+
return `<code>${escape(text, true)}</code>`;
|
|
164
218
|
}
|
|
165
219
|
|
|
166
|
-
br(): string {
|
|
220
|
+
br(_token: Tokens["Br"]): string {
|
|
167
221
|
return "<br>";
|
|
168
222
|
}
|
|
169
223
|
|
|
170
|
-
del(
|
|
171
|
-
return `<del>${
|
|
224
|
+
del({ tokens }: Tokens["Del"]): string {
|
|
225
|
+
return `<del>${this.parser.parseInline(tokens)}</del>`;
|
|
172
226
|
}
|
|
173
227
|
|
|
174
|
-
link(href
|
|
228
|
+
link({ href, title, tokens }: Tokens["Link"]): string {
|
|
229
|
+
const text = this.parser.parseInline(tokens);
|
|
175
230
|
const cleanHref = cleanUrl(href);
|
|
176
231
|
if (cleanHref === null) {
|
|
177
232
|
return text;
|
|
178
233
|
}
|
|
179
234
|
const attrs: HTMLAttrs = [["href", cleanHref]];
|
|
180
|
-
if (title) attrs.push(["title", title]);
|
|
235
|
+
if (title) attrs.push(["title", escape(title)]);
|
|
181
236
|
return injectHtmlAttributes(`<a>${text}</a>`, attrs);
|
|
182
237
|
}
|
|
183
238
|
|
|
184
|
-
image(href
|
|
239
|
+
image({ href, title, tokens }: Tokens["Image"]): string {
|
|
240
|
+
const text = this.parser.parseInline(tokens, this.parser.textRenderer);
|
|
185
241
|
const cleanHref = cleanUrl(href);
|
|
186
242
|
if (cleanHref === null) {
|
|
187
|
-
return text;
|
|
243
|
+
return escape(text);
|
|
188
244
|
}
|
|
189
245
|
const attrs: HTMLAttrs = [
|
|
190
246
|
["src", fixLocalImageHref(cleanHref, this.pantsdown.config)],
|
|
191
|
-
["alt", text],
|
|
247
|
+
["alt", escape(text)],
|
|
192
248
|
];
|
|
193
|
-
if (title) attrs.push(["title", title]);
|
|
249
|
+
if (title) attrs.push(["title", escape(title)]);
|
|
194
250
|
return injectHtmlAttributes("<img>", attrs);
|
|
195
251
|
}
|
|
196
252
|
|
|
@@ -202,9 +258,19 @@ export class Renderer {
|
|
|
202
258
|
}" data-footnote-ref aria-describedby="footnote-label">${token.label}</a></sup>`;
|
|
203
259
|
}
|
|
204
260
|
|
|
205
|
-
footnotes(token: Tokens["Footnotes"]
|
|
261
|
+
footnotes(token: Tokens["Footnotes"]) {
|
|
206
262
|
if (!token.items.length) return "";
|
|
207
263
|
|
|
264
|
+
const body = token.items.reduce((acc, { label, content, sourceMap }) => {
|
|
265
|
+
let footnoteItem = `<li id="footnote-${encodeURIComponent(label)}">\n`;
|
|
266
|
+
footnoteItem += this.parser.parse(content);
|
|
267
|
+
footnoteItem += "</li>\n";
|
|
268
|
+
|
|
269
|
+
footnoteItem = injectHtmlAttributes(footnoteItem, [], sourceMap);
|
|
270
|
+
|
|
271
|
+
return acc + footnoteItem;
|
|
272
|
+
}, "");
|
|
273
|
+
|
|
208
274
|
let footnotesHTML = '<section class="footnotes" data-footnotes>\n';
|
|
209
275
|
footnotesHTML += `<ol>\n${body}\n</ol>\n`;
|
|
210
276
|
footnotesHTML += "</section>\n";
|
|
@@ -212,13 +278,17 @@ export class Renderer {
|
|
|
212
278
|
return footnotesHTML;
|
|
213
279
|
}
|
|
214
280
|
|
|
215
|
-
text(
|
|
216
|
-
return
|
|
281
|
+
text(token: Tokens["Text"] | Tokens["Escape"] | Tokens["Tag"]): string {
|
|
282
|
+
return "tokens" in token
|
|
283
|
+
? this.parser.parseInline(token.tokens)
|
|
284
|
+
: "escaped" in token && token.escaped
|
|
285
|
+
? token.text
|
|
286
|
+
: escape(token.text);
|
|
217
287
|
}
|
|
218
288
|
|
|
219
|
-
latexBlock(
|
|
289
|
+
latexBlock({ text, sourceMap }: Pick<Tokens["LatexBlock"], "text" | "sourceMap">): string {
|
|
220
290
|
try {
|
|
221
|
-
const rendered = renderToString(
|
|
291
|
+
const rendered = renderToString(text, {
|
|
222
292
|
displayMode: true,
|
|
223
293
|
throwOnError: false,
|
|
224
294
|
output: "html",
|
|
@@ -227,16 +297,16 @@ export class Renderer {
|
|
|
227
297
|
return injectHtmlAttributes(`<div class="katex-block">${rendered}</div>\n`, [], sourceMap);
|
|
228
298
|
} catch {
|
|
229
299
|
return injectHtmlAttributes(
|
|
230
|
-
`<div class="katex-block katex-error"><code>${escape(
|
|
300
|
+
`<div class="katex-block katex-error"><code>${escape(text)}</code></div>\n`,
|
|
231
301
|
[],
|
|
232
302
|
sourceMap,
|
|
233
303
|
);
|
|
234
304
|
}
|
|
235
305
|
}
|
|
236
306
|
|
|
237
|
-
latexInline(
|
|
307
|
+
latexInline({ text }: Tokens["LatexInline"]): string {
|
|
238
308
|
try {
|
|
239
|
-
const rendered = renderToString(
|
|
309
|
+
const rendered = renderToString(text, {
|
|
240
310
|
displayMode: false,
|
|
241
311
|
throwOnError: false,
|
|
242
312
|
output: "html",
|
|
@@ -244,7 +314,7 @@ export class Renderer {
|
|
|
244
314
|
});
|
|
245
315
|
return `<span class="katex-inline">${rendered}</span>`;
|
|
246
316
|
} catch {
|
|
247
|
-
return `<span class="katex-inline katex-error"><code>${escape(
|
|
317
|
+
return `<span class="katex-inline katex-error"><code>${escape(text)}</code></span>`;
|
|
248
318
|
}
|
|
249
319
|
}
|
|
250
320
|
}
|
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,
|