@jxsuite/parser 0.23.2 → 0.25.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/package.json +11 -9
- package/src/ContentCollection.class.json +2 -1
- package/src/ContentEntry.class.json +1 -1
- package/src/{content.js → content.ts} +59 -38
- package/src/{html-to-jx.js → html-to-jx.ts} +14 -17
- package/src/{md.js → md.ts} +81 -37
- package/src/{transpile.js → transpile.ts} +47 -63
- package/src/types.ts +65 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Jx markdown parser and external class integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,29 +13,31 @@
|
|
|
13
13
|
],
|
|
14
14
|
"type": "module",
|
|
15
15
|
"exports": {
|
|
16
|
-
".": "./src/md.
|
|
17
|
-
"./content": "./src/content.
|
|
16
|
+
".": "./src/md.ts",
|
|
17
|
+
"./content": "./src/content.ts",
|
|
18
18
|
"./ContentCollection.class.json": "./src/ContentCollection.class.json",
|
|
19
19
|
"./ContentEntry.class.json": "./src/ContentEntry.class.json",
|
|
20
20
|
"./MarkdownCollection.class.json": "./src/MarkdownCollection.class.json",
|
|
21
21
|
"./MarkdownFile.class.json": "./src/MarkdownFile.class.json",
|
|
22
|
-
"./transpile": "./src/transpile.
|
|
23
|
-
"./html-to-jx": "./src/html-to-jx.
|
|
22
|
+
"./transpile": "./src/transpile.ts",
|
|
23
|
+
"./html-to-jx": "./src/html-to-jx.ts",
|
|
24
|
+
"./types": "./src/types.ts"
|
|
24
25
|
},
|
|
25
26
|
"publishConfig": {
|
|
26
27
|
"provenance": true
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
|
-
"test": "bun test",
|
|
30
|
-
"test:coverage": "bun test --coverage",
|
|
30
|
+
"test": "bun test --isolate",
|
|
31
|
+
"test:coverage": "bun test --isolate --coverage",
|
|
31
32
|
"upgrade": "bunx npm-check-updates -u && bun install"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
35
|
+
"@jxsuite/schema": "^0.25.0",
|
|
34
36
|
"glob": "^13.0.6",
|
|
35
37
|
"hast-util-from-html": "^2.0.3",
|
|
36
38
|
"hast-util-whitespace": "^3.0.0",
|
|
37
39
|
"mdast-util-to-string": "^4.0.0",
|
|
38
|
-
"property-information": "^7.
|
|
40
|
+
"property-information": "^7.2.0",
|
|
39
41
|
"rehype-stringify": "^10.0.1",
|
|
40
42
|
"remark-directive": "^4.0.0",
|
|
41
43
|
"remark-frontmatter": "^5.0.0",
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
52
|
"@happy-dom/global-registrator": "^20.9.0",
|
|
51
|
-
"@jxsuite/runtime": "^0.
|
|
53
|
+
"@jxsuite/runtime": "^0.25.0",
|
|
52
54
|
"@types/hast": "^3.0.4"
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "Query a project content type with filter, sort, and limit",
|
|
5
5
|
"$prototype": "Class",
|
|
6
6
|
"extends": "Object",
|
|
7
|
-
"$implementation": "./content.
|
|
7
|
+
"$implementation": "./content.ts",
|
|
8
8
|
|
|
9
9
|
"$defs": {
|
|
10
10
|
"parameters": {
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"scope": "instance",
|
|
106
106
|
"identifier": "resolve",
|
|
107
107
|
"parameters": [],
|
|
108
|
+
"returns": { "type": "array" },
|
|
108
109
|
"description": "Query the content type entries with filter, sort, and limit"
|
|
109
110
|
}
|
|
110
111
|
}
|
|
@@ -6,6 +6,26 @@
|
|
|
6
6
|
* queryContentType, findEntry) used by both the classes and the server endpoint.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { ContentLoaderEntry } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
interface CollectionConfig {
|
|
12
|
+
contentType?: string;
|
|
13
|
+
filter?: unknown;
|
|
14
|
+
sort?: unknown;
|
|
15
|
+
limit?: number;
|
|
16
|
+
_project?: { contentTypes?: Map<string, ContentLoaderEntry[]>; [k: string]: unknown };
|
|
17
|
+
[k: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface EntryConfig {
|
|
21
|
+
contentType?: string;
|
|
22
|
+
id?: unknown;
|
|
23
|
+
field?: string;
|
|
24
|
+
_project?: { contentTypes?: Map<string, ContentLoaderEntry[]>; [k: string]: unknown };
|
|
25
|
+
_document?: { route?: { _pathParams?: Record<string, string> }; [k: string]: unknown };
|
|
26
|
+
[k: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
// ─── Query Functions ────────────────────────────────────────────────────────
|
|
10
30
|
|
|
11
31
|
/**
|
|
@@ -15,7 +35,10 @@
|
|
|
15
35
|
* @param {ContentLoaderEntry} entry
|
|
16
36
|
* @returns {boolean}
|
|
17
37
|
*/
|
|
18
|
-
export function evaluateFilterRule(
|
|
38
|
+
export function evaluateFilterRule(
|
|
39
|
+
rule: { field: string; op: string; value?: unknown },
|
|
40
|
+
entry: ContentLoaderEntry,
|
|
41
|
+
) {
|
|
19
42
|
const actual = rule.field === "id" ? entry.id : entry.data[rule.field];
|
|
20
43
|
switch (rule.op) {
|
|
21
44
|
case "==":
|
|
@@ -31,13 +54,13 @@ export function evaluateFilterRule(rule, entry) {
|
|
|
31
54
|
case "not contains":
|
|
32
55
|
return typeof actual === "string" && !actual.includes(String(rule.value ?? ""));
|
|
33
56
|
case ">":
|
|
34
|
-
return
|
|
57
|
+
return Number(actual) > Number(rule.value);
|
|
35
58
|
case "<":
|
|
36
|
-
return
|
|
59
|
+
return Number(actual) < Number(rule.value);
|
|
37
60
|
case ">=":
|
|
38
|
-
return
|
|
61
|
+
return Number(actual) >= Number(rule.value);
|
|
39
62
|
case "<=":
|
|
40
|
-
return
|
|
63
|
+
return Number(actual) <= Number(rule.value);
|
|
41
64
|
default:
|
|
42
65
|
return true;
|
|
43
66
|
}
|
|
@@ -54,13 +77,19 @@ export function evaluateFilterRule(rule, entry) {
|
|
|
54
77
|
* }} [query]
|
|
55
78
|
* @returns {ContentLoaderEntry[]} Filtered, sorted, limited entries
|
|
56
79
|
*/
|
|
57
|
-
export function queryContentType(
|
|
80
|
+
export function queryContentType(
|
|
81
|
+
entries: ContentLoaderEntry[],
|
|
82
|
+
query: {
|
|
83
|
+
filter?: Record<string, unknown> | Array<{ field: string; op: string; value?: unknown }>;
|
|
84
|
+
sort?: { field: string; order?: string } | Array<{ field: string; order?: string }>;
|
|
85
|
+
limit?: number;
|
|
86
|
+
} = {},
|
|
87
|
+
) {
|
|
58
88
|
let result = [...entries];
|
|
59
89
|
|
|
60
90
|
// Filter
|
|
61
91
|
if (query.filter) {
|
|
62
|
-
|
|
63
|
-
let rules;
|
|
92
|
+
let rules: { field: string; op: string; value?: unknown }[];
|
|
64
93
|
if (Array.isArray(query.filter)) {
|
|
65
94
|
rules = query.filter;
|
|
66
95
|
} else if (typeof query.filter === "object") {
|
|
@@ -76,8 +105,8 @@ export function queryContentType(entries, query = {}) {
|
|
|
76
105
|
const sortRules = Array.isArray(query.sort) ? query.sort : [query.sort];
|
|
77
106
|
result.sort((a, b) => {
|
|
78
107
|
for (const { field, order = "asc" } of sortRules) {
|
|
79
|
-
const aVal =
|
|
80
|
-
const bVal =
|
|
108
|
+
const aVal = field === "id" ? a.id : ((a.data[field] ?? "") as string | number);
|
|
109
|
+
const bVal = field === "id" ? b.id : ((b.data[field] ?? "") as string | number);
|
|
81
110
|
if (aVal < bVal) return order === "asc" ? -1 : 1;
|
|
82
111
|
if (aVal > bVal) return order === "asc" ? 1 : -1;
|
|
83
112
|
}
|
|
@@ -100,35 +129,16 @@ export function queryContentType(entries, query = {}) {
|
|
|
100
129
|
* @param {string} id - Entry ID to find
|
|
101
130
|
* @returns {ContentLoaderEntry | null} The matching entry or null
|
|
102
131
|
*/
|
|
103
|
-
export function findEntry(entries, id) {
|
|
132
|
+
export function findEntry(entries: ContentLoaderEntry[], id: string) {
|
|
104
133
|
return entries.find((e) => e.id === id) ?? null;
|
|
105
134
|
}
|
|
106
135
|
|
|
107
136
|
// ─── Class Implementations ──────────────────────────────────────────────────
|
|
108
137
|
|
|
109
|
-
/**
|
|
110
|
-
* @typedef {{
|
|
111
|
-
* contentType?: string;
|
|
112
|
-
* filter?: unknown;
|
|
113
|
-
* sort?: unknown;
|
|
114
|
-
* limit?: number;
|
|
115
|
-
* _project?: { contentTypes?: Map<string, ContentLoaderEntry[]>; [k: string]: unknown };
|
|
116
|
-
* [k: string]: unknown;
|
|
117
|
-
* }} CollectionConfig
|
|
118
|
-
*
|
|
119
|
-
* @typedef {{
|
|
120
|
-
* contentType?: string;
|
|
121
|
-
* id?: unknown;
|
|
122
|
-
* field?: string;
|
|
123
|
-
* _project?: { contentTypes?: Map<string, ContentLoaderEntry[]>; [k: string]: unknown };
|
|
124
|
-
* _document?: { route?: { _pathParams?: Record<string, string> }; [k: string]: unknown };
|
|
125
|
-
* [k: string]: unknown;
|
|
126
|
-
* }} EntryConfig
|
|
127
|
-
*/
|
|
128
|
-
|
|
129
138
|
export class ContentCollection {
|
|
139
|
+
config: CollectionConfig;
|
|
130
140
|
/** @param {CollectionConfig} config */
|
|
131
|
-
constructor(config) {
|
|
141
|
+
constructor(config: CollectionConfig) {
|
|
132
142
|
this.config = config;
|
|
133
143
|
}
|
|
134
144
|
|
|
@@ -136,13 +146,24 @@ export class ContentCollection {
|
|
|
136
146
|
const { contentType, filter, sort, limit, _project } = this.config;
|
|
137
147
|
const entries = _project?.contentTypes?.get(contentType ?? "");
|
|
138
148
|
if (!entries) return [];
|
|
139
|
-
return queryContentType(entries,
|
|
149
|
+
return queryContentType(entries, {
|
|
150
|
+
...(filter != null && {
|
|
151
|
+
filter: filter as
|
|
152
|
+
| Record<string, unknown>
|
|
153
|
+
| Array<{ field: string; op: string; value?: unknown }>,
|
|
154
|
+
}),
|
|
155
|
+
...(sort != null && {
|
|
156
|
+
sort: sort as { field: string; order?: string } | Array<{ field: string; order?: string }>,
|
|
157
|
+
}),
|
|
158
|
+
...(limit != null && { limit }),
|
|
159
|
+
});
|
|
140
160
|
}
|
|
141
161
|
}
|
|
142
162
|
|
|
143
163
|
export class ContentEntry {
|
|
164
|
+
config: EntryConfig;
|
|
144
165
|
/** @param {EntryConfig} config */
|
|
145
|
-
constructor(config) {
|
|
166
|
+
constructor(config: EntryConfig) {
|
|
146
167
|
this.config = config;
|
|
147
168
|
}
|
|
148
169
|
|
|
@@ -155,16 +176,16 @@ export class ContentEntry {
|
|
|
155
176
|
if (
|
|
156
177
|
resolvedId &&
|
|
157
178
|
typeof resolvedId === "object" &&
|
|
158
|
-
|
|
179
|
+
(resolvedId as { $ref?: string }).$ref?.startsWith("#/$params/")
|
|
159
180
|
) {
|
|
160
|
-
const paramName =
|
|
181
|
+
const paramName = (resolvedId as { $ref: string }).$ref.replace("#/$params/", "");
|
|
161
182
|
resolvedId = _document?.route?._pathParams?.[paramName];
|
|
162
183
|
}
|
|
163
184
|
if (!resolvedId) return null;
|
|
164
185
|
|
|
165
186
|
if (field && field !== "id") {
|
|
166
|
-
return entries.find((e) => e.data[field] === resolvedId) ?? null;
|
|
187
|
+
return entries.find((e: ContentLoaderEntry) => e.data[field] === resolvedId) ?? null;
|
|
167
188
|
}
|
|
168
|
-
return findEntry(entries,
|
|
189
|
+
return findEntry(entries, resolvedId as string);
|
|
169
190
|
}
|
|
170
191
|
}
|
|
@@ -2,7 +2,8 @@ import { fromHtml } from "hast-util-from-html";
|
|
|
2
2
|
import { whitespace } from "hast-util-whitespace";
|
|
3
3
|
import { find, html as htmlInfo } from "property-information";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import type { Nodes as HastNode } from "hast";
|
|
6
|
+
import type { JxElement } from "@jxsuite/schema/types";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Convert an HTML string into an array of Jx tree nodes.
|
|
@@ -10,18 +11,17 @@ import { find, html as htmlInfo } from "property-information";
|
|
|
10
11
|
* @param {string} htmlString
|
|
11
12
|
* @returns {(JxElement | string)[]}
|
|
12
13
|
*/
|
|
13
|
-
export function htmlToJx(htmlString) {
|
|
14
|
+
export function htmlToJx(htmlString: string) {
|
|
14
15
|
const hast = fromHtml(htmlString, { fragment: true });
|
|
15
|
-
return convertHastChildren(
|
|
16
|
+
return convertHastChildren(hast.children as HastNode[]);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @param {HastNode[]} children
|
|
20
21
|
* @returns {(JxElement | string)[]}
|
|
21
22
|
*/
|
|
22
|
-
function convertHastChildren(children) {
|
|
23
|
-
|
|
24
|
-
const result = [];
|
|
23
|
+
function convertHastChildren(children: HastNode[]) {
|
|
24
|
+
const result: (JxElement | string)[] = [];
|
|
25
25
|
for (const child of children) {
|
|
26
26
|
const converted = convertHastNode(child);
|
|
27
27
|
if (converted != null) result.push(converted);
|
|
@@ -33,15 +33,14 @@ function convertHastChildren(children) {
|
|
|
33
33
|
* @param {HastNode} node
|
|
34
34
|
* @returns {JxElement | string | null}
|
|
35
35
|
*/
|
|
36
|
-
function convertHastNode(node) {
|
|
36
|
+
function convertHastNode(node: HastNode) {
|
|
37
37
|
if (node.type === "text") {
|
|
38
|
-
if (whitespace(
|
|
38
|
+
if (whitespace(node as unknown as HastNode)) return null;
|
|
39
39
|
return node.value ?? null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (node.type === "element") {
|
|
43
|
-
|
|
44
|
-
const el = { tagName: node.tagName };
|
|
43
|
+
const el: JxElement = { tagName: node.tagName };
|
|
45
44
|
|
|
46
45
|
if (node.properties && Object.keys(node.properties).length > 0) {
|
|
47
46
|
const { style, attrs } = hastPropsToJx(node.properties);
|
|
@@ -54,7 +53,7 @@ function convertHastNode(node) {
|
|
|
54
53
|
if (kids.length === 1 && typeof kids[0] === "string") {
|
|
55
54
|
el.textContent = kids[0];
|
|
56
55
|
} else if (kids.length > 0) {
|
|
57
|
-
el.children =
|
|
56
|
+
el.children = kids as JxElement[];
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
return el;
|
|
@@ -67,11 +66,9 @@ function convertHastNode(node) {
|
|
|
67
66
|
* @param {Record<string, unknown>} properties
|
|
68
67
|
* @returns {{ style: Record<string, string>; attrs: Record<string, string> }}
|
|
69
68
|
*/
|
|
70
|
-
function hastPropsToJx(properties) {
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
/** @type {Record<string, string>} */
|
|
74
|
-
const style = {};
|
|
69
|
+
function hastPropsToJx(properties: Record<string, unknown>) {
|
|
70
|
+
const attrs: Record<string, string> = {};
|
|
71
|
+
const style: Record<string, string> = {};
|
|
75
72
|
|
|
76
73
|
for (const [key, value] of Object.entries(properties)) {
|
|
77
74
|
if (value === false || value === undefined || value === null) continue;
|
|
@@ -99,7 +96,7 @@ function hastPropsToJx(properties) {
|
|
|
99
96
|
* @param {string} styleStr
|
|
100
97
|
* @param {Record<string, string>} out
|
|
101
98
|
*/
|
|
102
|
-
function parseInlineStyle(styleStr, out) {
|
|
99
|
+
function parseInlineStyle(styleStr: string, out: Record<string, string>) {
|
|
103
100
|
for (const decl of styleStr.split(";")) {
|
|
104
101
|
const colon = decl.indexOf(":");
|
|
105
102
|
if (colon === -1) continue;
|
package/src/{md.js → md.ts}
RENAMED
|
@@ -20,7 +20,9 @@ import remarkDirective from "remark-directive";
|
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
21
|
import { basename, extname, resolve as resolvePath } from "node:path";
|
|
22
22
|
import { globSync } from "glob";
|
|
23
|
-
import { mdastNodeToJx } from "./transpile.
|
|
23
|
+
import { mdastNodeToJx } from "./transpile.ts";
|
|
24
|
+
import type { MarkdownFileResult, TocEntry, MdastNode } from "./types.ts";
|
|
25
|
+
import type { JxElement } from "@jxsuite/schema/types";
|
|
24
26
|
|
|
25
27
|
// ─── Tree utilities (inline to avoid Bun ESM resolution issues with unist-util-*) ──
|
|
26
28
|
|
|
@@ -31,18 +33,22 @@ import { mdastNodeToJx } from "./transpile.js";
|
|
|
31
33
|
* @param {string | function} typeOrVisitor
|
|
32
34
|
* @param {function} [maybeVisitor]
|
|
33
35
|
*/
|
|
34
|
-
function visit(
|
|
36
|
+
function visit(
|
|
37
|
+
tree: MdastNode,
|
|
38
|
+
typeOrVisitor: string | ((node: MdastNode) => void),
|
|
39
|
+
maybeVisitor?: (node: MdastNode) => void,
|
|
40
|
+
) {
|
|
35
41
|
const type = typeof typeOrVisitor === "string" ? typeOrVisitor : null;
|
|
36
42
|
const visitor = type ? maybeVisitor : typeOrVisitor;
|
|
37
43
|
|
|
38
|
-
function walk(
|
|
44
|
+
function walk(node: MdastNode) {
|
|
39
45
|
if (!node || typeof node !== "object") return;
|
|
40
|
-
if (!type || node.type === type)
|
|
46
|
+
if (!type || node.type === type) (visitor as Function)(node);
|
|
41
47
|
if (Array.isArray(node.children)) {
|
|
42
48
|
for (const child of node.children) walk(child);
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
|
-
walk(
|
|
51
|
+
walk(tree as MdastNode);
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
/**
|
|
@@ -51,7 +57,7 @@ function visit(tree, typeOrVisitor, maybeVisitor) {
|
|
|
51
57
|
* @param {MdastNode} node
|
|
52
58
|
* @returns {string}
|
|
53
59
|
*/
|
|
54
|
-
function mdastToString(node) {
|
|
60
|
+
function mdastToString(node: MdastNode): string {
|
|
55
61
|
if (!node) return "";
|
|
56
62
|
if (typeof node === "string") return node;
|
|
57
63
|
if (node.value) return node.value;
|
|
@@ -67,7 +73,7 @@ function mdastToString(node) {
|
|
|
67
73
|
* @param {string} text
|
|
68
74
|
* @returns {number} Minutes (rounded up, minimum 1)
|
|
69
75
|
*/
|
|
70
|
-
function readingTime(text) {
|
|
76
|
+
function readingTime(text: string) {
|
|
71
77
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
72
78
|
return Math.max(1, Math.ceil(words / 200));
|
|
73
79
|
}
|
|
@@ -78,10 +84,10 @@ function readingTime(text) {
|
|
|
78
84
|
* @param {object} tree - Mdast AST
|
|
79
85
|
* @returns {{ depth: number; text: string; id: string }[]}
|
|
80
86
|
*/
|
|
81
|
-
function extractToc(tree) {
|
|
87
|
+
function extractToc(tree: MdastNode) {
|
|
82
88
|
/** @type {{ depth: number; text: string; id: string }[]} */
|
|
83
|
-
const entries = [];
|
|
84
|
-
visit(tree, "heading", (
|
|
89
|
+
const entries: TocEntry[] = [];
|
|
90
|
+
visit(tree, "heading", (node: MdastNode) => {
|
|
85
91
|
const text = mdastToString(node);
|
|
86
92
|
const id = text
|
|
87
93
|
.toLowerCase()
|
|
@@ -89,7 +95,7 @@ function extractToc(tree) {
|
|
|
89
95
|
.replace(/\s+/g, "-")
|
|
90
96
|
.replace(/-+/g, "-")
|
|
91
97
|
.replace(/^-|-$/g, "");
|
|
92
|
-
entries.push({ depth:
|
|
98
|
+
entries.push({ depth: node.depth as number, text, id });
|
|
93
99
|
});
|
|
94
100
|
return entries;
|
|
95
101
|
}
|
|
@@ -100,10 +106,9 @@ function extractToc(tree) {
|
|
|
100
106
|
* @param {object} tree - Mdast AST
|
|
101
107
|
* @returns {string} Plain text of first paragraph, or empty string
|
|
102
108
|
*/
|
|
103
|
-
function extractExcerpt(tree) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
visit(tree, "paragraph", (/** @type {MdastNode} */ node) => {
|
|
109
|
+
function extractExcerpt(tree: MdastNode) {
|
|
110
|
+
let firstParagraph: MdastNode | null = null;
|
|
111
|
+
visit(tree, "paragraph", (node: MdastNode) => {
|
|
107
112
|
if (!firstParagraph) firstParagraph = node;
|
|
108
113
|
});
|
|
109
114
|
if (!firstParagraph) return "";
|
|
@@ -122,7 +127,14 @@ function extractExcerpt(tree) {
|
|
|
122
127
|
* @param {unknown} [config.directiveOptions] - Directive plugin options
|
|
123
128
|
* @returns {MarkdownFileResult}
|
|
124
129
|
*/
|
|
125
|
-
function processMarkdown(
|
|
130
|
+
function processMarkdown(
|
|
131
|
+
source: string,
|
|
132
|
+
filePath: string,
|
|
133
|
+
config: {
|
|
134
|
+
directives?: boolean;
|
|
135
|
+
directiveOptions?: unknown;
|
|
136
|
+
} = {},
|
|
137
|
+
) {
|
|
126
138
|
let processor = unified()
|
|
127
139
|
.use(remarkParse)
|
|
128
140
|
.use(remarkFrontmatter, ["yaml"])
|
|
@@ -138,22 +150,20 @@ function processMarkdown(source, filePath, config = {}) {
|
|
|
138
150
|
// @ts-ignore - vfile shape satisfies unified's internal contract
|
|
139
151
|
processor.runSync(tree, vfile);
|
|
140
152
|
|
|
141
|
-
const vfileData =
|
|
142
|
-
const frontmatter =
|
|
143
|
-
const mdTree =
|
|
153
|
+
const vfileData = vfile.data as Record<string, unknown>;
|
|
154
|
+
const frontmatter = (vfileData.frontmatter ?? {}) as Record<string, unknown>;
|
|
155
|
+
const mdTree = tree as unknown as MdastNode;
|
|
144
156
|
const plainText = mdastToString(mdTree);
|
|
145
157
|
const toc = extractToc(tree);
|
|
146
158
|
const excerpt = extractExcerpt(tree);
|
|
147
159
|
const slug = basename(filePath, extname(filePath));
|
|
148
160
|
|
|
149
|
-
const bodyNodes =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
bodyNodes.map(/** @type {(n: MdastNode) => any} */ (mdastNodeToJx)).filter(Boolean)
|
|
156
|
-
);
|
|
161
|
+
const bodyNodes = tree.children.filter(
|
|
162
|
+
(n: MdastNode) => n.type !== "yaml" && n.type !== "toml",
|
|
163
|
+
) as MdastNode[];
|
|
164
|
+
const $children = bodyNodes
|
|
165
|
+
.map(/** @type {(n: MdastNode) => any} */ (mdastNodeToJx))
|
|
166
|
+
.filter(Boolean) as (JxElement | string)[];
|
|
157
167
|
|
|
158
168
|
return {
|
|
159
169
|
slug,
|
|
@@ -174,11 +184,11 @@ function processMarkdown(source, filePath, config = {}) {
|
|
|
174
184
|
* @param {string} path
|
|
175
185
|
* @returns {unknown}
|
|
176
186
|
*/
|
|
177
|
-
function getNestedValue(obj, path) {
|
|
187
|
+
function getNestedValue(obj: Record<string, unknown> | MarkdownFileResult, path: string) {
|
|
178
188
|
return path
|
|
179
189
|
.split(".")
|
|
180
190
|
.reduce(
|
|
181
|
-
(
|
|
191
|
+
(o: unknown, k) => (o as Record<string, unknown> | undefined)?.[k],
|
|
182
192
|
/** @type {unknown} */ (obj),
|
|
183
193
|
);
|
|
184
194
|
}
|
|
@@ -192,6 +202,13 @@ function getNestedValue(obj, path) {
|
|
|
192
202
|
* { "$prototype": "MarkdownFile", "$src": "@jxsuite/md", "src": "./content/about.md" }
|
|
193
203
|
*/
|
|
194
204
|
export class MarkdownFile {
|
|
205
|
+
config: {
|
|
206
|
+
src: string;
|
|
207
|
+
remarkPlugins?: unknown[];
|
|
208
|
+
rehypePlugins?: unknown[];
|
|
209
|
+
basePath?: string;
|
|
210
|
+
directives?: boolean;
|
|
211
|
+
};
|
|
195
212
|
/**
|
|
196
213
|
* @param {object} config
|
|
197
214
|
* @param {string} config.src - File path to markdown file
|
|
@@ -200,7 +217,13 @@ export class MarkdownFile {
|
|
|
200
217
|
* @param {string} [config.basePath] - Base path for resolving src
|
|
201
218
|
* @param {boolean} [config.directives] - Enable directive support
|
|
202
219
|
*/
|
|
203
|
-
constructor(config
|
|
220
|
+
constructor(config: {
|
|
221
|
+
src: string;
|
|
222
|
+
remarkPlugins?: unknown[];
|
|
223
|
+
rehypePlugins?: unknown[];
|
|
224
|
+
basePath?: string;
|
|
225
|
+
directives?: boolean;
|
|
226
|
+
}) {
|
|
204
227
|
this.config = config;
|
|
205
228
|
}
|
|
206
229
|
|
|
@@ -227,19 +250,40 @@ export class MarkdownFile {
|
|
|
227
250
|
* { "$prototype": "MarkdownCollection", "$src": "@jxsuite/md", "src": "./posts/*.md" }
|
|
228
251
|
*/
|
|
229
252
|
export class MarkdownCollection {
|
|
253
|
+
config: {
|
|
254
|
+
src: string;
|
|
255
|
+
sortBy?: string;
|
|
256
|
+
sortOrder?: string;
|
|
257
|
+
limit?: number;
|
|
258
|
+
filter?: (result: MarkdownFileResult) => boolean;
|
|
259
|
+
remarkPlugins?: unknown[];
|
|
260
|
+
rehypePlugins?: unknown[];
|
|
261
|
+
basePath?: string;
|
|
262
|
+
directives?: boolean;
|
|
263
|
+
};
|
|
230
264
|
/**
|
|
231
265
|
* @param {object} config
|
|
232
266
|
* @param {string} config.src - Glob pattern or directory path
|
|
233
267
|
* @param {string} [config.sortBy] Default is `'frontmatter.date'`
|
|
234
268
|
* @param {string} [config.sortOrder] Default is `'desc'`
|
|
235
269
|
* @param {number} [config.limit]
|
|
236
|
-
* @param {
|
|
270
|
+
* @param {(result: MarkdownFileResult) => boolean} [config.filter] - Filter function
|
|
237
271
|
* @param {unknown[]} [config.remarkPlugins] Default is `[]`
|
|
238
272
|
* @param {unknown[]} [config.rehypePlugins] Default is `[]`
|
|
239
273
|
* @param {string} [config.basePath] - Base path for resolving glob
|
|
240
274
|
* @param {boolean} [config.directives] - Enable directive support
|
|
241
275
|
*/
|
|
242
|
-
constructor(config
|
|
276
|
+
constructor(config: {
|
|
277
|
+
src: string;
|
|
278
|
+
sortBy?: string;
|
|
279
|
+
sortOrder?: string;
|
|
280
|
+
limit?: number;
|
|
281
|
+
filter?: (result: MarkdownFileResult) => boolean;
|
|
282
|
+
remarkPlugins?: unknown[];
|
|
283
|
+
rehypePlugins?: unknown[];
|
|
284
|
+
basePath?: string;
|
|
285
|
+
directives?: boolean;
|
|
286
|
+
}) {
|
|
243
287
|
this.config = config;
|
|
244
288
|
}
|
|
245
289
|
|
|
@@ -264,7 +308,7 @@ export class MarkdownCollection {
|
|
|
264
308
|
const pattern = resolved.split("\\").join("/");
|
|
265
309
|
const files = globSync(pattern, { absolute: true });
|
|
266
310
|
|
|
267
|
-
const results = files.map((
|
|
311
|
+
const results = files.map((filePath: string) => {
|
|
268
312
|
const source = readFileSync(filePath, "utf-8");
|
|
269
313
|
return processMarkdown(source, filePath, processorConfig);
|
|
270
314
|
});
|
|
@@ -276,9 +320,9 @@ export class MarkdownCollection {
|
|
|
276
320
|
}
|
|
277
321
|
|
|
278
322
|
// Sort
|
|
279
|
-
filtered.sort((
|
|
280
|
-
const aVal =
|
|
281
|
-
const bVal =
|
|
323
|
+
filtered.sort((a: MarkdownFileResult, b: MarkdownFileResult) => {
|
|
324
|
+
const aVal = getNestedValue(a, sortBy) ?? ("" as string | number);
|
|
325
|
+
const bVal = getNestedValue(b, sortBy) ?? ("" as string | number);
|
|
282
326
|
if (aVal < bVal) return sortOrder === "asc" ? -1 : 1;
|
|
283
327
|
if (aVal > bVal) return sortOrder === "asc" ? 1 : -1;
|
|
284
328
|
return 0;
|
|
@@ -307,4 +351,4 @@ export {
|
|
|
307
351
|
convertChildren,
|
|
308
352
|
jxKey,
|
|
309
353
|
mdKey,
|
|
310
|
-
} from "./transpile.
|
|
354
|
+
} from "./transpile.ts";
|
|
@@ -17,7 +17,9 @@ import remarkFrontmatter from "remark-frontmatter";
|
|
|
17
17
|
import remarkParseFrontmatter from "remark-parse-frontmatter";
|
|
18
18
|
import remarkGfm from "remark-gfm";
|
|
19
19
|
import remarkDirective from "remark-directive";
|
|
20
|
-
import { htmlToJx } from "./html-to-jx.
|
|
20
|
+
import { htmlToJx } from "./html-to-jx.ts";
|
|
21
|
+
import type { MdastNode } from "./types.ts";
|
|
22
|
+
import type { JxDocument, JxElement } from "@jxsuite/schema/types";
|
|
21
23
|
export { htmlToJx };
|
|
22
24
|
|
|
23
25
|
// ─── Dot-path expansion ─────────────────────────────────────────────────────
|
|
@@ -40,7 +42,7 @@ const JX_ANNOTATION_KEYS = new Set(["title", "description"]);
|
|
|
40
42
|
* @param {string} key
|
|
41
43
|
* @returns {string}
|
|
42
44
|
*/
|
|
43
|
-
export function jxKey(key) {
|
|
45
|
+
export function jxKey(key: string) {
|
|
44
46
|
if (JX_DOLLAR_KEYS.has(key)) return `$${key}`;
|
|
45
47
|
if (key.startsWith("--") && JX_ANNOTATION_KEYS.has(key.slice(2))) return `$${key.slice(2)}`;
|
|
46
48
|
return key;
|
|
@@ -52,7 +54,7 @@ export function jxKey(key) {
|
|
|
52
54
|
* @param {string} key
|
|
53
55
|
* @returns {string}
|
|
54
56
|
*/
|
|
55
|
-
export function mdKey(key) {
|
|
57
|
+
export function mdKey(key: string) {
|
|
56
58
|
if (key.startsWith("$") && JX_DOLLAR_KEYS.has(key.slice(1))) {
|
|
57
59
|
return key.slice(1);
|
|
58
60
|
}
|
|
@@ -68,9 +70,8 @@ export function mdKey(key) {
|
|
|
68
70
|
* @param {Record<string, string>} attrs - Flat attribute map from remark-directive
|
|
69
71
|
* @returns {Record<string, unknown>} Nested object
|
|
70
72
|
*/
|
|
71
|
-
export function expandDotPaths(attrs) {
|
|
72
|
-
|
|
73
|
-
const result = {};
|
|
73
|
+
export function expandDotPaths(attrs: Record<string, string>) {
|
|
74
|
+
const result: Record<string, unknown> = {};
|
|
74
75
|
|
|
75
76
|
for (const [key, value] of Object.entries(attrs)) {
|
|
76
77
|
const dotIndex = key.indexOf(".");
|
|
@@ -86,7 +87,7 @@ export function expandDotPaths(attrs) {
|
|
|
86
87
|
if (!(seg in target) || typeof target[seg] !== "object") {
|
|
87
88
|
target[seg] = {};
|
|
88
89
|
}
|
|
89
|
-
target =
|
|
90
|
+
target = target[seg] as Record<string, unknown>;
|
|
90
91
|
}
|
|
91
92
|
target[jxKey(segments[segments.length - 1])] = value;
|
|
92
93
|
}
|
|
@@ -100,15 +101,14 @@ export function expandDotPaths(attrs) {
|
|
|
100
101
|
* @param {Record<string, unknown>} obj - Nested object
|
|
101
102
|
* @returns {Record<string, string>} Flat attribute map
|
|
102
103
|
*/
|
|
103
|
-
export function collapseDotPaths(obj) {
|
|
104
|
-
|
|
105
|
-
const result = {};
|
|
104
|
+
export function collapseDotPaths(obj: Record<string, unknown>) {
|
|
105
|
+
const result: Record<string, string> = {};
|
|
106
106
|
|
|
107
|
-
function walk(
|
|
107
|
+
function walk(node: Record<string, unknown>, prefix: string) {
|
|
108
108
|
for (const [key, value] of Object.entries(node)) {
|
|
109
109
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
110
110
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
111
|
-
walk(
|
|
111
|
+
walk(value as Record<string, unknown>, path);
|
|
112
112
|
} else {
|
|
113
113
|
result[path] = String(value);
|
|
114
114
|
}
|
|
@@ -151,9 +151,8 @@ const CSS_PSEUDO_NAMES = new Set([
|
|
|
151
151
|
* @param {Record<string, unknown>} styleObj
|
|
152
152
|
* @returns {Record<string, unknown>}
|
|
153
153
|
*/
|
|
154
|
-
export function applyStyleKeyMapping(styleObj) {
|
|
155
|
-
|
|
156
|
-
const result = {};
|
|
154
|
+
export function applyStyleKeyMapping(styleObj: Record<string, unknown>) {
|
|
155
|
+
const result: Record<string, unknown> = {};
|
|
157
156
|
for (const [key, value] of Object.entries(styleObj)) {
|
|
158
157
|
if (CSS_PSEUDO_NAMES.has(key)) {
|
|
159
158
|
result[`:${key}`] = value;
|
|
@@ -175,7 +174,7 @@ export function applyStyleKeyMapping(styleObj) {
|
|
|
175
174
|
* @param {Record<string, string>} attrs
|
|
176
175
|
* @returns {Record<string, unknown>}
|
|
177
176
|
*/
|
|
178
|
-
export function expandStylePaths(attrs) {
|
|
177
|
+
export function expandStylePaths(attrs: Record<string, string>) {
|
|
179
178
|
return applyStyleKeyMapping(expandDotPaths(attrs));
|
|
180
179
|
}
|
|
181
180
|
|
|
@@ -188,9 +187,8 @@ export function expandStylePaths(attrs) {
|
|
|
188
187
|
* @param {Record<string, unknown>} styleObj
|
|
189
188
|
* @returns {Record<string, string>}
|
|
190
189
|
*/
|
|
191
|
-
export function collapseStylePaths(styleObj) {
|
|
192
|
-
|
|
193
|
-
const normalized = {};
|
|
190
|
+
export function collapseStylePaths(styleObj: Record<string, unknown>) {
|
|
191
|
+
const normalized: Record<string, unknown> = {};
|
|
194
192
|
|
|
195
193
|
for (const [key, value] of Object.entries(styleObj)) {
|
|
196
194
|
if (key.startsWith(":") && CSS_PSEUDO_NAMES.has(key.slice(1))) {
|
|
@@ -214,7 +212,7 @@ export function collapseStylePaths(styleObj) {
|
|
|
214
212
|
* @param {string} source - Raw markdown string
|
|
215
213
|
* @returns {boolean}
|
|
216
214
|
*/
|
|
217
|
-
export function isJxMarkdown(source) {
|
|
215
|
+
export function isJxMarkdown(source: string) {
|
|
218
216
|
const fmMatch = source.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
219
217
|
if (!fmMatch) return false;
|
|
220
218
|
return /^tagName:\s*.+-.+/m.test(fmMatch[1]);
|
|
@@ -273,22 +271,20 @@ const PHRASING_ELEMENTS = new Set([
|
|
|
273
271
|
* @param {Record<string, string>} attrs
|
|
274
272
|
* @returns {{ props: Record<string, unknown>; attributes: Record<string, string> }}
|
|
275
273
|
*/
|
|
276
|
-
function routeAttributes(attrs) {
|
|
274
|
+
function routeAttributes(attrs: Record<string, string>) {
|
|
277
275
|
const expanded = expandDotPaths(attrs);
|
|
278
276
|
|
|
279
277
|
// Apply style-key mapping (pseudo-classes, media queries) to the style sub-object
|
|
280
278
|
if (expanded.style && typeof expanded.style === "object") {
|
|
281
|
-
expanded.style = applyStyleKeyMapping(
|
|
279
|
+
expanded.style = applyStyleKeyMapping(expanded.style as Record<string, unknown>);
|
|
282
280
|
}
|
|
283
281
|
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
/** @type {Record<string, string>} */
|
|
287
|
-
const attributes = {};
|
|
282
|
+
const props: Record<string, unknown> = {};
|
|
283
|
+
const attributes: Record<string, string> = {};
|
|
288
284
|
|
|
289
285
|
for (const [key, value] of Object.entries(expanded)) {
|
|
290
286
|
if (HTML_ATTR_PATTERN.test(key)) {
|
|
291
|
-
attributes[key] =
|
|
287
|
+
attributes[key] = value as string;
|
|
292
288
|
} else {
|
|
293
289
|
props[key] = value;
|
|
294
290
|
}
|
|
@@ -302,8 +298,8 @@ function routeAttributes(attrs) {
|
|
|
302
298
|
*
|
|
303
299
|
* @type {Record<string, (n: MdastNode) => string>}
|
|
304
300
|
*/
|
|
305
|
-
const JX_TAG_MAP = {
|
|
306
|
-
heading: (
|
|
301
|
+
const JX_TAG_MAP: Record<string, (n: MdastNode) => string> = {
|
|
302
|
+
heading: (n: MdastNode) => `h${n.depth}`,
|
|
307
303
|
paragraph: () => "p",
|
|
308
304
|
emphasis: () => "em",
|
|
309
305
|
strong: () => "strong",
|
|
@@ -312,14 +308,14 @@ const JX_TAG_MAP = {
|
|
|
312
308
|
link: () => "a",
|
|
313
309
|
image: () => "img",
|
|
314
310
|
blockquote: () => "blockquote",
|
|
315
|
-
list: (
|
|
311
|
+
list: (n: MdastNode) => (n.ordered ? "ol" : "ul"),
|
|
316
312
|
listItem: () => "li",
|
|
317
313
|
code: () => "pre",
|
|
318
314
|
thematicBreak: () => "hr",
|
|
319
315
|
break: () => "br",
|
|
320
316
|
table: () => "table",
|
|
321
317
|
tableRow: () => "tr",
|
|
322
|
-
tableCell: (
|
|
318
|
+
tableCell: (n: MdastNode) => (n.isHeader ? "th" : "td"),
|
|
323
319
|
};
|
|
324
320
|
|
|
325
321
|
/**
|
|
@@ -328,7 +324,7 @@ const JX_TAG_MAP = {
|
|
|
328
324
|
* @param {MdastNode} node
|
|
329
325
|
* @returns {JxElement | string | (JxElement | string)[] | null} Jx element or null
|
|
330
326
|
*/
|
|
331
|
-
export function mdastNodeToJx(node) {
|
|
327
|
+
export function mdastNodeToJx(node: MdastNode) {
|
|
332
328
|
if (!node || typeof node !== "object") return null;
|
|
333
329
|
|
|
334
330
|
if (node.type === "yaml" || node.type === "toml") return null;
|
|
@@ -354,8 +350,7 @@ export function mdastNodeToJx(node) {
|
|
|
354
350
|
if (!tagFn) return null;
|
|
355
351
|
|
|
356
352
|
const tag = tagFn(node);
|
|
357
|
-
|
|
358
|
-
const el = { tagName: tag };
|
|
353
|
+
const el: JxElement = { tagName: tag };
|
|
359
354
|
|
|
360
355
|
switch (node.type) {
|
|
361
356
|
case "heading":
|
|
@@ -377,7 +372,7 @@ export function mdastNodeToJx(node) {
|
|
|
377
372
|
}
|
|
378
373
|
|
|
379
374
|
case "inlineCode":
|
|
380
|
-
el.textContent = node.value;
|
|
375
|
+
el.textContent = node.value ?? null;
|
|
381
376
|
break;
|
|
382
377
|
|
|
383
378
|
case "link":
|
|
@@ -411,7 +406,7 @@ export function mdastNodeToJx(node) {
|
|
|
411
406
|
el.children = [
|
|
412
407
|
{
|
|
413
408
|
tagName: "code",
|
|
414
|
-
textContent: node.value,
|
|
409
|
+
textContent: node.value ?? null,
|
|
415
410
|
...(node.lang ? { className: `language-${node.lang}` } : {}),
|
|
416
411
|
},
|
|
417
412
|
];
|
|
@@ -425,7 +420,7 @@ export function mdastNodeToJx(node) {
|
|
|
425
420
|
const rows = convertChildren(node.children ?? []);
|
|
426
421
|
const thead = rows.length > 0 ? { tagName: "thead", children: [rows[0]] } : null;
|
|
427
422
|
const tbody = rows.length > 1 ? { tagName: "tbody", children: rows.slice(1) } : null;
|
|
428
|
-
el.children =
|
|
423
|
+
el.children = [thead, tbody].filter(Boolean) as JxElement[];
|
|
429
424
|
break;
|
|
430
425
|
}
|
|
431
426
|
}
|
|
@@ -439,13 +434,12 @@ export function mdastNodeToJx(node) {
|
|
|
439
434
|
* @param {MdastNode} node
|
|
440
435
|
* @returns {JxElement}
|
|
441
436
|
*/
|
|
442
|
-
function directiveToJx(node) {
|
|
443
|
-
|
|
444
|
-
const el = { tagName: /** @type {string} */ (node.name) };
|
|
437
|
+
function directiveToJx(node: MdastNode) {
|
|
438
|
+
const el: JxElement = { tagName: node.name as string };
|
|
445
439
|
|
|
446
440
|
if (node.attributes && Object.keys(node.attributes).length > 0) {
|
|
447
441
|
const { props, attributes } = routeAttributes(node.attributes);
|
|
448
|
-
const isCustomElement =
|
|
442
|
+
const isCustomElement = (node.name as string).includes("-");
|
|
449
443
|
if (isCustomElement) {
|
|
450
444
|
// For custom elements:
|
|
451
445
|
// - style, children, textContent, innerHTML, $-prefixed → element-level
|
|
@@ -461,7 +455,7 @@ function directiveToJx(node) {
|
|
|
461
455
|
) {
|
|
462
456
|
el[key] = value;
|
|
463
457
|
} else if (key === "props") {
|
|
464
|
-
el.$props =
|
|
458
|
+
el.$props = value as Record<string, unknown>;
|
|
465
459
|
} else {
|
|
466
460
|
if (!el.attributes) el.attributes = {};
|
|
467
461
|
el.attributes[key] = value;
|
|
@@ -516,9 +510,8 @@ function directiveToJx(node) {
|
|
|
516
510
|
}
|
|
517
511
|
|
|
518
512
|
if (node.children && node.children.length > 0) {
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
const isPhrasingParent = PHRASING_ELEMENTS.has(/** @type {string} */ (node.name));
|
|
513
|
+
const jxChildren: (JxElement | string)[] = [];
|
|
514
|
+
const isPhrasingParent = PHRASING_ELEMENTS.has(node.name as string);
|
|
522
515
|
|
|
523
516
|
for (const child of node.children) {
|
|
524
517
|
if (isPhrasingParent && child.type === "paragraph") {
|
|
@@ -557,11 +550,9 @@ function directiveToJx(node) {
|
|
|
557
550
|
* @param {MdastNode[]} children
|
|
558
551
|
* @returns {(JxElement | string)[]}
|
|
559
552
|
*/
|
|
560
|
-
export function convertChildren(children) {
|
|
553
|
+
export function convertChildren(children: MdastNode[]) {
|
|
561
554
|
if (!children) return [];
|
|
562
|
-
return
|
|
563
|
-
children.flatMap(mdastNodeToJx).filter((c) => c != null)
|
|
564
|
-
);
|
|
555
|
+
return children.flatMap(mdastNodeToJx).filter((c) => c != null) as (JxElement | string)[];
|
|
565
556
|
}
|
|
566
557
|
|
|
567
558
|
/**
|
|
@@ -573,7 +564,7 @@ export function convertChildren(children) {
|
|
|
573
564
|
* @param {string} source - Raw markdown string
|
|
574
565
|
* @returns {object} Complete Jx JSON document
|
|
575
566
|
*/
|
|
576
|
-
export function transpileJxMarkdown(source) {
|
|
567
|
+
export function transpileJxMarkdown(source: string) {
|
|
577
568
|
const processor = unified()
|
|
578
569
|
.use(remarkParse)
|
|
579
570
|
.use(remarkFrontmatter, ["yaml"])
|
|
@@ -585,26 +576,19 @@ export function transpileJxMarkdown(source) {
|
|
|
585
576
|
const vfile = { data: {} };
|
|
586
577
|
processor.runSync(tree, vfile);
|
|
587
578
|
|
|
588
|
-
const frontmatter =
|
|
589
|
-
?.frontmatter ?? {};
|
|
579
|
+
const frontmatter = (vfile.data as unknown as Record<string, unknown>)?.frontmatter ?? {};
|
|
590
580
|
|
|
591
|
-
|
|
592
|
-
const doc = {};
|
|
581
|
+
const doc: JxDocument = {};
|
|
593
582
|
|
|
594
583
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
595
584
|
doc[key] = value;
|
|
596
585
|
}
|
|
597
586
|
|
|
598
|
-
const bodyNodes =
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
(/** @type {{ type: string }} */ n) => n.type !== "yaml" && n.type !== "toml",
|
|
602
|
-
)
|
|
603
|
-
)
|
|
604
|
-
);
|
|
587
|
+
const bodyNodes = tree.children.filter(
|
|
588
|
+
(n: MdastNode) => n.type !== "yaml" && n.type !== "toml",
|
|
589
|
+
) as unknown as MdastNode[];
|
|
605
590
|
|
|
606
|
-
|
|
607
|
-
const children = [];
|
|
591
|
+
const children: (JxElement | string)[] = [];
|
|
608
592
|
|
|
609
593
|
for (const node of bodyNodes) {
|
|
610
594
|
const converted = mdastNodeToJx(node);
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { JxElement } from "@jxsuite/schema/types";
|
|
2
|
+
|
|
3
|
+
export interface MarkdownFileResult {
|
|
4
|
+
slug: string;
|
|
5
|
+
path: string;
|
|
6
|
+
frontmatter: Record<string, unknown>;
|
|
7
|
+
$children: (JxElement | string)[];
|
|
8
|
+
$excerpt?: string;
|
|
9
|
+
$toc?: TocEntry[];
|
|
10
|
+
$readingTime?: number;
|
|
11
|
+
$wordCount?: number;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TocEntry {
|
|
16
|
+
depth: number;
|
|
17
|
+
text: string;
|
|
18
|
+
id: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ContentEntry extends MarkdownFileResult {
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ContentLoaderEntry {
|
|
26
|
+
id: string;
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
body: string | null;
|
|
29
|
+
$children?: (JxElement | string)[];
|
|
30
|
+
_meta?: {
|
|
31
|
+
excerpt?: string;
|
|
32
|
+
toc?: TocEntry[];
|
|
33
|
+
readingTime?: number;
|
|
34
|
+
wordCount?: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MdastNode {
|
|
39
|
+
type: string;
|
|
40
|
+
children?: MdastNode[];
|
|
41
|
+
value?: string;
|
|
42
|
+
data?: unknown;
|
|
43
|
+
position?: unknown;
|
|
44
|
+
depth?: number;
|
|
45
|
+
ordered?: boolean | null;
|
|
46
|
+
start?: number | null;
|
|
47
|
+
spread?: boolean;
|
|
48
|
+
isHeader?: boolean;
|
|
49
|
+
name?: string;
|
|
50
|
+
attributes?: Record<string, string>;
|
|
51
|
+
url?: string;
|
|
52
|
+
alt?: string;
|
|
53
|
+
title?: string | null;
|
|
54
|
+
lang?: string | null;
|
|
55
|
+
meta?: string | null;
|
|
56
|
+
align?: (string | null)[] | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface HastNode {
|
|
60
|
+
type: string;
|
|
61
|
+
tagName?: string;
|
|
62
|
+
properties?: Record<string, unknown>;
|
|
63
|
+
children?: HastNode[];
|
|
64
|
+
value?: string;
|
|
65
|
+
}
|