@ubean/markdown 0.1.1 → 0.1.3
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/dist/index.d.ts +45 -0
- package/dist/index.js +145 -0
- package/package.json +1 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
interface MarkdownFrontmatter {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
date?: string;
|
|
6
|
+
layout?: string | false;
|
|
7
|
+
path?: string;
|
|
8
|
+
seo?: Record<string, unknown>;
|
|
9
|
+
meta?: Record<string, unknown>;
|
|
10
|
+
head?: Record<string, unknown>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
interface ParsedMarkdown {
|
|
14
|
+
frontmatter: MarkdownFrontmatter;
|
|
15
|
+
content: string;
|
|
16
|
+
excerpt?: string;
|
|
17
|
+
headings: MarkdownHeading[];
|
|
18
|
+
html?: string;
|
|
19
|
+
}
|
|
20
|
+
interface MarkdownHeading {
|
|
21
|
+
level: number;
|
|
22
|
+
text: string;
|
|
23
|
+
id: string;
|
|
24
|
+
}
|
|
25
|
+
interface MarkdownOptions {
|
|
26
|
+
html?: boolean;
|
|
27
|
+
linkify?: boolean;
|
|
28
|
+
breaks?: boolean;
|
|
29
|
+
typographer?: boolean;
|
|
30
|
+
excerpt?: boolean;
|
|
31
|
+
excerptSeparator?: string;
|
|
32
|
+
headingIds?: boolean;
|
|
33
|
+
highlighter?: (code: string, lang: string) => string;
|
|
34
|
+
}
|
|
35
|
+
declare function parseFrontmatter(source: string): {
|
|
36
|
+
data: MarkdownFrontmatter;
|
|
37
|
+
content: string;
|
|
38
|
+
};
|
|
39
|
+
declare function markdownToHtml(markdown: string, options?: MarkdownOptions): string;
|
|
40
|
+
declare function extractHeadings(markdown: string): MarkdownHeading[];
|
|
41
|
+
declare function extractExcerpt(markdown: string, separator?: string): string | undefined;
|
|
42
|
+
declare function parseMarkdown(source: string, options?: MarkdownOptions): ParsedMarkdown;
|
|
43
|
+
declare function defineMarkdownPage(frontmatter: MarkdownFrontmatter & Record<string, unknown>): MarkdownFrontmatter & Record<string, unknown>;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { MarkdownFrontmatter, MarkdownHeading, MarkdownOptions, ParsedMarkdown, defineMarkdownPage, extractExcerpt, extractHeadings, markdownToHtml, parseFrontmatter, parseMarkdown };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createMarkdownExit } from "markdown-exit";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---\s*\n?/;
|
|
4
|
+
function parseFrontmatter(source) {
|
|
5
|
+
const match = source.match(FRONTMATTER_REGEX);
|
|
6
|
+
if (!match) return {
|
|
7
|
+
data: {},
|
|
8
|
+
content: source
|
|
9
|
+
};
|
|
10
|
+
const raw = match[1];
|
|
11
|
+
const content = source.slice(match[0].length);
|
|
12
|
+
return {
|
|
13
|
+
data: parseYamlSimple(raw),
|
|
14
|
+
content
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function parseYamlSimple(yaml) {
|
|
18
|
+
const result = {};
|
|
19
|
+
const lines = yaml.split("\n");
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
const trimmed = line.trimEnd();
|
|
22
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
23
|
+
const colonIdx = trimmed.indexOf(":");
|
|
24
|
+
if (colonIdx === -1) continue;
|
|
25
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
26
|
+
let value = trimmed.slice(colonIdx + 1).trim();
|
|
27
|
+
if (!key) continue;
|
|
28
|
+
if (value === "") value = true;
|
|
29
|
+
else if (value === "true" || value === "false") value = value === "true";
|
|
30
|
+
else if (value === "null" || value === "~") value = null;
|
|
31
|
+
else if (!isNaN(Number(value)) && value !== "") {
|
|
32
|
+
const num = Number(value);
|
|
33
|
+
if (isFinite(num)) value = num;
|
|
34
|
+
} else if (typeof value === "string" && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
35
|
+
else if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) value = value.slice(1, -1).split(",").map((v) => v.trim()).filter((v) => v !== "").map((v) => {
|
|
36
|
+
if (v === "true") return true;
|
|
37
|
+
if (v === "false") return false;
|
|
38
|
+
if (v === "null") return null;
|
|
39
|
+
if (!isNaN(Number(v))) return Number(v);
|
|
40
|
+
return v.replace(/^["']|["']$/g, "");
|
|
41
|
+
});
|
|
42
|
+
const nestedKeys = key.split(".");
|
|
43
|
+
let current = result;
|
|
44
|
+
for (let i = 0; i < nestedKeys.length - 1; i++) {
|
|
45
|
+
const k = nestedKeys[i];
|
|
46
|
+
if (!(k in current) || typeof current[k] !== "object") current[k] = {};
|
|
47
|
+
current = current[k];
|
|
48
|
+
}
|
|
49
|
+
current[nestedKeys[nestedKeys.length - 1]] = value;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function slugify(text) {
|
|
54
|
+
return text.toLowerCase().replace(/<[^>]*>/g, "").replace(/[^\w\s\u4e00-\u9fa5-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
55
|
+
}
|
|
56
|
+
function applyHeadingIds(md) {
|
|
57
|
+
const headingOpenRule = md.renderer.rules.heading_open;
|
|
58
|
+
md.renderer.rules.heading_open = (tokens, idx, options, env, self) => {
|
|
59
|
+
const token = tokens[idx];
|
|
60
|
+
const nextToken = tokens[idx + 1];
|
|
61
|
+
if (nextToken && nextToken.type === "inline") {
|
|
62
|
+
const text = nextToken.children?.filter((t) => t.type === "text" || t.type === "code_inline").map((t) => t.content).join("") || "";
|
|
63
|
+
token.attrSet("id", slugify(text));
|
|
64
|
+
}
|
|
65
|
+
if (headingOpenRule) return headingOpenRule(tokens, idx, options, env, self);
|
|
66
|
+
return self.renderToken(tokens, idx, options);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function createInstance(options) {
|
|
70
|
+
const md = createMarkdownExit({
|
|
71
|
+
html: options.html ?? false,
|
|
72
|
+
linkify: options.linkify ?? true,
|
|
73
|
+
breaks: options.breaks ?? false,
|
|
74
|
+
typographer: options.typographer ?? false,
|
|
75
|
+
highlight: options.highlighter ? (str, lang) => {
|
|
76
|
+
const result = options.highlighter(str.trimEnd(), lang);
|
|
77
|
+
if (result.startsWith("<pre")) return result;
|
|
78
|
+
return `<pre><code class="language-${lang}">${result}</code></pre>`;
|
|
79
|
+
} : void 0
|
|
80
|
+
});
|
|
81
|
+
if (options.headingIds !== false) applyHeadingIds(md);
|
|
82
|
+
return md;
|
|
83
|
+
}
|
|
84
|
+
function markdownToHtml(markdown, options = {}) {
|
|
85
|
+
return createInstance(options).render(markdown).trim();
|
|
86
|
+
}
|
|
87
|
+
function extractHeadings(markdown) {
|
|
88
|
+
const headings = [];
|
|
89
|
+
const tokens = createMarkdownExit({ html: false }).parse(markdown, {});
|
|
90
|
+
function walkTokenList(tokenList) {
|
|
91
|
+
for (let i = 0; i < tokenList.length; i++) {
|
|
92
|
+
const token = tokenList[i];
|
|
93
|
+
if (token.type === "heading_open") {
|
|
94
|
+
const level = parseInt(token.tag.slice(1), 10);
|
|
95
|
+
const inlineToken = tokenList[i + 1];
|
|
96
|
+
if (inlineToken && inlineToken.type === "inline") {
|
|
97
|
+
const text = extractTextFromTokens(inlineToken.children || []);
|
|
98
|
+
headings.push({
|
|
99
|
+
level,
|
|
100
|
+
text,
|
|
101
|
+
id: slugify(text)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (token.children) walkTokenList(token.children);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
walkTokenList(tokens);
|
|
109
|
+
return headings;
|
|
110
|
+
}
|
|
111
|
+
function extractTextFromTokens(tokens) {
|
|
112
|
+
let text = "";
|
|
113
|
+
for (const token of tokens) if (token.type === "text" || token.type === "code_inline") text += token.content;
|
|
114
|
+
else if (token.children) text += extractTextFromTokens(token.children);
|
|
115
|
+
return text;
|
|
116
|
+
}
|
|
117
|
+
function extractExcerpt(markdown, separator = "<!-- more -->") {
|
|
118
|
+
const idx = markdown.indexOf(separator);
|
|
119
|
+
if (idx !== -1) return markdown.slice(0, idx).trim();
|
|
120
|
+
const paragraphs = markdown.split("\n\n").filter((p) => p.trim() !== "");
|
|
121
|
+
for (const para of paragraphs) {
|
|
122
|
+
const trimmed = para.trim();
|
|
123
|
+
if (trimmed.startsWith("#")) continue;
|
|
124
|
+
if (trimmed.startsWith("```")) continue;
|
|
125
|
+
if (trimmed.startsWith(">")) continue;
|
|
126
|
+
if (trimmed.startsWith("- ") || trimmed.startsWith("* ") || /^\d+\.\s/.test(trimmed)) continue;
|
|
127
|
+
if (trimmed.length < 500) return trimmed;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function parseMarkdown(source, options = {}) {
|
|
131
|
+
const { data: frontmatter, content } = parseFrontmatter(source);
|
|
132
|
+
const headings = extractHeadings(content);
|
|
133
|
+
return {
|
|
134
|
+
frontmatter,
|
|
135
|
+
content,
|
|
136
|
+
excerpt: options.excerpt !== false ? extractExcerpt(content, options.excerptSeparator) : void 0,
|
|
137
|
+
headings,
|
|
138
|
+
html: markdownToHtml(content, options)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function defineMarkdownPage(frontmatter) {
|
|
142
|
+
return frontmatter;
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { defineMarkdownPage, extractExcerpt, extractHeadings, markdownToHtml, parseFrontmatter, parseMarkdown };
|