@jxsuite/parser 0.28.2 → 0.28.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/parser",
3
- "version": "0.28.2",
3
+ "version": "0.28.3",
4
4
  "description": "Jx markdown parser and external class integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "upgrade": "bunx npm-check-updates -u && bun install"
40
40
  },
41
41
  "dependencies": {
42
- "@jxsuite/schema": "^0.28.2",
42
+ "@jxsuite/schema": "^0.28.3",
43
43
  "glob": "^13.0.6",
44
44
  "hast-util-from-html": "^2.0.3",
45
45
  "hast-util-whitespace": "^3.0.0",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "devDependencies": {
61
61
  "@happy-dom/global-registrator": "^20.10.2",
62
- "@jxsuite/runtime": "^0.28.2",
62
+ "@jxsuite/runtime": "^0.28.3",
63
63
  "@types/hast": "^3.0.4",
64
64
  "@types/mdast": "^4.0.4"
65
65
  }
package/src/content.ts CHANGED
@@ -50,28 +50,39 @@ export function evaluateFilterRule(
50
50
  ) {
51
51
  const actual = rule.field === "id" ? entry.id : entry.data[rule.field];
52
52
  switch (rule.op) {
53
- case "==":
53
+ case "==": {
54
54
  return actual === rule.value;
55
- case "!=":
55
+ }
56
+ case "!=": {
56
57
  return actual !== rule.value;
57
- case "empty":
58
+ }
59
+ case "empty": {
58
60
  return actual == null || actual === "" || (Array.isArray(actual) && actual.length === 0);
59
- case "not empty":
61
+ }
62
+ case "not empty": {
60
63
  return actual != null && actual !== "" && !(Array.isArray(actual) && actual.length === 0);
61
- case "contains":
64
+ }
65
+ case "contains": {
62
66
  return typeof actual === "string" && actual.includes(String(rule.value ?? ""));
63
- case "not contains":
67
+ }
68
+ case "not contains": {
64
69
  return typeof actual === "string" && !actual.includes(String(rule.value ?? ""));
65
- case ">":
70
+ }
71
+ case ">": {
66
72
  return Number(actual) > Number(rule.value);
67
- case "<":
73
+ }
74
+ case "<": {
68
75
  return Number(actual) < Number(rule.value);
69
- case ">=":
76
+ }
77
+ case ">=": {
70
78
  return Number(actual) >= Number(rule.value);
71
- case "<=":
79
+ }
80
+ case "<=": {
72
81
  return Number(actual) <= Number(rule.value);
73
- default:
82
+ }
83
+ default: {
74
84
  return true;
85
+ }
75
86
  }
76
87
  }
77
88
 
@@ -89,8 +100,8 @@ export function evaluateFilterRule(
89
100
  export function queryContentType(
90
101
  entries: ContentLoaderEntry[],
91
102
  query: {
92
- filter?: Record<string, unknown> | Array<{ field: string; op: string; value?: unknown }>;
93
- sort?: { field: string; order?: string } | Array<{ field: string; order?: string }>;
103
+ filter?: Record<string, unknown> | { field: string; op: string; value?: unknown }[];
104
+ sort?: { field: string; order?: string } | { field: string; order?: string }[];
94
105
  limit?: number;
95
106
  } = {},
96
107
  ) {
@@ -120,8 +131,12 @@ export function queryContentType(
120
131
  for (const { field, order = "asc" } of sortRules) {
121
132
  const aVal = field === "id" ? a.id : ((a.data[field] ?? "") as string | number);
122
133
  const bVal = field === "id" ? b.id : ((b.data[field] ?? "") as string | number);
123
- if (aVal < bVal) return order === "asc" ? -1 : 1;
124
- if (aVal > bVal) return order === "asc" ? 1 : -1;
134
+ if (aVal < bVal) {
135
+ return order === "asc" ? -1 : 1;
136
+ }
137
+ if (aVal > bVal) {
138
+ return order === "asc" ? 1 : -1;
139
+ }
125
140
  }
126
141
  return 0;
127
142
  });
@@ -158,15 +173,17 @@ export class ContentCollection {
158
173
  resolve() {
159
174
  const { contentType, filter, sort, limit, _project } = this.config;
160
175
  const entries = _project?.contentTypes?.get(contentType ?? "");
161
- if (!entries) return [];
176
+ if (!entries) {
177
+ return [];
178
+ }
162
179
  return queryContentType(entries, {
163
180
  ...(filter != null && {
164
181
  filter: filter as
165
182
  | Record<string, unknown>
166
- | Array<{ field: string; op: string; value?: unknown }>,
183
+ | { field: string; op: string; value?: unknown }[],
167
184
  }),
168
185
  ...(sort != null && {
169
- sort: sort as { field: string; order?: string } | Array<{ field: string; order?: string }>,
186
+ sort: sort as { field: string; order?: string } | { field: string; order?: string }[],
170
187
  }),
171
188
  ...(limit != null && { limit }),
172
189
  });
@@ -183,7 +200,9 @@ export class ContentEntry {
183
200
  resolve() {
184
201
  const { contentType, id, field, _project, _document } = this.config;
185
202
  const entries = _project?.contentTypes?.get(contentType ?? "");
186
- if (!entries) return null;
203
+ if (!entries) {
204
+ return null;
205
+ }
187
206
 
188
207
  let resolvedId = id;
189
208
  if (
@@ -194,7 +213,9 @@ export class ContentEntry {
194
213
  const paramName = (resolvedId as { $ref: string }).$ref.replace("#/$params/", "");
195
214
  resolvedId = _document?.route?._pathParams?.[paramName];
196
215
  }
197
- if (!resolvedId) return null;
216
+ if (!resolvedId) {
217
+ return null;
218
+ }
198
219
 
199
220
  if (field && field !== "id") {
200
221
  return entries.find((e: ContentLoaderEntry) => e.data[field] === resolvedId) ?? null;
package/src/csv.ts CHANGED
@@ -32,21 +32,27 @@ export function parseCSV(csv: string): Record<string, string>[] {
32
32
  current += ch;
33
33
  if (inQuotes && csv[i + 1] === '"') {
34
34
  current += csv[i + 1];
35
- i++;
35
+ i += 1;
36
36
  } else {
37
37
  inQuotes = !inQuotes;
38
38
  }
39
39
  } else if ((ch === "\n" || (ch === "\r" && csv[i + 1] === "\n")) && !inQuotes) {
40
40
  lines.push(current);
41
41
  current = "";
42
- if (ch === "\r") i++;
42
+ if (ch === "\r") {
43
+ i += 1;
44
+ }
43
45
  } else {
44
46
  current += ch;
45
47
  }
46
48
  }
47
- if (current.trim()) lines.push(current);
49
+ if (current.trim()) {
50
+ lines.push(current);
51
+ }
48
52
 
49
- if (lines.length === 0) return [];
53
+ if (lines.length === 0) {
54
+ return [];
55
+ }
50
56
 
51
57
  const parseRow = (line: string) => {
52
58
  const fields: string[] = [];
@@ -57,7 +63,7 @@ export function parseCSV(csv: string): Record<string, string>[] {
57
63
  if (ch === '"') {
58
64
  if (q && line[i + 1] === '"') {
59
65
  field += '"';
60
- i++;
66
+ i += 1;
61
67
  } else {
62
68
  q = !q;
63
69
  }
@@ -107,15 +113,17 @@ export function coerceCSVRows(
107
113
  const data: Record<string, unknown> = { ...row };
108
114
  if (schema?.properties) {
109
115
  for (const [key, def] of Object.entries(schema.properties)) {
110
- if (!(key in data)) continue;
116
+ if (!(key in data)) {
117
+ continue;
118
+ }
111
119
  if (def.type === "number") {
112
120
  const raw = String(data[key] ?? "").trim();
113
121
  if (raw === "") {
114
122
  data[key] = null;
115
123
  } else {
116
- const cleaned = raw.replace(/[$€£¥,\s]/g, "");
124
+ const cleaned = raw.replaceAll(/[$€£¥,\s]/g, "");
117
125
  const n = Number(cleaned);
118
- data[key] = isNaN(n) ? null : n;
126
+ data[key] = Number.isNaN(n) ? null : n;
119
127
  }
120
128
  } else if (def.type === "boolean") {
121
129
  data[key] = data[key] === "true";
@@ -138,7 +146,7 @@ export function coerceCSVRows(
138
146
  (data.slug as string | undefined) ??
139
147
  (data.Slug as string | undefined) ??
140
148
  String(i);
141
- return { id, data, body: null };
149
+ return { body: null, data, id };
142
150
  });
143
151
  }
144
152
 
@@ -193,7 +201,7 @@ export class Csv {
193
201
  return Csv.parse(await response.text(), options);
194
202
  }
195
203
  const { readFileSync } = await import("node:fs");
196
- return Csv.parse(readFileSync(path, "utf-8"), options);
204
+ return Csv.parse(readFileSync(path, "utf8"), options);
197
205
  }
198
206
 
199
207
  /** Runtime on-demand access: load the configured source. */
package/src/html-to-jx.ts CHANGED
@@ -24,7 +24,9 @@ function convertHastChildren(children: HastNode[]) {
24
24
  const result: (JxElement | string)[] = [];
25
25
  for (const child of children) {
26
26
  const converted = convertHastNode(child);
27
- if (converted != null) result.push(converted);
27
+ if (converted != null) {
28
+ result.push(converted);
29
+ }
28
30
  }
29
31
  return result;
30
32
  }
@@ -35,7 +37,9 @@ function convertHastChildren(children: HastNode[]) {
35
37
  */
36
38
  function convertHastNode(node: HastNode) {
37
39
  if (node.type === "text") {
38
- if (whitespace(node as unknown as HastNode)) return null;
40
+ if (whitespace(node as unknown as HastNode)) {
41
+ return null;
42
+ }
39
43
  return node.value ?? null;
40
44
  }
41
45
 
@@ -44,14 +48,18 @@ function convertHastNode(node: HastNode) {
44
48
 
45
49
  if (node.properties && Object.keys(node.properties).length > 0) {
46
50
  const { style, attrs } = hastPropsToJx(node.properties);
47
- if (Object.keys(attrs).length > 0) el.attributes = attrs;
48
- if (Object.keys(style).length > 0) el.style = style;
51
+ if (Object.keys(attrs).length > 0) {
52
+ el.attributes = attrs;
53
+ }
54
+ if (Object.keys(style).length > 0) {
55
+ el.style = style;
56
+ }
49
57
  }
50
58
 
51
59
  const kids = node.children ? convertHastChildren(node.children) : [];
52
60
 
53
61
  if (kids.length === 1 && typeof kids[0] === "string") {
54
- el.textContent = kids[0];
62
+ [el.textContent] = kids;
55
63
  } else if (kids.length > 0) {
56
64
  el.children = kids as JxElement[];
57
65
  }
@@ -71,7 +79,9 @@ function hastPropsToJx(properties: Record<string, unknown>) {
71
79
  const style: Record<string, string> = {};
72
80
 
73
81
  for (const [key, value] of Object.entries(properties)) {
74
- if (value === false || value === undefined || value === null) continue;
82
+ if (value === false || value === undefined || value === null) {
83
+ continue;
84
+ }
75
85
 
76
86
  const info = find(htmlInfo, key);
77
87
  const name = info.attribute;
@@ -89,7 +99,7 @@ function hastPropsToJx(properties: Record<string, unknown>) {
89
99
  attrs[name] = String(value);
90
100
  }
91
101
  }
92
- return { style, attrs };
102
+ return { attrs, style };
93
103
  }
94
104
 
95
105
  /**
@@ -99,9 +109,13 @@ function hastPropsToJx(properties: Record<string, unknown>) {
99
109
  function parseInlineStyle(styleStr: string, out: Record<string, string>) {
100
110
  for (const decl of styleStr.split(";")) {
101
111
  const colon = decl.indexOf(":");
102
- if (colon === -1) continue;
112
+ if (colon === -1) {
113
+ continue;
114
+ }
103
115
  const prop = decl.slice(0, colon).trim();
104
116
  const val = decl.slice(colon + 1).trim();
105
- if (prop && val) out[prop] = val;
117
+ if (prop && val) {
118
+ out[prop] = val;
119
+ }
106
120
  }
107
121
  }
package/src/markdown.ts CHANGED
@@ -16,7 +16,8 @@
16
16
  */
17
17
 
18
18
  import { transpileJxMarkdown } from "./transpile.ts";
19
- import { serializeJxMarkdown, type SerializeOptions } from "./serialize.ts";
19
+ import { serializeJxMarkdown } from "./serialize.ts";
20
+ import type { SerializeOptions } from "./serialize.ts";
20
21
  import type { JxDocument } from "@jxsuite/schema/types";
21
22
  import type { ContentLoaderEntry, MarkdownFileResult } from "./types.ts";
22
23
 
@@ -76,7 +77,7 @@ export class Markdown {
76
77
  ): Promise<ContentLoaderEntry[]> {
77
78
  const { processMarkdown } = await import("./md.ts");
78
79
  const { readFileSync } = await import("node:fs");
79
- const source = readFileSync(path, "utf-8");
80
+ const source = readFileSync(path, "utf8");
80
81
  const result = processMarkdown(source, path, {
81
82
  ...(options.directives !== undefined && { directives: options.directives }),
82
83
  ...(options.directiveOptions !== undefined && {
@@ -84,17 +85,25 @@ export class Markdown {
84
85
  }),
85
86
  });
86
87
  const _meta: ContentLoaderEntry["_meta"] = {};
87
- if (result.$excerpt != null) _meta.excerpt = result.$excerpt;
88
- if (result.$toc != null) _meta.toc = result.$toc;
89
- if (result.$readingTime != null) _meta.readingTime = result.$readingTime;
90
- if (result.$wordCount != null) _meta.wordCount = result.$wordCount;
88
+ if (result.$excerpt != null) {
89
+ _meta.excerpt = result.$excerpt;
90
+ }
91
+ if (result.$toc != null) {
92
+ _meta.toc = result.$toc;
93
+ }
94
+ if (result.$readingTime != null) {
95
+ _meta.readingTime = result.$readingTime;
96
+ }
97
+ if (result.$wordCount != null) {
98
+ _meta.wordCount = result.$wordCount;
99
+ }
91
100
  return [
92
101
  {
93
- id: result.slug,
94
- data: result.frontmatter,
95
- body: source,
96
102
  $children: result.$children,
97
103
  _meta,
104
+ body: source,
105
+ data: result.frontmatter,
106
+ id: result.slug,
98
107
  },
99
108
  ];
100
109
  }
@@ -106,7 +115,7 @@ export class Markdown {
106
115
  const { resolve } = await import("node:path");
107
116
  const { src, basePath, ...processorConfig } = this.config;
108
117
  const filePath = basePath ? resolve(basePath, src) : resolve(src);
109
- const source = readFileSync(filePath, "utf-8");
118
+ const source = readFileSync(filePath, "utf8");
110
119
  return processMarkdown(source, filePath, processorConfig);
111
120
  }
112
121
  }
package/src/md.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * jxsuite/md — Markdown integration for Jx
2
+ * Jxsuite/md — Markdown integration for Jx
3
3
  *
4
4
  * Provides two exports:
5
5
  * - MarkdownFile — Parse a single markdown file (external class for $prototype)
@@ -21,7 +21,7 @@ import { readFileSync } from "node:fs";
21
21
  import { basename, extname, resolve as resolvePath } from "node:path";
22
22
  import { globSync } from "glob";
23
23
  import { mdastNodeToJx } from "./transpile.ts";
24
- import type { MarkdownFileResult, TocEntry, MdastNode } from "./types.ts";
24
+ import type { MarkdownFileResult, MdastNode, TocEntry } from "./types.ts";
25
25
  import type { JxElement } from "@jxsuite/schema/types";
26
26
 
27
27
  // ─── Tree utilities (inline to avoid Bun ESM resolution issues with unist-util-*) ──
@@ -42,10 +42,16 @@ function visit(
42
42
  const visitor = type ? maybeVisitor : typeOrVisitor;
43
43
 
44
44
  function walk(node: MdastNode) {
45
- if (!node || typeof node !== "object") return;
46
- if (!type || node.type === type) (visitor as Function)(node);
45
+ if (!node || typeof node !== "object") {
46
+ return;
47
+ }
48
+ if (!type || node.type === type) {
49
+ (visitor as (node: MdastNode) => void)(node);
50
+ }
47
51
  if (Array.isArray(node.children)) {
48
- for (const child of node.children) walk(child);
52
+ for (const child of node.children) {
53
+ walk(child);
54
+ }
49
55
  }
50
56
  }
51
57
  walk(tree as MdastNode);
@@ -58,10 +64,18 @@ function visit(
58
64
  * @returns {string}
59
65
  */
60
66
  function mdastToString(node: MdastNode): string {
61
- if (!node) return "";
62
- if (typeof node === "string") return node;
63
- if (node.value) return node.value;
64
- if (Array.isArray(node.children)) return node.children.map(mdastToString).join("");
67
+ if (!node) {
68
+ return "";
69
+ }
70
+ if (typeof node === "string") {
71
+ return node;
72
+ }
73
+ if (node.value) {
74
+ return node.value;
75
+ }
76
+ if (Array.isArray(node.children)) {
77
+ return node.children.map(mdastToString).join("");
78
+ }
65
79
  return "";
66
80
  }
67
81
 
@@ -91,11 +105,11 @@ function extractToc(tree: MdastNode) {
91
105
  const text = mdastToString(node);
92
106
  const id = text
93
107
  .toLowerCase()
94
- .replace(/[^\w\s-]/g, "")
95
- .replace(/\s+/g, "-")
96
- .replace(/-+/g, "-")
97
- .replace(/^-|-$/g, "");
98
- entries.push({ depth: node.depth as number, text, id });
108
+ .replaceAll(/[^\w\s-]/g, "")
109
+ .replaceAll(/\s+/g, "-")
110
+ .replaceAll(/-+/g, "-")
111
+ .replaceAll(/^-|-$/g, "");
112
+ entries.push({ depth: node.depth as number, id, text });
99
113
  });
100
114
  return entries;
101
115
  }
@@ -109,9 +123,13 @@ function extractToc(tree: MdastNode) {
109
123
  function extractExcerpt(tree: MdastNode) {
110
124
  let firstParagraph: MdastNode | null = null;
111
125
  visit(tree, "paragraph", (node: MdastNode) => {
112
- if (!firstParagraph) firstParagraph = node;
126
+ if (!firstParagraph) {
127
+ firstParagraph = node;
128
+ }
113
129
  });
114
- if (!firstParagraph) return "";
130
+ if (!firstParagraph) {
131
+ return "";
132
+ }
115
133
  return mdastToString(firstParagraph);
116
134
  }
117
135
 
@@ -147,7 +165,6 @@ export function processMarkdown(
147
165
 
148
166
  const tree = processor.parse(source);
149
167
  const vfile = { data: {} };
150
- // @ts-ignore - vfile shape satisfies unified's internal contract
151
168
  processor.runSync(tree, vfile);
152
169
 
153
170
  const vfileData = vfile.data as Record<string, unknown>;
@@ -161,19 +178,20 @@ export function processMarkdown(
161
178
  const bodyNodes = tree.children.filter(
162
179
  (n: MdastNode) => n.type !== "yaml" && n.type !== "toml",
163
180
  ) as MdastNode[];
164
- const $children = bodyNodes
165
- .map(/** @type {(n: MdastNode) => any} */ mdastNodeToJx)
166
- .filter(Boolean) as (JxElement | string)[];
181
+ const $children = bodyNodes.map((n: MdastNode) => mdastNodeToJx(n)).filter(Boolean) as (
182
+ | JxElement
183
+ | string
184
+ )[];
167
185
 
168
186
  return {
169
- slug,
170
- path: filePath,
171
- frontmatter,
172
187
  $children,
173
188
  $excerpt: excerpt,
174
- $toc: toc,
175
189
  $readingTime: readingTime(plainText),
190
+ $toc: toc,
176
191
  $wordCount: plainText.split(/\s+/).filter(Boolean).length,
192
+ frontmatter,
193
+ path: filePath,
194
+ slug,
177
195
  };
178
196
  }
179
197
 
@@ -185,12 +203,11 @@ export function processMarkdown(
185
203
  * @returns {unknown}
186
204
  */
187
205
  function getNestedValue(obj: Record<string, unknown> | MarkdownFileResult, path: string) {
188
- return path
189
- .split(".")
190
- .reduce(
191
- (o: unknown, k) => (o as Record<string, unknown> | undefined)?.[k],
192
- /** @type {unknown} */ obj,
193
- );
206
+ let current: unknown = obj;
207
+ for (const k of path.split(".")) {
208
+ current = (current as Record<string, unknown> | undefined)?.[k];
209
+ }
210
+ return current;
194
211
  }
195
212
 
196
213
  // ─── Markdown format class (re-exported from browser-safe module) ────────────
@@ -266,22 +283,28 @@ export class MarkdownCollection {
266
283
  const files = globSync(pattern, { absolute: true });
267
284
 
268
285
  const results = files.map((filePath: string) => {
269
- const source = readFileSync(filePath, "utf-8");
286
+ const source = readFileSync(filePath, "utf8");
270
287
  return processMarkdown(source, filePath, processorConfig);
271
288
  });
272
289
 
273
290
  // Filter
274
291
  let filtered = results;
275
292
  if (typeof filter === "function") {
276
- filtered = results.filter(/** @type {(r: MarkdownFileResult) => boolean} */ filter);
293
+ filtered = results.filter((r: MarkdownFileResult) =>
294
+ (filter as (r: MarkdownFileResult) => boolean)(r),
295
+ );
277
296
  }
278
297
 
279
298
  // Sort
280
299
  filtered.sort((a: MarkdownFileResult, b: MarkdownFileResult) => {
281
300
  const aVal = getNestedValue(a, sortBy) ?? ("" as string | number);
282
301
  const bVal = getNestedValue(b, sortBy) ?? ("" as string | number);
283
- if (aVal < bVal) return sortOrder === "asc" ? -1 : 1;
284
- if (aVal > bVal) return sortOrder === "asc" ? 1 : -1;
302
+ if (aVal < bVal) {
303
+ return sortOrder === "asc" ? -1 : 1;
304
+ }
305
+ if (aVal > bVal) {
306
+ return sortOrder === "asc" ? 1 : -1;
307
+ }
285
308
  return 0;
286
309
  });
287
310