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