pantsdown 1.4.4 → 1.4.5
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 +0 -2
- package/package.json +1 -1
- package/src/parser.ts +2 -2
- package/src/renderer.ts +22 -44
- package/src/tokenizer.ts +1 -3
- package/src/types.ts +1 -1
- package/src/utils.ts +15 -19
package/README.md
CHANGED
|
@@ -132,8 +132,6 @@ console.log(html);
|
|
|
132
132
|
|
|
133
133
|
## Roadmap
|
|
134
134
|
|
|
135
|
-
- [x] Add id & anchors to headings
|
|
136
|
-
- [ ] Add sourcemap to list items
|
|
137
135
|
- [ ] Add button to copy code in fences
|
|
138
136
|
- [ ] [Mermaid](https://mermaid.js.org/) support
|
|
139
137
|
- [ ] [Footnote](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes) support [Base](https://github.com/bent10/marked-extensions/tree/main/packages/footnote)
|
package/package.json
CHANGED
package/src/parser.ts
CHANGED
|
@@ -126,13 +126,13 @@ export class Parser {
|
|
|
126
126
|
itemBody,
|
|
127
127
|
task,
|
|
128
128
|
Boolean(checked),
|
|
129
|
-
|
|
129
|
+
item.sourceMap,
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
const listClasses: string[] = [];
|
|
134
134
|
if (containsTaskList) listClasses.push("contains-task-list");
|
|
135
|
-
out += this.renderer.list(body, ordered, start,
|
|
135
|
+
out += this.renderer.list(body, ordered, start, listClasses);
|
|
136
136
|
continue;
|
|
137
137
|
}
|
|
138
138
|
case "html": {
|
package/src/renderer.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
getHtmlElementText,
|
|
11
11
|
injectHtmlAttributes,
|
|
12
12
|
renderHtmlClasses,
|
|
13
|
-
renderSourceMap,
|
|
14
13
|
} from "./utils.ts";
|
|
15
14
|
|
|
16
15
|
const defaultConfig: NonNullable<PantsdownConfig["renderer"]> = {
|
|
@@ -38,13 +37,14 @@ export class Renderer {
|
|
|
38
37
|
const language = lang && hljs.getLanguage(lang) ? lang : "plaintext";
|
|
39
38
|
const highlightedCode = hljs.highlight(code, { language }).value;
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
`<pre
|
|
40
|
+
const result =
|
|
41
|
+
`<pre><code class="hljs language-` +
|
|
43
42
|
escape(language) +
|
|
44
43
|
'">' +
|
|
45
44
|
highlightedCode +
|
|
46
|
-
"</code></pre>\n"
|
|
47
|
-
|
|
45
|
+
"</code></pre>\n";
|
|
46
|
+
|
|
47
|
+
return injectHtmlAttributes(result, [], sourceMap);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
blockquote(quote: string): string {
|
|
@@ -54,69 +54,45 @@ export class Renderer {
|
|
|
54
54
|
html(html: string, _block: boolean, sourceMap?: SourceMap | undefined): string {
|
|
55
55
|
const result = fixHtmlLocalImageHref(html, this.rendererConfig.relativeImageUrlPrefix);
|
|
56
56
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
if (sourceMap) {
|
|
60
|
-
htmlAttributes.push(["line-start", sourceMap[0]], ["line-end", sourceMap[1]]);
|
|
61
|
-
}
|
|
57
|
+
const attrs: [name: string, value: string | number][] = [];
|
|
62
58
|
|
|
63
59
|
if (this.rendererConfig.detailsTagDefaultOpen) {
|
|
64
60
|
const tag = inline.tag.exec(html);
|
|
65
61
|
if (tag?.[0] === "<details>") {
|
|
66
|
-
|
|
62
|
+
attrs.push(["open", ""]);
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
return injectHtmlAttributes(result,
|
|
66
|
+
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
heading(text: string, level: number, sourceMap: SourceMap): string {
|
|
74
70
|
const elementText = getHtmlElementText(text);
|
|
75
71
|
const slug = this.slugger.slug(elementText);
|
|
76
|
-
let result = `<h${level}
|
|
72
|
+
let result = `<h${level} style="position: relative;">`;
|
|
77
73
|
// span with negative top to add some offset when scrolling to #slug
|
|
78
74
|
result += `<span style="position: absolute; top: -50px;" id="${slug}"></span>`;
|
|
79
75
|
result += `${text}<a class="anchor octicon-link" href="#${slug}"></a>`;
|
|
80
76
|
result += `</h${level}>\n`;
|
|
81
|
-
return result;
|
|
77
|
+
return injectHtmlAttributes(result, [], sourceMap);
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
hr(sourceMap: SourceMap): string {
|
|
85
|
-
return `<hr
|
|
81
|
+
return injectHtmlAttributes(`<hr>\n`, [], sourceMap);
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
list(
|
|
89
|
-
body: string,
|
|
90
|
-
ordered: boolean,
|
|
91
|
-
start: number | "",
|
|
92
|
-
sourceMap: SourceMap,
|
|
93
|
-
classes: string[] = [],
|
|
94
|
-
): string {
|
|
84
|
+
list(body: string, ordered: boolean, start: number | "", classes: string[] = []): string {
|
|
95
85
|
const type = ordered ? "ol" : "ul";
|
|
96
86
|
const startatt = ordered && start !== 1 ? ' start="' + start + '"' : "";
|
|
97
87
|
return (
|
|
98
|
-
"<" +
|
|
99
|
-
type +
|
|
100
|
-
startatt +
|
|
101
|
-
`${renderSourceMap(sourceMap)}${renderHtmlClasses(classes)}>\n` +
|
|
102
|
-
body +
|
|
103
|
-
"</" +
|
|
104
|
-
type +
|
|
105
|
-
">\n"
|
|
88
|
+
"<" + type + startatt + `${renderHtmlClasses(classes)}>\n` + body + "</" + type + ">\n"
|
|
106
89
|
);
|
|
107
90
|
}
|
|
108
91
|
|
|
109
|
-
listitem(
|
|
110
|
-
|
|
111
|
-
task
|
|
112
|
-
|
|
113
|
-
lineStart: number | undefined,
|
|
114
|
-
): string {
|
|
115
|
-
const classes: string[] = [];
|
|
116
|
-
if (task) classes.push("task-list-item");
|
|
117
|
-
return `<li${renderHtmlClasses(classes)}${renderSourceMap(
|
|
118
|
-
lineStart ? [lineStart, lineStart] : undefined,
|
|
119
|
-
)}>${text}</li>\n`;
|
|
92
|
+
listitem(text: string, task: boolean, _checked: boolean, sourceMap: SourceMap): string {
|
|
93
|
+
const attrs: [string, string][] = [];
|
|
94
|
+
if (task) attrs.push(["class", "task-list-item"]);
|
|
95
|
+
return injectHtmlAttributes(`<li>${text}</li>\n`, attrs, sourceMap);
|
|
120
96
|
}
|
|
121
97
|
|
|
122
98
|
checkbox(checked: boolean, classes: string[] = []): string {
|
|
@@ -128,7 +104,7 @@ export class Renderer {
|
|
|
128
104
|
}
|
|
129
105
|
|
|
130
106
|
paragraph(text: string, sourceMap: SourceMap): string {
|
|
131
|
-
return `<p
|
|
107
|
+
return injectHtmlAttributes(`<p>${text}</p>\n`, [], sourceMap);
|
|
132
108
|
}
|
|
133
109
|
|
|
134
110
|
table(header: string, body: string): string {
|
|
@@ -137,9 +113,11 @@ export class Renderer {
|
|
|
137
113
|
}
|
|
138
114
|
|
|
139
115
|
tablerow(content: string, sourceMapStart: number | undefined): string {
|
|
140
|
-
return
|
|
116
|
+
return injectHtmlAttributes(
|
|
117
|
+
`<tr>\n${content}</tr>\n`,
|
|
118
|
+
[],
|
|
141
119
|
sourceMapStart ? [sourceMapStart, sourceMapStart] : undefined,
|
|
142
|
-
)
|
|
120
|
+
);
|
|
143
121
|
}
|
|
144
122
|
|
|
145
123
|
tablecell(
|
package/src/tokenizer.ts
CHANGED
|
@@ -132,7 +132,6 @@ export class Tokenizer {
|
|
|
132
132
|
start: isordered ? +bull.slice(0, -1) : "",
|
|
133
133
|
loose: false,
|
|
134
134
|
items: [] as Tokens["ListItem"][],
|
|
135
|
-
sourceMap: [0, 0], // updated below once token.raw is built
|
|
136
135
|
};
|
|
137
136
|
|
|
138
137
|
bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
|
|
@@ -294,10 +293,9 @@ export class Tokenizer {
|
|
|
294
293
|
list.items[list.items.length - 1]!.text = itemContents.trimEnd();
|
|
295
294
|
list.raw = list.raw.trimEnd();
|
|
296
295
|
|
|
297
|
-
list.sourceMap = this.lexer.getSourceMap(list.raw);
|
|
298
|
-
|
|
299
296
|
// Item child tokens handled here at end because we needed to have the final item to trim it first
|
|
300
297
|
for (let i = 0, listItemsLen = list.items.length; i < listItemsLen; i++) {
|
|
298
|
+
list.items[i]!.sourceMap = this.lexer.getSourceMap(list.items[i]!.raw);
|
|
301
299
|
this.lexer.state.top = false;
|
|
302
300
|
list.items[i]!.tokens = this.lexer.blockTokens(list.items[i]!.text, []);
|
|
303
301
|
|
package/src/types.ts
CHANGED
|
@@ -61,7 +61,6 @@ export interface Tokens extends Record<string, BaseToken> {
|
|
|
61
61
|
start: number | "";
|
|
62
62
|
loose: boolean;
|
|
63
63
|
items: Tokens["ListItem"][];
|
|
64
|
-
sourceMap: SourceMap;
|
|
65
64
|
};
|
|
66
65
|
ListItem: {
|
|
67
66
|
type: "list_item";
|
|
@@ -71,6 +70,7 @@ export interface Tokens extends Record<string, BaseToken> {
|
|
|
71
70
|
checked?: boolean | undefined;
|
|
72
71
|
loose: boolean;
|
|
73
72
|
tokens: Token[];
|
|
73
|
+
sourceMap?: SourceMap;
|
|
74
74
|
};
|
|
75
75
|
Paragraph: {
|
|
76
76
|
type: "paragraph";
|
package/src/utils.ts
CHANGED
|
@@ -41,20 +41,15 @@ export function renderHtmlClasses(classes: string[]) {
|
|
|
41
41
|
return result;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function parseHtmlElement(html: string) {
|
|
45
|
-
const parser = new DOMParser();
|
|
46
|
-
const doc = parser.parseFromString(html, "text/html");
|
|
47
|
-
// eslint-disable-next-line
|
|
48
|
-
if (!doc.body) throw Error("Invalid HTML");
|
|
49
|
-
const element = doc.body.firstChild as HTMLElement;
|
|
50
|
-
// eslint-disable-next-line
|
|
51
|
-
if (!element) throw Error("No valid element found");
|
|
52
|
-
return element;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
44
|
export function getHtmlElementText(html: string) {
|
|
56
45
|
try {
|
|
57
|
-
const
|
|
46
|
+
const parser = new DOMParser();
|
|
47
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
48
|
+
// eslint-disable-next-line
|
|
49
|
+
if (!doc.body) throw Error("Invalid HTML");
|
|
50
|
+
const element = doc.body.firstChild as HTMLElement;
|
|
51
|
+
// eslint-disable-next-line
|
|
52
|
+
if (!element) throw Error("No valid element found");
|
|
58
53
|
return element.textContent ?? html;
|
|
59
54
|
} catch (_) {
|
|
60
55
|
return html;
|
|
@@ -70,14 +65,20 @@ export function getHtmlElementText(html: string) {
|
|
|
70
65
|
export function injectHtmlAttributes(
|
|
71
66
|
html: string,
|
|
72
67
|
attrs: [name: string, value: string | number][],
|
|
68
|
+
sourceMap: SourceMap,
|
|
73
69
|
) {
|
|
70
|
+
if (sourceMap) {
|
|
71
|
+
attrs.push(["line-start", sourceMap[0]]);
|
|
72
|
+
attrs.push(["line-end", sourceMap[1]]);
|
|
73
|
+
}
|
|
74
|
+
|
|
74
75
|
if (!attrs.length) return html;
|
|
75
76
|
|
|
76
|
-
const closingBracket = /[a-zA-Z0-9]
|
|
77
|
+
const closingBracket = /[a-zA-Z0-9\/"]>/;
|
|
77
78
|
const match = html.match(closingBracket);
|
|
78
79
|
if (match) {
|
|
79
80
|
let htmlAttrs = "";
|
|
80
|
-
attrs.forEach((atrr) => (htmlAttrs += ` ${atrr[0]}
|
|
81
|
+
attrs.forEach((atrr) => (htmlAttrs += ` ${atrr[0]}="${atrr[1]}"`));
|
|
81
82
|
const sliceIdx = match.index! + (match[0] === "/>" ? -1 : 1);
|
|
82
83
|
return html.slice(0, sliceIdx) + htmlAttrs + html.slice(sliceIdx);
|
|
83
84
|
}
|
|
@@ -128,11 +129,6 @@ export function fixLocalImageHref(
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
export function renderSourceMap(sourceMap: SourceMap) {
|
|
132
|
-
if (!sourceMap) return "";
|
|
133
|
-
return ` line-start=${sourceMap[0]} line-end=${sourceMap[1]}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
132
|
export function cleanUrl(href: string) {
|
|
137
133
|
try {
|
|
138
134
|
href = encodeURI(href).replace(/%25/g, "%");
|