@jxsuite/parser 2.0.1 → 3.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/dist/content-loader.js +608 -284
- package/dist/content.js +183 -30
- package/dist/markdown.js +394 -352
- package/dist/serialize.js +262 -232
- package/package.json +5 -5
- package/src/alerts.ts +22 -7
- package/src/content-data.ts +40 -0
- package/src/content-links.ts +15 -7
- package/src/content-loader.ts +681 -221
- package/src/content-routes.ts +23 -10
- package/src/content-rules.ts +40 -29
- package/src/content.ts +108 -70
- package/src/csv.ts +18 -14
- package/src/dates.ts +18 -13
- package/src/highlight.ts +6 -5
- package/src/inert.ts +7 -5
- package/src/markdown.ts +27 -23
- package/src/md.ts +133 -64
- package/src/serialize.ts +367 -266
- package/src/transpile.ts +182 -152
- package/src/types.ts +43 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Jx markdown parser and external class integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"upgrade": "bun update --latest && bun install"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@jxsuite/markup": "^0.4.
|
|
52
|
-
"@jxsuite/schema": "^
|
|
51
|
+
"@jxsuite/markup": "^0.4.13",
|
|
52
|
+
"@jxsuite/schema": "^4.0.0",
|
|
53
53
|
"@shikijs/langs": "^4.5.0",
|
|
54
54
|
"@shikijs/themes": "^4.5.0",
|
|
55
55
|
"glob": "^13.0.6",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"yaml": "^2.9.1"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@happy-dom/global-registrator": "^
|
|
70
|
-
"@jxsuite/runtime": "^
|
|
69
|
+
"@happy-dom/global-registrator": "^21.0.3",
|
|
70
|
+
"@jxsuite/runtime": "^5.0.0",
|
|
71
71
|
"@types/mdast": "^4.0.4",
|
|
72
72
|
"ajv": "^8.20.0"
|
|
73
73
|
},
|
package/src/alerts.ts
CHANGED
|
@@ -21,10 +21,25 @@
|
|
|
21
21
|
* @docs framework/site/jx-markdown
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { isJsonObject, isString } from "@jxsuite/schema/guards";
|
|
25
|
+
import type { JsonValue } from "@jxsuite/schema/types";
|
|
26
|
+
|
|
24
27
|
import type { MdastNode } from "./types.ts";
|
|
25
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Default titles by lower-case alert type: GitHub's five are always present, and the renderer looks
|
|
31
|
+
* up whatever type an author wrote, so a type a project switched on itself reads as absent.
|
|
32
|
+
*/
|
|
33
|
+
export interface AlertTitles extends Readonly<Record<string, string>> {
|
|
34
|
+
readonly caution: string;
|
|
35
|
+
readonly important: string;
|
|
36
|
+
readonly note: string;
|
|
37
|
+
readonly tip: string;
|
|
38
|
+
readonly warning: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
/** The five alert types GitHub defines, and the title each shows by default. */
|
|
27
|
-
export const DEFAULT_ALERT_TITLES:
|
|
42
|
+
export const DEFAULT_ALERT_TITLES: AlertTitles = {
|
|
28
43
|
caution: "Caution",
|
|
29
44
|
important: "Important",
|
|
30
45
|
note: "Note",
|
|
@@ -60,7 +75,7 @@ const TAG_NAME = /^[A-Za-z][\w.-]*$/;
|
|
|
60
75
|
* Throws on a value that is not a tag name so a typo in `project.json` fails the build where it was
|
|
61
76
|
* written rather than rendering a callout as an unknown element.
|
|
62
77
|
*/
|
|
63
|
-
export function normalizeAlerts(config?:
|
|
78
|
+
export function normalizeAlerts(config?: JsonValue, where = "alerts"): AlertSettings | null {
|
|
64
79
|
if (config === false) {
|
|
65
80
|
return null;
|
|
66
81
|
}
|
|
@@ -71,7 +86,7 @@ export function normalizeAlerts(config?: unknown, where = "alerts"): AlertSettin
|
|
|
71
86
|
if (config === undefined || config === true) {
|
|
72
87
|
return { disabled, types };
|
|
73
88
|
}
|
|
74
|
-
if (
|
|
89
|
+
if (!isJsonObject(config)) {
|
|
75
90
|
throw new TypeError(
|
|
76
91
|
`${where}: expected an object mapping alert types to element names, or false`,
|
|
77
92
|
);
|
|
@@ -86,7 +101,7 @@ export function normalizeAlerts(config?: unknown, where = "alerts"): AlertSettin
|
|
|
86
101
|
disabled.add(key);
|
|
87
102
|
} else if (element === true) {
|
|
88
103
|
types.set(key, null);
|
|
89
|
-
} else if (
|
|
104
|
+
} else if (isString(element) && TAG_NAME.test(element)) {
|
|
90
105
|
types.set(key, element);
|
|
91
106
|
} else {
|
|
92
107
|
throw new TypeError(
|
|
@@ -114,7 +129,7 @@ const MARKER = /^\[!([A-Za-z][\w-]*)\][+-]?/;
|
|
|
114
129
|
export function inlineText(nodes: readonly MdastNode[]): string {
|
|
115
130
|
let out = "";
|
|
116
131
|
for (const node of nodes) {
|
|
117
|
-
if (
|
|
132
|
+
if (node.value !== undefined) {
|
|
118
133
|
out += node.value;
|
|
119
134
|
} else if (node.children) {
|
|
120
135
|
out += inlineText(node.children);
|
|
@@ -139,7 +154,7 @@ const LINE_END = /\r?\n/;
|
|
|
139
154
|
*/
|
|
140
155
|
function markerOf(paragraph: MdastNode): { type: string; rest: string } | null {
|
|
141
156
|
const [first] = paragraph.children ?? [];
|
|
142
|
-
if (first?.type !== "text" ||
|
|
157
|
+
if (first?.type !== "text" || first.value === undefined) {
|
|
143
158
|
return null;
|
|
144
159
|
}
|
|
145
160
|
const match = MARKER.exec(first.value);
|
|
@@ -178,7 +193,7 @@ function splitMarker(
|
|
|
178
193
|
const take = (node: MdastNode) => {
|
|
179
194
|
if (inTitle && node.type === "break") {
|
|
180
195
|
inTitle = false;
|
|
181
|
-
} else if (inTitle && node.type === "text" &&
|
|
196
|
+
} else if (inTitle && node.type === "text" && node.value !== undefined) {
|
|
182
197
|
const lineEnd = LINE_END.exec(node.value);
|
|
183
198
|
if (lineEnd === null) {
|
|
184
199
|
title.push(node);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-data — how a reader gets entries out of the `content` section's loaded data.
|
|
3
|
+
*
|
|
4
|
+
* The section's data is a map of type name to entries, which the content loader's `ContentStore`
|
|
5
|
+
* may fill on demand (specs/extensions.md §8). This module is the reading side alone, with no Node
|
|
6
|
+
* imports, because `ContentCollection` and `ContentEntry` (content.ts) are bundled on their own and
|
|
7
|
+
* must not pull the loader in with them.
|
|
8
|
+
*
|
|
9
|
+
* @module @jxsuite/parser/content-data
|
|
10
|
+
* @docs extending/extensions/project-sections
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ContentLoaderEntry } from "./types.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `content` section's loaded data as a reader sees it: a map of type name to entries. `load`
|
|
17
|
+
* and `materialize` are optional so a plain map (one a host or a test built) reads the same way.
|
|
18
|
+
*/
|
|
19
|
+
export interface ContentSectionData extends ReadonlyMap<string, ContentLoaderEntry[]> {
|
|
20
|
+
/** One type's entries, loading the type first if it has not been. */
|
|
21
|
+
load?: (name: string) => Promise<ContentLoaderEntry[] | undefined>;
|
|
22
|
+
/** Give these entries (and the entries they relate to) their bodies before they are returned. */
|
|
23
|
+
materialize?: (entries: readonly (ContentLoaderEntry | null | undefined)[]) => Promise<void>;
|
|
24
|
+
/** Load every type and every body, so a synchronous `get` sees the whole section. */
|
|
25
|
+
complete?: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One type's entries from a section's data, loading the type first when the data loads lazily.
|
|
30
|
+
*
|
|
31
|
+
* @param {ContentSectionData | undefined} data - The section's loaded data
|
|
32
|
+
* @param {string} name - Content type name
|
|
33
|
+
* @returns {Promise<ContentLoaderEntry[] | undefined>} The entries, or undefined for no such type
|
|
34
|
+
*/
|
|
35
|
+
export async function entriesOf(
|
|
36
|
+
data: ContentSectionData | undefined,
|
|
37
|
+
name: string,
|
|
38
|
+
): Promise<ContentLoaderEntry[] | undefined> {
|
|
39
|
+
return (await data?.load?.(name)) ?? data?.get(name);
|
|
40
|
+
}
|
package/src/content-links.ts
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* @docs framework/site/content-collections
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
+
import { isNonNullObject, isString } from "@jxsuite/schema/guards";
|
|
33
34
|
import type { JxElement } from "@jxsuite/schema/types";
|
|
34
35
|
import { routeHref } from "./content-routes.ts";
|
|
35
36
|
import type { ContentNodes } from "./inert.ts";
|
|
@@ -102,8 +103,15 @@ function fragmentFor(raw: string): string {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/** The three parts of an href; `query` and `hash` keep their delimiter and are `""` when absent. */
|
|
107
|
+
interface HrefParts {
|
|
108
|
+
path: string;
|
|
109
|
+
query: string;
|
|
110
|
+
hash: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
/** Split `path?query#fragment` into its three parts (query and fragment keep their delimiter). */
|
|
106
|
-
function splitHref(href: string):
|
|
114
|
+
function splitHref(href: string): HrefParts {
|
|
107
115
|
const hashAt = href.indexOf("#");
|
|
108
116
|
const beforeHash = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
109
117
|
const hash = hashAt === -1 ? "" : href.slice(hashAt);
|
|
@@ -288,13 +296,13 @@ function resolveHref(href: string, from: string, index: LinkIndex): Resolution {
|
|
|
288
296
|
/** What a removed anchor leaves behind: its own content, as inline nodes. */
|
|
289
297
|
function contentOf(anchor: JxElement): ContentNodes {
|
|
290
298
|
if (Array.isArray(anchor.children)) {
|
|
291
|
-
return anchor.children
|
|
299
|
+
return anchor.children;
|
|
292
300
|
}
|
|
293
|
-
if (
|
|
301
|
+
if (anchor.innerHTML !== undefined) {
|
|
294
302
|
// Text that holds `${` was made inert as escaped `innerHTML` (inert.ts); it keeps that form.
|
|
295
303
|
return [{ innerHTML: anchor.innerHTML, tagName: "span" }];
|
|
296
304
|
}
|
|
297
|
-
return
|
|
305
|
+
return isString(anchor.textContent) ? [anchor.textContent] : [];
|
|
298
306
|
}
|
|
299
307
|
|
|
300
308
|
/**
|
|
@@ -318,11 +326,11 @@ export function rewriteLinks(
|
|
|
318
326
|
rewriteLinks(node, from, index, report);
|
|
319
327
|
continue;
|
|
320
328
|
}
|
|
321
|
-
if (
|
|
329
|
+
if (!isNonNullObject(node)) {
|
|
322
330
|
continue;
|
|
323
331
|
}
|
|
324
332
|
const href = node.tagName === "a" ? node.attributes?.href : undefined;
|
|
325
|
-
if (
|
|
333
|
+
if (isString(href)) {
|
|
326
334
|
const resolution = resolveHref(href, from, index);
|
|
327
335
|
if ("problem" in resolution) {
|
|
328
336
|
report({ href, reason: resolution.problem });
|
|
@@ -337,7 +345,7 @@ export function rewriteLinks(
|
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
347
|
if (Array.isArray(node.children)) {
|
|
340
|
-
rewriteLinks(node.children
|
|
348
|
+
rewriteLinks(node.children, from, index, report);
|
|
341
349
|
}
|
|
342
350
|
}
|
|
343
351
|
}
|