pantsdown 1.4.5 → 1.5.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 CHANGED
@@ -5,12 +5,11 @@
5
5
  <img src="https://raw.githubusercontent.com/wallpants/pantsdown/main/docs/github.svg" height="60px" align="right" />
6
6
  <img src="https://raw.githubusercontent.com/wallpants/pantsdown/main/docs/markdown.svg" height="60px" align="right" />
7
7
 
8
- Pantsdown is a **Markdown** to **"GitHub HTML"** converter based on [Marked](https://github.com/markedjs/marked).
9
- Basically this is a non customizable version of Marked, optimized to render markdown similar to how GitHub does it plus
8
+ Pantsdown is a **Markdown** to **HTML** converter. It attempts to render markdown similar to how GitHub does it plus
10
9
  some features developed specifically for [github-preview.nvim](https://github.com/wallpants/github-preview.nvim).
11
10
 
12
11
  If you need a feature that's supported by GitHub and is not already listed in the [Roadmap](#roadmap),
13
- feel free to open an issue. If you need anything else, use Marked or another library.
12
+ feel free to open an issue.
14
13
 
15
14
  ### [Demo](https://wallpants.github.io/pantsdown/)
16
15
 
@@ -133,5 +132,5 @@ console.log(html);
133
132
  ## Roadmap
134
133
 
135
134
  - [ ] Add button to copy code in fences
136
- - [ ] [Mermaid](https://mermaid.js.org/) support
135
+ - [ ] [Mermaid](https://mermaid.js.org/) usage docs
137
136
  - [ ] [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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantsdown",
3
- "version": "1.4.5",
3
+ "version": "1.5.0",
4
4
  "author": "wallpants",
5
5
  "description": "Markdown to 'GitHub HTML' parser",
6
6
  "license": "MIT",
@@ -64,6 +64,13 @@
64
64
  word-wrap: break-word;
65
65
  }
66
66
 
67
+ .pantsdown .mermaid-container {
68
+ margin-bottom: 16px;
69
+ }
70
+
71
+ /* .pantsdown .mermaid { */
72
+ /* } */
73
+
67
74
  .pantsdown .octicon {
68
75
  display: inline-block;
69
76
  fill: currentColor;
package/src/renderer.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import GithubSlugger from "github-slugger";
2
2
  import hljs from "highlight.js";
3
3
  import { inline } from "./rules/inline.ts";
4
- import { type PantsdownConfig, type SourceMap } from "./types.ts";
4
+ import { type HTMLAttrs, type PantsdownConfig, type SourceMap } from "./types.ts";
5
5
  import {
6
6
  cleanUrl,
7
7
  escape,
@@ -9,7 +9,6 @@ import {
9
9
  fixLocalImageHref,
10
10
  getHtmlElementText,
11
11
  injectHtmlAttributes,
12
- renderHtmlClasses,
13
12
  } from "./utils.ts";
14
13
 
15
14
  const defaultConfig: NonNullable<PantsdownConfig["renderer"]> = {
@@ -31,9 +30,16 @@ export class Renderer {
31
30
 
32
31
  code(code: string, infostring: string | undefined, sourceMap: SourceMap): string {
33
32
  const lang = (infostring ?? "").match(/^\S*/)?.[0];
34
-
35
33
  code = code.replace(/\n$/, "") + "\n";
36
34
 
35
+ if (lang === "mermaid") {
36
+ return injectHtmlAttributes(
37
+ `<section><div class="mermaid">${code}</div></section>`,
38
+ [["class", "mermaid-container"]],
39
+ sourceMap,
40
+ );
41
+ }
42
+
37
43
  const language = lang && hljs.getLanguage(lang) ? lang : "plaintext";
38
44
  const highlightedCode = hljs.highlight(code, { language }).value;
39
45
 
@@ -54,7 +60,7 @@ export class Renderer {
54
60
  html(html: string, _block: boolean, sourceMap?: SourceMap | undefined): string {
55
61
  const result = fixHtmlLocalImageHref(html, this.rendererConfig.relativeImageUrlPrefix);
56
62
 
57
- const attrs: [name: string, value: string | number][] = [];
63
+ const attrs: HTMLAttrs = [];
58
64
 
59
65
  if (this.rendererConfig.detailsTagDefaultOpen) {
60
66
  const tag = inline.tag.exec(html);
@@ -69,12 +75,12 @@ export class Renderer {
69
75
  heading(text: string, level: number, sourceMap: SourceMap): string {
70
76
  const elementText = getHtmlElementText(text);
71
77
  const slug = this.slugger.slug(elementText);
72
- let result = `<h${level} style="position: relative;">`;
78
+ let result = `<h${level}>`;
73
79
  // span with negative top to add some offset when scrolling to #slug
74
80
  result += `<span style="position: absolute; top: -50px;" id="${slug}"></span>`;
75
81
  result += `${text}<a class="anchor octicon-link" href="#${slug}"></a>`;
76
82
  result += `</h${level}>\n`;
77
- return injectHtmlAttributes(result, [], sourceMap);
83
+ return injectHtmlAttributes(result, [["style", "position: relative;"]], sourceMap);
78
84
  }
79
85
 
80
86
  hr(sourceMap: SourceMap): string {
@@ -83,28 +89,35 @@ export class Renderer {
83
89
 
84
90
  list(body: string, ordered: boolean, start: number | "", classes: string[] = []): string {
85
91
  const type = ordered ? "ol" : "ul";
86
- const startatt = ordered && start !== 1 ? ' start="' + start + '"' : "";
87
- return (
88
- "<" + type + startatt + `${renderHtmlClasses(classes)}>\n` + body + "</" + type + ">\n"
89
- );
92
+ const attrs: HTMLAttrs = [["class", classes.join(" ")]];
93
+ if (ordered && start && start !== 1) {
94
+ attrs.push(["start", String(start)]);
95
+ }
96
+ const result = `<${type}>\n${body}</${type}>\n`;
97
+ return injectHtmlAttributes(result, attrs);
90
98
  }
91
99
 
92
100
  listitem(text: string, task: boolean, _checked: boolean, sourceMap: SourceMap): string {
93
101
  const attrs: [string, string][] = [];
94
102
  if (task) attrs.push(["class", "task-list-item"]);
95
- return injectHtmlAttributes(`<li>${text}</li>\n`, attrs, sourceMap);
103
+ const result = `<li>${text}</li>\n`;
104
+ return injectHtmlAttributes(result, attrs, sourceMap);
96
105
  }
97
106
 
98
107
  checkbox(checked: boolean, classes: string[] = []): string {
99
- return (
100
- "<input " +
101
- (checked ? 'checked="" ' : "") +
102
- `disabled="" type="checkbox"${renderHtmlClasses(classes)}>`
103
- );
108
+ const result = "<input>";
109
+ const attrs: HTMLAttrs = [
110
+ ["disabled", ""],
111
+ ["type", "checkbox"],
112
+ ["class", classes.join(" ")],
113
+ ];
114
+ if (checked) attrs.push(["checked", ""]);
115
+ return injectHtmlAttributes(result, attrs);
104
116
  }
105
117
 
106
118
  paragraph(text: string, sourceMap: SourceMap): string {
107
- return injectHtmlAttributes(`<p>${text}</p>\n`, [], sourceMap);
119
+ const result = `<p>${text}</p>\n`;
120
+ return injectHtmlAttributes(result, [], sourceMap);
108
121
  }
109
122
 
110
123
  table(header: string, body: string): string {
@@ -113,11 +126,8 @@ export class Renderer {
113
126
  }
114
127
 
115
128
  tablerow(content: string, sourceMapStart: number | undefined): string {
116
- return injectHtmlAttributes(
117
- `<tr>\n${content}</tr>\n`,
118
- [],
119
- sourceMapStart ? [sourceMapStart, sourceMapStart] : undefined,
120
- );
129
+ const sourceMap: SourceMap = sourceMapStart ? [sourceMapStart, sourceMapStart] : undefined;
130
+ return injectHtmlAttributes(`<tr>\n${content}</tr>\n`, [], sourceMap);
121
131
  }
122
132
 
123
133
  tablecell(
@@ -128,8 +138,10 @@ export class Renderer {
128
138
  },
129
139
  ): string {
130
140
  const type = flags.header ? "th" : "td";
131
- const tag = flags.align ? `<${type} align="${flags.align}">` : `<${type}>`;
132
- return tag + content + `</${type}>\n`;
141
+ const attrs: HTMLAttrs = [];
142
+ if (flags.align) attrs.push(["align", flags.align]);
143
+ const result = `<${type}>` + content + `</${type}>\n`;
144
+ return injectHtmlAttributes(result, attrs);
133
145
  }
134
146
 
135
147
  /**
@@ -160,13 +172,9 @@ export class Renderer {
160
172
  if (cleanHref === null) {
161
173
  return text;
162
174
  }
163
- href = cleanHref;
164
- let out = '<a href="' + href + '"';
165
- if (title) {
166
- out += ' title="' + title + '"';
167
- }
168
- out += ">" + text + "</a>";
169
- return out;
175
+ const attrs: HTMLAttrs = [["href", cleanHref]];
176
+ if (title) attrs.push(["title", title]);
177
+ return injectHtmlAttributes(`<a>${text}</a>`, attrs);
170
178
  }
171
179
 
172
180
  image(href: string, title: string | null, text: string): string {
@@ -174,15 +182,12 @@ export class Renderer {
174
182
  if (cleanHref === null) {
175
183
  return text;
176
184
  }
177
- href = fixLocalImageHref(cleanHref, this.rendererConfig.relativeImageUrlPrefix);
178
-
179
- let out = `<img src="${href}" alt="${text}"`;
180
-
181
- if (title) {
182
- out += ` title="${title}"`;
183
- }
184
- out += ">";
185
- return out;
185
+ const attrs: HTMLAttrs = [
186
+ ["src", fixLocalImageHref(cleanHref, this.rendererConfig.relativeImageUrlPrefix)],
187
+ ["alt", text],
188
+ ];
189
+ if (title) attrs.push(["title", title]);
190
+ return injectHtmlAttributes("<img>", attrs);
186
191
  }
187
192
 
188
193
  text(text: string): string {
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export type HTMLAttrs = [name: string, value: string][];
2
+
1
3
  type BaseToken = {
2
4
  type: string;
3
5
  raw: string;
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Lexer } from "./lexer.ts";
2
- import { type SourceMap, type Tokens } from "./types.ts";
2
+ import { type HTMLAttrs, type SourceMap, type Tokens } from "./types.ts";
3
3
 
4
4
  /**
5
5
  * Helpers
@@ -33,14 +33,6 @@ export function escape(html: string, encode?: boolean) {
33
33
  return html;
34
34
  }
35
35
 
36
- export function renderHtmlClasses(classes: string[]) {
37
- if (!classes.length) return "";
38
- let result = ' class="';
39
- result += classes.join(" ");
40
- result += '"';
41
- return result;
42
- }
43
-
44
36
  export function getHtmlElementText(html: string) {
45
37
  try {
46
38
  const parser = new DOMParser();
@@ -56,20 +48,10 @@ export function getHtmlElementText(html: string) {
56
48
  }
57
49
  }
58
50
 
59
- /**
60
- * Only works when html element has no other attributes already defined.
61
- * For the regex to work the element must be clean of attributes:
62
- *
63
- * <div>{whatever}</div>
64
- */
65
- export function injectHtmlAttributes(
66
- html: string,
67
- attrs: [name: string, value: string | number][],
68
- sourceMap: SourceMap,
69
- ) {
51
+ export function injectHtmlAttributes(html: string, attrs: HTMLAttrs, sourceMap?: SourceMap) {
70
52
  if (sourceMap) {
71
- attrs.push(["line-start", sourceMap[0]]);
72
- attrs.push(["line-end", sourceMap[1]]);
53
+ attrs.push(["line-start", String(sourceMap[0])]);
54
+ attrs.push(["line-end", String(sourceMap[1])]);
73
55
  }
74
56
 
75
57
  if (!attrs.length) return html;