pantsdown 2.2.5 → 2.2.7
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/package.json +17 -17
- package/src/lexer.ts +5 -0
- package/src/renderer.ts +3 -1
- package/src/tokenizer.ts +7 -12
- package/src/types.ts +1 -1
- package/src/utils.ts +7 -7
- 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 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantsdown",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
4
4
|
"description": "Markdown to 'GitHub HTML' parser",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wallpants",
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git://github.com/wallpants/pantsdown.git"
|
|
10
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"!src/**/*.test.ts"
|
|
14
|
+
],
|
|
11
15
|
"type": "module",
|
|
12
16
|
"exports": {
|
|
13
17
|
"./*": "./*",
|
|
@@ -26,33 +30,29 @@
|
|
|
26
30
|
"commit": "cz",
|
|
27
31
|
"format": "oxfmt .",
|
|
28
32
|
"typecheck": "tsc",
|
|
29
|
-
"lint": "
|
|
33
|
+
"lint": "oxlint .",
|
|
30
34
|
"check": "bun run typecheck && bun run lint",
|
|
31
35
|
"docs:build": "bun run docs/build.ts"
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
34
38
|
"github-slugger": "^2.0.0",
|
|
35
39
|
"highlight.js": "^11.11.1",
|
|
36
|
-
"katex": "^0.
|
|
40
|
+
"katex": "^0.18.0"
|
|
37
41
|
},
|
|
38
42
|
"devDependencies": {
|
|
39
|
-
"@commitlint/config-conventional": "^21.0
|
|
40
|
-
"@commitlint/cz-commitlint": "^21.0
|
|
41
|
-
"@happy-dom/global-registrator": "^20.
|
|
43
|
+
"@commitlint/config-conventional": "^21.2.0",
|
|
44
|
+
"@commitlint/cz-commitlint": "^21.2.0",
|
|
45
|
+
"@happy-dom/global-registrator": "^20.11.0",
|
|
42
46
|
"@types/bun": "^1.3.14",
|
|
43
47
|
"@types/katex": "^0.16.8",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"commitizen": "^4.3.1",
|
|
47
|
-
"commitlint": "^21.0.2",
|
|
48
|
-
"eslint": "^9.39.4",
|
|
49
|
-
"eslint-import-resolver-typescript": "^4.4.4",
|
|
50
|
-
"eslint-plugin-import-x": "^4.16.2",
|
|
48
|
+
"commitizen": "^4.3.2",
|
|
49
|
+
"commitlint": "^21.2.1",
|
|
51
50
|
"inquirer": "^12.11.1",
|
|
52
|
-
"oxfmt": "^0.
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
51
|
+
"oxfmt": "^0.59.0",
|
|
52
|
+
"oxlint": "^1.73.0",
|
|
53
|
+
"oxlint-tsgolint": "^0.25.0",
|
|
54
|
+
"semantic-release": "^25.0.8",
|
|
55
|
+
"typescript": "7.0.2"
|
|
56
56
|
},
|
|
57
57
|
"commitlint": {
|
|
58
58
|
"extends": [
|
package/src/lexer.ts
CHANGED
package/src/renderer.ts
CHANGED
|
@@ -33,7 +33,9 @@ export class Renderer {
|
|
|
33
33
|
|
|
34
34
|
if (lang === "mermaid") {
|
|
35
35
|
attrs.push(["class", "mermaid-container mermaid"]);
|
|
36
|
-
|
|
36
|
+
// escape so diagram source can't be parsed as HTML;
|
|
37
|
+
// mermaid reads textContent, which undoes the escaping
|
|
38
|
+
const result = `<pre style="position: relative;">` + escape(code, true) + "\n" + `</pre>`;
|
|
37
39
|
return injectHtmlAttributes(result, attrs, sourceMap);
|
|
38
40
|
}
|
|
39
41
|
|
package/src/tokenizer.ts
CHANGED
|
@@ -317,7 +317,8 @@ export class Tokenizer {
|
|
|
317
317
|
const lastTrimmed = raw.trimEnd();
|
|
318
318
|
|
|
319
319
|
if (list.items[list.items.length - 1]!.sourceMap) {
|
|
320
|
-
|
|
320
|
+
// give back the trimmed trailing newlines; the counter tracks lines, not chars
|
|
321
|
+
this.lexer.line -= (raw.slice(lastTrimmed.length).match(/\n/g) ?? []).length;
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
list.items[list.items.length - 1]!.raw = lastTrimmed;
|
|
@@ -333,8 +334,7 @@ export class Tokenizer {
|
|
|
333
334
|
// Check if list should be loose
|
|
334
335
|
const spacers = list.items[i]!.tokens.filter((t) => t.type === "space");
|
|
335
336
|
const hasMultipleLineBreaks =
|
|
336
|
-
|
|
337
|
-
spacers.length > 0 && spacers.some((t: any) => /\n.*\n/.test(t.raw));
|
|
337
|
+
spacers.length > 0 && spacers.some((t) => /\n.*\n/.test(t.raw));
|
|
338
338
|
|
|
339
339
|
list.loose = hasMultipleLineBreaks;
|
|
340
340
|
}
|
|
@@ -703,13 +703,11 @@ export class Tokenizer {
|
|
|
703
703
|
// _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
|
|
704
704
|
if (match[3] && /[\p{L}\p{N}]/u.exec(prevChar)) return;
|
|
705
705
|
|
|
706
|
-
// eslint-disable-next-line
|
|
707
706
|
const nextChar = match[1] || match[2] || "";
|
|
708
707
|
|
|
709
708
|
if (!nextChar || !prevChar || inline.punctuation.exec(prevChar)) {
|
|
710
|
-
// unicode Regex counts emoji as 1 char;
|
|
711
|
-
|
|
712
|
-
const lLength = [...match[0]].length - 1;
|
|
709
|
+
// unicode Regex counts emoji as 1 char; convert to array for proper count (used multiple times below)
|
|
710
|
+
const lLength = Array.from(match[0]).length - 1;
|
|
713
711
|
let rDelim,
|
|
714
712
|
rLength,
|
|
715
713
|
delimTotal = lLength,
|
|
@@ -724,13 +722,11 @@ export class Tokenizer {
|
|
|
724
722
|
maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
|
|
725
723
|
|
|
726
724
|
while ((match = endReg.exec(maskedSrc)) != null) {
|
|
727
|
-
// eslint-disable-next-line
|
|
728
725
|
rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
|
|
729
726
|
|
|
730
727
|
if (!rDelim) continue; // skip single * in __abc*abc__
|
|
731
728
|
|
|
732
|
-
|
|
733
|
-
rLength = [...rDelim].length;
|
|
729
|
+
rLength = Array.from(rDelim).length;
|
|
734
730
|
|
|
735
731
|
if (match[3] || match[4]) {
|
|
736
732
|
// found another Left Delim
|
|
@@ -751,8 +747,7 @@ export class Tokenizer {
|
|
|
751
747
|
// Remove extra characters. *a*** -> *a*
|
|
752
748
|
rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
|
|
753
749
|
// char length can be >1 for unicode characters;
|
|
754
|
-
|
|
755
|
-
const lastCharLength = [...match[0]][0]!.length;
|
|
750
|
+
const lastCharLength = Array.from(match[0])[0]!.length;
|
|
756
751
|
const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
|
|
757
752
|
|
|
758
753
|
// Create `em` if smallest delimiter has odd char count. *a***
|
package/src/types.ts
CHANGED
|
@@ -9,7 +9,7 @@ type BaseToken = {
|
|
|
9
9
|
|
|
10
10
|
export type SourceMap = [start: number, end: number] | undefined;
|
|
11
11
|
|
|
12
|
-
// eslint-disable-next-line
|
|
12
|
+
// eslint-disable-next-line typescript/consistent-type-definitions
|
|
13
13
|
export interface Tokens extends Record<string, BaseToken> {
|
|
14
14
|
Space: {
|
|
15
15
|
type: "space";
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Lexer } from "./lexer.ts";
|
|
2
|
-
import { PantsdownConfig, type HTMLAttrs, type SourceMap, type Tokens } from "./types.ts";
|
|
2
|
+
import { type PantsdownConfig, type HTMLAttrs, type SourceMap, type Tokens } from "./types.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Helpers
|
|
@@ -37,10 +37,7 @@ export function getHtmlElementText(html: string) {
|
|
|
37
37
|
try {
|
|
38
38
|
const parser = new DOMParser();
|
|
39
39
|
const doc = parser.parseFromString(html, "text/html");
|
|
40
|
-
|
|
41
|
-
if (!doc.body) throw Error("Invalid HTML");
|
|
42
|
-
const element = doc.body.firstChild as HTMLElement;
|
|
43
|
-
// eslint-disable-next-line
|
|
40
|
+
const element = doc.body.firstChild;
|
|
44
41
|
if (!element) throw Error("No valid element found");
|
|
45
42
|
return element.textContent || html;
|
|
46
43
|
} catch (_) {
|
|
@@ -56,12 +53,15 @@ export function injectHtmlAttributes(html: string, attrs: HTMLAttrs, sourceMap?:
|
|
|
56
53
|
|
|
57
54
|
if (!attrs.length) return html;
|
|
58
55
|
|
|
56
|
+
// closing tags cannot carry attributes
|
|
57
|
+
if (/^\s*<\//.test(html)) return html;
|
|
58
|
+
|
|
59
59
|
const closingBracket = /[a-zA-Z0-9\/"]>/;
|
|
60
60
|
const match = closingBracket.exec(html);
|
|
61
61
|
if (match) {
|
|
62
62
|
let htmlAttrs = "";
|
|
63
63
|
attrs.forEach((atrr) => (htmlAttrs += ` ${atrr[0]}="${atrr[1]}"`));
|
|
64
|
-
const sliceIdx = match.index + (match[0] === "/>" ?
|
|
64
|
+
const sliceIdx = match.index + (match[0] === "/>" ? 0 : 1);
|
|
65
65
|
return html.slice(0, sliceIdx) + htmlAttrs + html.slice(sliceIdx);
|
|
66
66
|
}
|
|
67
67
|
return html;
|
|
@@ -314,7 +314,7 @@ export function indentCodeCompensation(raw: string, text: string) {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
function makeAlertRegex(type: string) {
|
|
317
|
-
return new RegExp(`^(?:\\[\\!${type.toUpperCase()}\\]|[\\*]{2}${type}[\\*]{2})[
|
|
317
|
+
return new RegExp(`^(?:\\[\\!${type.toUpperCase()}\\]|[\\*]{2}${type}[\\*]{2})[ \\t]*\\n?`);
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
export const ALERTS = [
|
package/.oxfmtrc.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
-
"ignorePatterns": [".github"],
|
|
4
|
-
"printWidth": 100,
|
|
5
|
-
"tabWidth": 3,
|
|
6
|
-
"sortImports": {
|
|
7
|
-
"newlinesBetween": false
|
|
8
|
-
},
|
|
9
|
-
"overrides": [
|
|
10
|
-
{
|
|
11
|
-
"files": ["*.json", "*.md"],
|
|
12
|
-
"options": {
|
|
13
|
-
"tabWidth": 2
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
package/CLAUDE.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
Pantsdown is a Markdown to HTML converter that renders markdown similar to GitHub's styling. It was built specifically for [github-preview.nvim](https://github.com/wallpants/github-preview.nvim). Based on [Marked](https://github.com/markedjs/marked).
|
|
8
|
-
|
|
9
|
-
## Commands
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Type checking
|
|
13
|
-
bun run typecheck
|
|
14
|
-
|
|
15
|
-
# Linting
|
|
16
|
-
bun run lint
|
|
17
|
-
|
|
18
|
-
# Both typecheck and lint
|
|
19
|
-
bun run check
|
|
20
|
-
|
|
21
|
-
# Run tests (uses bun:test with happy-dom)
|
|
22
|
-
bun test
|
|
23
|
-
|
|
24
|
-
# Run a single test file
|
|
25
|
-
bun test tests/parse.test.ts
|
|
26
|
-
|
|
27
|
-
# Update test snapshots
|
|
28
|
-
bun test --update-snapshots
|
|
29
|
-
|
|
30
|
-
# Format code
|
|
31
|
-
bun run format
|
|
32
|
-
|
|
33
|
-
# Build docs
|
|
34
|
-
bun run docs:build
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Architecture
|
|
38
|
-
|
|
39
|
-
The parsing pipeline follows a classic compiler pattern:
|
|
40
|
-
|
|
41
|
-
1. **Lexer** (`src/lexer.ts`) - Tokenizes markdown source into an array of tokens
|
|
42
|
-
- Uses `Tokenizer` for the actual token creation
|
|
43
|
-
- Processes block-level tokens first, then inline tokens
|
|
44
|
-
- Tracks source maps for line number references
|
|
45
|
-
- Collects footnotes separately
|
|
46
|
-
|
|
47
|
-
2. **Tokenizer** (`src/tokenizer.ts`) - Creates tokens from markdown patterns
|
|
48
|
-
- Uses regex rules from `src/rules/block.ts` and `src/rules/inline.ts`
|
|
49
|
-
|
|
50
|
-
3. **Parser** (`src/parser.ts`) - Converts tokens to HTML by dispatching to the Renderer
|
|
51
|
-
- Recursively processes nested tokens
|
|
52
|
-
|
|
53
|
-
4. **Renderer** (`src/renderer.ts`) - Produces HTML output for each token type
|
|
54
|
-
- Uses highlight.js for syntax highlighting
|
|
55
|
-
- Uses github-slugger for heading anchors
|
|
56
|
-
- Handles special cases like mermaid diagrams and alerts
|
|
57
|
-
|
|
58
|
-
Entry point: `Pantsdown` class in `src/pantsdown.ts` coordinates the pipeline.
|
|
59
|
-
|
|
60
|
-
## Key Types
|
|
61
|
-
|
|
62
|
-
All token types are defined in `src/types.ts`. The `Token` union type covers all possible markdown elements (headings, code blocks, lists, tables, footnotes, alerts, etc.).
|
|
63
|
-
|
|
64
|
-
## Output
|
|
65
|
-
|
|
66
|
-
`pantsdown.parse(markdown)` returns `{ html, javascript }`:
|
|
67
|
-
|
|
68
|
-
- `html`: The rendered HTML string
|
|
69
|
-
- `javascript`: A script for interactive features (task list checkboxes, copy buttons)
|
|
70
|
-
|
|
71
|
-
## Styling
|
|
72
|
-
|
|
73
|
-
CSS is in `src/css/styles.css`. Requires a parent element with classes `pantsdown light` or `pantsdown dark` (optionally with `high-contrast`).
|