@linxiraos/pi-utils 1.0.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/CHANGELOG.md +449 -0
- package/README.md +36 -0
- package/dist/types/abortable.d.ts +32 -0
- package/dist/types/acp/connection.d.ts +118 -0
- package/dist/types/acp/protocol.d.ts +526 -0
- package/dist/types/acp/schema.d.ts +41 -0
- package/dist/types/acp/stream.d.ts +8 -0
- package/dist/types/acp/transport.d.ts +81 -0
- package/dist/types/acp.d.ts +6 -0
- package/dist/types/async.d.ts +20 -0
- package/dist/types/binary.d.ts +17 -0
- package/dist/types/browsers.d.ts +68 -0
- package/dist/types/chalk.d.ts +125 -0
- package/dist/types/cli.d.ts +134 -0
- package/dist/types/color.d.ts +102 -0
- package/dist/types/dates.d.ts +7 -0
- package/dist/types/dirs.d.ts +282 -0
- package/dist/types/docx/converter.d.ts +46 -0
- package/dist/types/docx/xml.d.ts +26 -0
- package/dist/types/docx/zip.d.ts +6 -0
- package/dist/types/docx.d.ts +11 -0
- package/dist/types/dom/core.d.ts +431 -0
- package/dist/types/dom/parser.d.ts +7 -0
- package/dist/types/dom/selector.d.ts +5 -0
- package/dist/types/dom.d.ts +5 -0
- package/dist/types/env.d.ts +124 -0
- package/dist/types/fetch-retry.d.ts +96 -0
- package/dist/types/file-lock.d.ts +23 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +46 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/headers.d.ts +34 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/json-parse.d.ts +74 -0
- package/dist/types/json.d.ts +14 -0
- package/dist/types/logger/rotating-file.d.ts +18 -0
- package/dist/types/logger.d.ts +96 -0
- package/dist/types/loop-phase.d.ts +10 -0
- package/dist/types/lru.d.ts +46 -0
- package/dist/types/marked/core.d.ts +445 -0
- package/dist/types/marked.d.ts +2 -0
- package/dist/types/mermaid-ascii.d.ts +11 -0
- package/dist/types/mime.d.ts +29 -0
- package/dist/types/module-timer.d.ts +1 -0
- package/dist/types/path-tree.d.ts +76 -0
- package/dist/types/path.d.ts +2 -0
- package/dist/types/peek-file.d.ts +29 -0
- package/dist/types/postmortem.d.ts +104 -0
- package/dist/types/process-name.d.ts +7 -0
- package/dist/types/procmgr.d.ts +74 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +110 -0
- package/dist/types/readability/readability.d.ts +9 -0
- package/dist/types/readability/readerable.d.ts +10 -0
- package/dist/types/readability/types.d.ts +70 -0
- package/dist/types/readability.d.ts +4 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/runtime-install.d.ts +78 -0
- package/dist/types/sanitize-text.d.ts +29 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stderr-guard.d.ts +22 -0
- package/dist/types/stream.d.ts +67 -0
- package/dist/types/tab-spacing.d.ts +24 -0
- package/dist/types/temp.d.ts +17 -0
- package/dist/types/template.d.ts +62 -0
- package/dist/types/timing-buffer.d.ts +22 -0
- package/dist/types/tls-fetch.d.ts +37 -0
- package/dist/types/turndown/gfm.d.ts +11 -0
- package/dist/types/turndown/html.d.ts +5 -0
- package/dist/types/turndown/service.d.ts +21 -0
- package/dist/types/turndown/types.d.ts +70 -0
- package/dist/types/turndown.d.ts +4 -0
- package/dist/types/type-guards.d.ts +5 -0
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
- package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
- package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
- package/dist/types/version.d.ts +18 -0
- package/dist/types/vterm/buffer.d.ts +99 -0
- package/dist/types/vterm/terminal.d.ts +44 -0
- package/dist/types/vterm.d.ts +8 -0
- package/dist/types/which.d.ts +37 -0
- package/dist/types/worker-host.d.ts +51 -0
- package/dist/types/xml.d.ts +31 -0
- package/package.json +59 -0
- package/src/abortable.ts +109 -0
- package/src/acp/connection.ts +344 -0
- package/src/acp/protocol.ts +466 -0
- package/src/acp/schema.ts +160 -0
- package/src/acp/stream.ts +82 -0
- package/src/acp/transport.ts +213 -0
- package/src/acp.ts +6 -0
- package/src/async.ts +91 -0
- package/src/binary.ts +50 -0
- package/src/browsers.ts +501 -0
- package/src/chalk.ts +312 -0
- package/src/cli.ts +529 -0
- package/src/color.ts +302 -0
- package/src/dates.ts +194 -0
- package/src/dirs.ts +1026 -0
- package/src/docx/converter.ts +681 -0
- package/src/docx/xml.ts +166 -0
- package/src/docx/zip.ts +87 -0
- package/src/docx.ts +20 -0
- package/src/dom/core.ts +1254 -0
- package/src/dom/parser.ts +370 -0
- package/src/dom/selector.ts +290 -0
- package/src/dom.ts +33 -0
- package/src/env.ts +400 -0
- package/src/fetch-retry.ts +379 -0
- package/src/file-lock.ts +69 -0
- package/src/format.ts +113 -0
- package/src/frontmatter.ts +205 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/headers.ts +167 -0
- package/src/index.ts +59 -0
- package/src/json-parse.ts +821 -0
- package/src/json.ts +23 -0
- package/src/logger/rotating-file.ts +149 -0
- package/src/logger.ts +798 -0
- package/src/loop-phase.ts +49 -0
- package/src/lru.ts +185 -0
- package/src/marked/core.ts +1576 -0
- package/src/marked.ts +2 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mime.ts +159 -0
- package/src/module-timer.ts +148 -0
- package/src/path-tree.ts +147 -0
- package/src/path.ts +28 -0
- package/src/peek-file.ts +188 -0
- package/src/postmortem.ts +433 -0
- package/src/process-name.ts +57 -0
- package/src/procmgr.ts +247 -0
- package/src/prompt.ts +545 -0
- package/src/ptree.ts +412 -0
- package/src/readability/readability.ts +533 -0
- package/src/readability/readerable.ts +51 -0
- package/src/readability/types.ts +72 -0
- package/src/readability.ts +11 -0
- package/src/ring.ts +169 -0
- package/src/runtime-install.ts +393 -0
- package/src/sanitize-text.ts +96 -0
- package/src/snowflake.ts +121 -0
- package/src/stderr-guard.ts +155 -0
- package/src/stream.ts +454 -0
- package/src/tab-spacing.ts +336 -0
- package/src/temp.ts +134 -0
- package/src/template.ts +586 -0
- package/src/timing-buffer.ts +47 -0
- package/src/tls-fetch.ts +178 -0
- package/src/turndown/gfm.ts +106 -0
- package/src/turndown/html.ts +257 -0
- package/src/turndown/service.ts +334 -0
- package/src/turndown/types.ts +81 -0
- package/src/turndown.ts +5 -0
- package/src/type-guards.ts +17 -0
- package/src/vendor/mermaid-ascii/NOTICE +33 -0
- package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
- package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
- package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
- package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
- package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
- package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
- package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
- package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
- package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
- package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
- package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
- package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
- package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
- package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
- package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
- package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
- package/src/vendor/mermaid-ascii/class/types.ts +121 -0
- package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
- package/src/vendor/mermaid-ascii/er/types.ts +91 -0
- package/src/vendor/mermaid-ascii/index.ts +14 -0
- package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
- package/src/vendor/mermaid-ascii/parser.ts +645 -0
- package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
- package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
- package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
- package/src/vendor/mermaid-ascii/types.ts +164 -0
- package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
- package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
- package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
- package/src/version.ts +99 -0
- package/src/vterm/buffer.ts +218 -0
- package/src/vterm/terminal.ts +773 -0
- package/src/vterm.ts +8 -0
- package/src/which.ts +234 -0
- package/src/worker-host.ts +100 -0
- package/src/xml.ts +313 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { parseHtmlFragment, serializeNode } from "./html";
|
|
2
|
+
import type {
|
|
3
|
+
ResolvedTurndownOptions,
|
|
4
|
+
RuleFilter,
|
|
5
|
+
TurndownNode,
|
|
6
|
+
TurndownOptions,
|
|
7
|
+
TurndownPlugin,
|
|
8
|
+
TurndownRule,
|
|
9
|
+
} from "./types";
|
|
10
|
+
|
|
11
|
+
const BLOCK_ELEMENTS: Readonly<Record<string, true>> = {
|
|
12
|
+
ADDRESS: true,
|
|
13
|
+
ARTICLE: true,
|
|
14
|
+
ASIDE: true,
|
|
15
|
+
BLOCKQUOTE: true,
|
|
16
|
+
BODY: true,
|
|
17
|
+
CANVAS: true,
|
|
18
|
+
CENTER: true,
|
|
19
|
+
DD: true,
|
|
20
|
+
DETAILS: true,
|
|
21
|
+
DIR: true,
|
|
22
|
+
DIV: true,
|
|
23
|
+
DL: true,
|
|
24
|
+
DT: true,
|
|
25
|
+
FIELDSET: true,
|
|
26
|
+
FIGCAPTION: true,
|
|
27
|
+
FIGURE: true,
|
|
28
|
+
FOOTER: true,
|
|
29
|
+
FORM: true,
|
|
30
|
+
FRAMESET: true,
|
|
31
|
+
H1: true,
|
|
32
|
+
H2: true,
|
|
33
|
+
H3: true,
|
|
34
|
+
H4: true,
|
|
35
|
+
H5: true,
|
|
36
|
+
H6: true,
|
|
37
|
+
HEADER: true,
|
|
38
|
+
HGROUP: true,
|
|
39
|
+
HR: true,
|
|
40
|
+
HTML: true,
|
|
41
|
+
ISINDEX: true,
|
|
42
|
+
LI: true,
|
|
43
|
+
MAIN: true,
|
|
44
|
+
MENU: true,
|
|
45
|
+
NAV: true,
|
|
46
|
+
NOFRAMES: true,
|
|
47
|
+
NOSCRIPT: true,
|
|
48
|
+
OL: true,
|
|
49
|
+
OUTPUT: true,
|
|
50
|
+
P: true,
|
|
51
|
+
PRE: true,
|
|
52
|
+
SECTION: true,
|
|
53
|
+
SUMMARY: true,
|
|
54
|
+
TABLE: true,
|
|
55
|
+
TBODY: true,
|
|
56
|
+
TD: true,
|
|
57
|
+
TFOOT: true,
|
|
58
|
+
TH: true,
|
|
59
|
+
THEAD: true,
|
|
60
|
+
TR: true,
|
|
61
|
+
UL: true,
|
|
62
|
+
};
|
|
63
|
+
const NONBLANK_EMPTY_ELEMENTS: Readonly<Record<string, true>> = {
|
|
64
|
+
A: true,
|
|
65
|
+
AUDIO: true,
|
|
66
|
+
BR: true,
|
|
67
|
+
HR: true,
|
|
68
|
+
IFRAME: true,
|
|
69
|
+
IMG: true,
|
|
70
|
+
INPUT: true,
|
|
71
|
+
SCRIPT: true,
|
|
72
|
+
SOURCE: true,
|
|
73
|
+
TD: true,
|
|
74
|
+
TH: true,
|
|
75
|
+
VIDEO: true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type RuleEntry = { key: string; rule: TurndownRule };
|
|
79
|
+
type Reference = { destination: string };
|
|
80
|
+
|
|
81
|
+
const DEFAULT_OPTIONS: ResolvedTurndownOptions = {
|
|
82
|
+
headingStyle: "setext",
|
|
83
|
+
hr: "* * *",
|
|
84
|
+
bulletListMarker: "*",
|
|
85
|
+
codeBlockStyle: "indented",
|
|
86
|
+
fence: "```",
|
|
87
|
+
emDelimiter: "_",
|
|
88
|
+
strongDelimiter: "**",
|
|
89
|
+
linkStyle: "inlined",
|
|
90
|
+
linkReferenceStyle: "full",
|
|
91
|
+
preformattedCode: false,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function matchesFilter(filter: RuleFilter, node: TurndownNode, options: ResolvedTurndownOptions): boolean {
|
|
95
|
+
if (typeof filter === "function") return filter(node, options);
|
|
96
|
+
const name = node.nodeName.toLowerCase();
|
|
97
|
+
if (typeof filter === "string") return name === filter.toLowerCase();
|
|
98
|
+
return filter.some(tag => name === tag.toLowerCase());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function contentWithFlankingWhitespace(content: string, delimiter: string): string {
|
|
102
|
+
const leading = content.match(/^\s+/)?.[0] ?? "";
|
|
103
|
+
const trailing = content.match(/\s+$/)?.[0] ?? "";
|
|
104
|
+
const body = content.slice(leading.length, trailing ? -trailing.length : undefined);
|
|
105
|
+
return body ? `${leading}${delimiter}${body}${delimiter}${trailing}` : "";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function languageFromCode(node: TurndownNode): string {
|
|
109
|
+
const code = Array.from(node.children).find(child => child.nodeName === "CODE");
|
|
110
|
+
const className = code?.getAttribute("class") ?? "";
|
|
111
|
+
return /(?:^|\s)language-([^\s]+)/.exec(className)?.[1] ?? "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function listItemPrefix(node: TurndownNode, options: ResolvedTurndownOptions): string {
|
|
115
|
+
const parent = node.parentNode;
|
|
116
|
+
if (parent?.nodeName !== "OL") return `${options.bulletListMarker} `;
|
|
117
|
+
const start = Number(parent.getAttribute("start") ?? "1");
|
|
118
|
+
const siblings = Array.from(parent.children);
|
|
119
|
+
return `${(Number.isFinite(start) ? start : 1) + siblings.indexOf(node)}. `;
|
|
120
|
+
}
|
|
121
|
+
function hasNonblankDescendant(node: TurndownNode): boolean {
|
|
122
|
+
for (const child of Array.from(node.children)) {
|
|
123
|
+
if (NONBLANK_EMPTY_ELEMENTS[child.nodeName] || hasNonblankDescendant(child)) return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Convert HTML fragments to Markdown with extensible Turndown-compatible rules. */
|
|
129
|
+
export default class TurndownService {
|
|
130
|
+
readonly options: ResolvedTurndownOptions;
|
|
131
|
+
readonly #rules: RuleEntry[] = [];
|
|
132
|
+
readonly #keepFilters: RuleFilter[] = [];
|
|
133
|
+
readonly #removeFilters: RuleFilter[] = [];
|
|
134
|
+
#references: Reference[] = [];
|
|
135
|
+
#previousSourceWhitespace = false;
|
|
136
|
+
|
|
137
|
+
constructor(options: TurndownOptions = {}) {
|
|
138
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Install a highest-priority named conversion rule. */
|
|
142
|
+
addRule(key: string, rule: TurndownRule): this {
|
|
143
|
+
const previous = this.#rules.findIndex(entry => entry.key === key);
|
|
144
|
+
if (previous >= 0) this.#rules.splice(previous, 1);
|
|
145
|
+
this.#rules.unshift({ key, rule });
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Preserve matching elements as HTML when no custom rule handles them. */
|
|
150
|
+
keep(filter: RuleFilter): this {
|
|
151
|
+
this.#keepFilters.unshift(filter);
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Drop matching elements and all of their converted content. */
|
|
156
|
+
remove(filter: RuleFilter): this {
|
|
157
|
+
this.#removeFilters.unshift(filter);
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Install one plugin or an ordered list of plugins. */
|
|
162
|
+
use(plugin: TurndownPlugin | readonly TurndownPlugin[]): this {
|
|
163
|
+
if (typeof plugin === "function") {
|
|
164
|
+
plugin(this);
|
|
165
|
+
} else {
|
|
166
|
+
for (const install of plugin) install(this);
|
|
167
|
+
}
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Escape Markdown punctuation using Turndown's public escaping rules. */
|
|
172
|
+
escape(text: string): string {
|
|
173
|
+
return text
|
|
174
|
+
.replace(/\\/g, "\\\\")
|
|
175
|
+
.replace(/([*_[\]])/g, "\\$1")
|
|
176
|
+
.replace(/^(\s*)(#{1,6}|[+>])(?=\s)/gm, "$1\\$2")
|
|
177
|
+
.replace(/^(\s*)-(?=\s)/gm, "$1\\-")
|
|
178
|
+
.replace(/^(\s*\d+)\.(?=\s)/gm, "$1\\.");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Convert an HTML string or standards-shaped DOM node to Markdown. */
|
|
182
|
+
turndown(input: string | TurndownNode): string {
|
|
183
|
+
const root = typeof input === "string" ? parseHtmlFragment(input) : input;
|
|
184
|
+
this.#references = [];
|
|
185
|
+
this.#previousSourceWhitespace = false;
|
|
186
|
+
let markdown =
|
|
187
|
+
root.nodeType === 9 || root.nodeType === 11 ? this.#convertChildren(root) : this.#convertNode(root);
|
|
188
|
+
markdown = markdown
|
|
189
|
+
.replace(/^[\t\r\n]+/, "")
|
|
190
|
+
.replace(/[\t\r\n ]+$/, "")
|
|
191
|
+
.replace(/\n{3,}/g, "\n\n");
|
|
192
|
+
if (this.#references.length > 0) {
|
|
193
|
+
markdown += `\n\n${this.#references.map(reference => reference.destination).join("\n")}`;
|
|
194
|
+
}
|
|
195
|
+
return markdown;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Convert only a node's children within the active conversion. */
|
|
199
|
+
convertChildren(node: TurndownNode): string {
|
|
200
|
+
return this.#convertChildren(node);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#convertChildren(node: TurndownNode): string {
|
|
204
|
+
let content = "";
|
|
205
|
+
for (const child of Array.from(node.childNodes)) content += this.#convertNode(child);
|
|
206
|
+
return content;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#convertNode(node: TurndownNode): string {
|
|
210
|
+
if (node.nodeType === 3) {
|
|
211
|
+
const text = node.textContent ?? "";
|
|
212
|
+
if (node.parentNode?.nodeName === "PRE") return text;
|
|
213
|
+
let collapsed = text.replace(/[\t\r\n\f ]+/g, " ");
|
|
214
|
+
if (this.#previousSourceWhitespace && collapsed.startsWith(" ")) collapsed = collapsed.slice(1);
|
|
215
|
+
this.#previousSourceWhitespace = collapsed.endsWith(" ");
|
|
216
|
+
return node.parentNode?.nodeName === "CODE" ? collapsed : this.escape(collapsed);
|
|
217
|
+
}
|
|
218
|
+
if (node.nodeType !== 1) return this.#convertChildren(node);
|
|
219
|
+
if (!(node.textContent ?? "").trim() && !NONBLANK_EMPTY_ELEMENTS[node.nodeName] && !hasNonblankDescendant(node)) {
|
|
220
|
+
return (
|
|
221
|
+
this.options.blankReplacement?.("", node, this.options) ?? (BLOCK_ELEMENTS[node.nodeName] ? "\n\n" : "")
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const block = Boolean(BLOCK_ELEMENTS[node.nodeName]);
|
|
226
|
+
if (block) this.#previousSourceWhitespace = false;
|
|
227
|
+
const content = this.#convertChildren(node);
|
|
228
|
+
if (block) this.#previousSourceWhitespace = false;
|
|
229
|
+
if (node.nodeName === "CODE") this.#previousSourceWhitespace = false;
|
|
230
|
+
const custom = this.#rules.find(entry => matchesFilter(entry.rule.filter, node, this.options));
|
|
231
|
+
if (custom) return custom.rule.replacement(content, node, this.options);
|
|
232
|
+
if (this.#keepFilters.some(filter => matchesFilter(filter, node, this.options))) {
|
|
233
|
+
return this.options.keepReplacement?.(content, node, this.options) ?? serializeNode(node);
|
|
234
|
+
}
|
|
235
|
+
if (this.#removeFilters.some(filter => matchesFilter(filter, node, this.options))) return "";
|
|
236
|
+
return this.#defaultReplacement(content, node);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#defaultReplacement(content: string, node: TurndownNode): string {
|
|
240
|
+
const name = node.nodeName;
|
|
241
|
+
if (name === "P") return content.trim() ? `\n\n${content.trim()}\n\n` : "";
|
|
242
|
+
if (/^H[1-6]$/.test(name)) {
|
|
243
|
+
const level = Number(name.charAt(1));
|
|
244
|
+
const body = content.trim();
|
|
245
|
+
if (this.options.headingStyle === "setext" && level < 3) {
|
|
246
|
+
return `\n\n${body}\n${(level === 1 ? "=" : "-").repeat(body.length)}\n\n`;
|
|
247
|
+
}
|
|
248
|
+
return `\n\n${"#".repeat(level)} ${body}\n\n`;
|
|
249
|
+
}
|
|
250
|
+
if (name === "BR") return " \n";
|
|
251
|
+
if (name === "HR") return `\n\n${this.options.hr}\n\n`;
|
|
252
|
+
if (name === "BLOCKQUOTE") {
|
|
253
|
+
const body = content
|
|
254
|
+
.trim()
|
|
255
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
256
|
+
.replace(/^/gm, "> ");
|
|
257
|
+
return body ? `\n\n${body}\n\n` : "";
|
|
258
|
+
}
|
|
259
|
+
if (name === "UL" || name === "OL") {
|
|
260
|
+
const body = content.replace(/^\n+|\n+$/g, "");
|
|
261
|
+
return node.parentNode?.nodeName === "LI" ? `\n${body}` : `\n\n${body}\n\n`;
|
|
262
|
+
}
|
|
263
|
+
if (name === "LI") {
|
|
264
|
+
const body = content.replace(/^\n+/, "").replace(/\n+$/, "\n").replace(/\n/gm, "\n ");
|
|
265
|
+
return `${listItemPrefix(node, this.options)}${body}${node.nextSibling ? "\n" : ""}`;
|
|
266
|
+
}
|
|
267
|
+
if (name === "PRE" && Array.from(node.children).some(child => child.nodeName === "CODE")) {
|
|
268
|
+
return this.#replacePre(node);
|
|
269
|
+
}
|
|
270
|
+
if (name === "EM" || name === "I") return contentWithFlankingWhitespace(content, this.options.emDelimiter);
|
|
271
|
+
if (name === "STRONG" || name === "B") {
|
|
272
|
+
return contentWithFlankingWhitespace(content, this.options.strongDelimiter);
|
|
273
|
+
}
|
|
274
|
+
if (name === "CODE") return this.#replaceInlineCode(content);
|
|
275
|
+
if (name === "A") return this.#replaceLink(content, node);
|
|
276
|
+
if (name === "IMG") return this.#replaceImage(node);
|
|
277
|
+
if (this.options.defaultReplacement) return this.options.defaultReplacement(content, node, this.options);
|
|
278
|
+
if (BLOCK_ELEMENTS[name]) return content.trim() ? `\n\n${content.trim()}\n\n` : "";
|
|
279
|
+
return content;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#replacePre(node: TurndownNode): string {
|
|
283
|
+
const text = node.textContent ?? "";
|
|
284
|
+
if (this.options.codeBlockStyle === "indented") {
|
|
285
|
+
return `\n\n${text.replace(/^/gm, " ")}\n\n`;
|
|
286
|
+
}
|
|
287
|
+
const fenceCharacter = this.options.fence.charAt(0);
|
|
288
|
+
let longestFence = this.options.fence.length;
|
|
289
|
+
let run = 0;
|
|
290
|
+
for (const character of text) {
|
|
291
|
+
run = character === fenceCharacter ? run + 1 : 0;
|
|
292
|
+
longestFence = Math.max(longestFence, run + 1);
|
|
293
|
+
}
|
|
294
|
+
const fence = fenceCharacter.repeat(longestFence);
|
|
295
|
+
const body = text.replace(/\n$/, "");
|
|
296
|
+
return `\n\n${fence}${languageFromCode(node)}\n${body}\n${fence}\n\n`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
#replaceInlineCode(text: string): string {
|
|
300
|
+
const body = text.trim();
|
|
301
|
+
if (!body) return "";
|
|
302
|
+
let longestRun = 0;
|
|
303
|
+
let run = 0;
|
|
304
|
+
for (const character of body) {
|
|
305
|
+
run = character === "`" ? run + 1 : 0;
|
|
306
|
+
longestRun = Math.max(longestRun, run);
|
|
307
|
+
}
|
|
308
|
+
const delimiter = "`".repeat(longestRun + 1);
|
|
309
|
+
const padding = /^`|`$/.test(body) ? " " : "";
|
|
310
|
+
return `${delimiter}${padding}${body}${padding}${delimiter}`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#replaceLink(content: string, node: TurndownNode): string {
|
|
314
|
+
const href = (node.getAttribute("href") ?? "").replace(/([()<>])/g, "\\$1");
|
|
315
|
+
const title = node.getAttribute("title");
|
|
316
|
+
const destination = `${href}${title ? ` "${title.replace(/"/g, '\\"')}"` : ""}`;
|
|
317
|
+
if (this.options.linkStyle !== "referenced") return `[${content}](${destination})`;
|
|
318
|
+
const referenceNumber = this.#references.length + 1;
|
|
319
|
+
let label = String(referenceNumber);
|
|
320
|
+
if (this.options.linkReferenceStyle === "collapsed") label = "";
|
|
321
|
+
if (this.options.linkReferenceStyle === "shortcut") label = content;
|
|
322
|
+
const marker = this.options.linkReferenceStyle === "shortcut" ? `[${content}]` : `[${content}][${label}]`;
|
|
323
|
+
this.#references.push({ destination: `[${label || content}]: ${destination}` });
|
|
324
|
+
return marker;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#replaceImage(node: TurndownNode): string {
|
|
328
|
+
const source = (node.getAttribute("src") ?? "").replace(/([()<>])/g, "\\$1");
|
|
329
|
+
if (!source) return "";
|
|
330
|
+
const alternative = this.escape(node.getAttribute("alt") ?? "");
|
|
331
|
+
const title = node.getAttribute("title");
|
|
332
|
+
return `}"` : ""})`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Behavior-compatible reimplementation of turndown's used surface. */
|
|
2
|
+
|
|
3
|
+
/** A DOM-shaped node accepted by conversion rules. */
|
|
4
|
+
export interface TurndownNode {
|
|
5
|
+
readonly nodeType: number;
|
|
6
|
+
readonly nodeName: string;
|
|
7
|
+
readonly parentNode: TurndownNode | null;
|
|
8
|
+
readonly childNodes: ArrayLike<TurndownNode>;
|
|
9
|
+
readonly children: ArrayLike<TurndownNode>;
|
|
10
|
+
readonly firstChild: TurndownNode | null;
|
|
11
|
+
readonly lastChild: TurndownNode | null;
|
|
12
|
+
readonly nextSibling: TurndownNode | null;
|
|
13
|
+
readonly previousSibling: TurndownNode | null;
|
|
14
|
+
readonly textContent: string | null;
|
|
15
|
+
readonly outerHTML?: string;
|
|
16
|
+
getAttribute(name: string): string | null;
|
|
17
|
+
hasAttribute(name: string): boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Options supported by the HTML-to-Markdown converter. */
|
|
21
|
+
export interface TurndownOptions {
|
|
22
|
+
headingStyle?: "setext" | "atx";
|
|
23
|
+
hr?: string;
|
|
24
|
+
bulletListMarker?: "*" | "-" | "+";
|
|
25
|
+
codeBlockStyle?: "indented" | "fenced";
|
|
26
|
+
fence?: string;
|
|
27
|
+
emDelimiter?: "_" | "*";
|
|
28
|
+
strongDelimiter?: "**" | "__";
|
|
29
|
+
linkStyle?: "inlined" | "referenced";
|
|
30
|
+
linkReferenceStyle?: "full" | "collapsed" | "shortcut";
|
|
31
|
+
preformattedCode?: boolean;
|
|
32
|
+
blankReplacement?: ReplacementFunction;
|
|
33
|
+
keepReplacement?: ReplacementFunction;
|
|
34
|
+
defaultReplacement?: ReplacementFunction;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Fully resolved options passed to replacement callbacks. */
|
|
38
|
+
export interface ResolvedTurndownOptions {
|
|
39
|
+
headingStyle: "setext" | "atx";
|
|
40
|
+
hr: string;
|
|
41
|
+
bulletListMarker: "*" | "-" | "+";
|
|
42
|
+
codeBlockStyle: "indented" | "fenced";
|
|
43
|
+
fence: string;
|
|
44
|
+
emDelimiter: "_" | "*";
|
|
45
|
+
strongDelimiter: "**" | "__";
|
|
46
|
+
linkStyle: "inlined" | "referenced";
|
|
47
|
+
linkReferenceStyle: "full" | "collapsed" | "shortcut";
|
|
48
|
+
preformattedCode: boolean;
|
|
49
|
+
blankReplacement?: ReplacementFunction;
|
|
50
|
+
keepReplacement?: ReplacementFunction;
|
|
51
|
+
defaultReplacement?: ReplacementFunction;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A tag name, list of tag names, or predicate selecting nodes for a rule. */
|
|
55
|
+
export type RuleFilter =
|
|
56
|
+
| string
|
|
57
|
+
| readonly string[]
|
|
58
|
+
| ((node: TurndownNode, options: ResolvedTurndownOptions) => boolean);
|
|
59
|
+
|
|
60
|
+
/** Produces Markdown for a matched node and its already-converted children. */
|
|
61
|
+
export type ReplacementFunction = (content: string, node: TurndownNode, options: ResolvedTurndownOptions) => string;
|
|
62
|
+
|
|
63
|
+
/** A named conversion rule accepted by `addRule`. */
|
|
64
|
+
export interface TurndownRule {
|
|
65
|
+
filter: RuleFilter;
|
|
66
|
+
replacement: ReplacementFunction;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A plugin that installs one or more conversion rules. */
|
|
70
|
+
export type TurndownPlugin = (service: TurndownServiceLike) => void;
|
|
71
|
+
|
|
72
|
+
/** Structural service surface available to plugins. */
|
|
73
|
+
export interface TurndownServiceLike {
|
|
74
|
+
readonly options: ResolvedTurndownOptions;
|
|
75
|
+
addRule(key: string, rule: TurndownRule): this;
|
|
76
|
+
convertChildren(node: TurndownNode): string;
|
|
77
|
+
keep(filter: RuleFilter): this;
|
|
78
|
+
remove(filter: RuleFilter): this;
|
|
79
|
+
turndown(input: string | TurndownNode): string;
|
|
80
|
+
escape(text: string): string;
|
|
81
|
+
}
|
package/src/turndown.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
2
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/** Reads an own string-valued property without invoking accessors. */
|
|
6
|
+
export function stringProperty(value: object, key: string): string | undefined {
|
|
7
|
+
const field = Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
8
|
+
return typeof field === "string" ? field : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
12
|
+
return isRecord(value) ? value : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function toError(value: unknown): Error {
|
|
16
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
17
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
This directory contains an in-house Mermaid-diagram-to-ASCII renderer adapted
|
|
2
|
+
from beautiful-mermaid (https://github.com/lukilabs/beautiful-mermaid), used
|
|
3
|
+
under the MIT License.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Craft Docs
|
|
6
|
+
|
|
7
|
+
Only the ASCII rendering pipeline is ported (flowchart/state, sequence, class,
|
|
8
|
+
ER, and xychart diagrams); the SVG renderer and its `elkjs` graph-layout
|
|
9
|
+
dependency, the browser entry point, and the SVG theme/style modules were
|
|
10
|
+
dropped. Terminal display width is reimplemented on `Bun.stringWidth`, and
|
|
11
|
+
inline label formatting (HTML tags, markdown emphasis) is reduced to plain text
|
|
12
|
+
for ASCII output. Layout and edge-routing logic is preserved faithfully so
|
|
13
|
+
ASCII output matches the upstream package.
|
|
14
|
+
|
|
15
|
+
MIT License
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|