@jxsuite/parser 1.8.2 → 2.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/serialize.js CHANGED
@@ -39288,6 +39288,148 @@ var require_dist2 = __commonJS(function(exports, module) {
39288
39288
  module.exports = plugin;
39289
39289
  });
39290
39290
 
39291
+ // src/alerts.ts
39292
+ function normalizeAlerts(config, where = "alerts") {
39293
+ if (config === false) {
39294
+ return null;
39295
+ }
39296
+ const types = new Map(Object.keys(DEFAULT_ALERT_TITLES).map((type) => [type, null]));
39297
+ const disabled = new Set;
39298
+ if (config === undefined || config === true) {
39299
+ return { disabled, types };
39300
+ }
39301
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
39302
+ throw new TypeError(`${where}: expected an object mapping alert types to element names, or false`);
39303
+ }
39304
+ for (const [type, element] of Object.entries(config)) {
39305
+ const key = type.toLowerCase();
39306
+ if (!/^[a-z][\w-]*$/.test(key)) {
39307
+ throw new TypeError(`${where}: "${type}" is not a valid alert type name`);
39308
+ }
39309
+ if (element === false || element === null) {
39310
+ types.delete(key);
39311
+ disabled.add(key);
39312
+ } else if (element === true) {
39313
+ types.set(key, null);
39314
+ } else if (typeof element === "string" && TAG_NAME.test(element)) {
39315
+ types.set(key, element);
39316
+ } else {
39317
+ throw new TypeError(`${where}.${type}: expected an element name such as "doc-note", true or false, got ${JSON.stringify(element)}`);
39318
+ }
39319
+ }
39320
+ return { disabled, types };
39321
+ }
39322
+ function inlineText(nodes) {
39323
+ let out = "";
39324
+ for (const node of nodes) {
39325
+ if (typeof node.value === "string") {
39326
+ out += node.value;
39327
+ } else if (node.children) {
39328
+ out += inlineText(node.children);
39329
+ }
39330
+ }
39331
+ return out;
39332
+ }
39333
+ function textNode(value) {
39334
+ return { type: "text", value };
39335
+ }
39336
+ function markerOf(paragraph) {
39337
+ const [first] = paragraph.children ?? [];
39338
+ if (first?.type !== "text" || typeof first.value !== "string") {
39339
+ return null;
39340
+ }
39341
+ const match = MARKER2.exec(first.value);
39342
+ if (!match) {
39343
+ return null;
39344
+ }
39345
+ const rest = first.value.slice(match[0].length);
39346
+ if (rest !== "" && !/^[ \t\r\n]/.test(rest)) {
39347
+ return null;
39348
+ }
39349
+ return { rest, type: match[1].toLowerCase() };
39350
+ }
39351
+ function splitMarker(paragraph, settings) {
39352
+ const marker = markerOf(paragraph);
39353
+ if (!marker || !settings.types.has(marker.type)) {
39354
+ return null;
39355
+ }
39356
+ const { type, rest } = marker;
39357
+ const [, ...siblings] = paragraph.children ?? [];
39358
+ const title = [];
39359
+ const body = [];
39360
+ let inTitle = true;
39361
+ const take = (node) => {
39362
+ if (inTitle && node.type === "break") {
39363
+ inTitle = false;
39364
+ } else if (inTitle && node.type === "text" && typeof node.value === "string") {
39365
+ const lineEnd = LINE_END.exec(node.value);
39366
+ if (lineEnd === null) {
39367
+ title.push(node);
39368
+ } else {
39369
+ title.push(textNode(node.value.slice(0, lineEnd.index)));
39370
+ inTitle = false;
39371
+ body.push(textNode(node.value.slice(lineEnd.index + lineEnd[0].length)));
39372
+ }
39373
+ } else {
39374
+ (inTitle ? title : body).push(node);
39375
+ }
39376
+ };
39377
+ take(textNode(rest.replace(/^[ \t]+/, "")));
39378
+ for (const node of siblings) {
39379
+ take(node);
39380
+ }
39381
+ const trimmed = title.filter((node) => !(node.type === "text" && node.value === ""));
39382
+ const text = inlineText(trimmed);
39383
+ return {
39384
+ body: body.filter((node) => !(node.type === "text" && node.value === "")),
39385
+ title: text.trim() === "" ? [] : trimmed,
39386
+ type
39387
+ };
39388
+ }
39389
+ function transformAlerts(tree, settings, onUnknown) {
39390
+ const { children } = tree;
39391
+ if (!children) {
39392
+ return;
39393
+ }
39394
+ for (const [i, child] of children.entries()) {
39395
+ if (child.type === "blockquote") {
39396
+ const [paragraph, ...rest] = child.children ?? [];
39397
+ const split = paragraph?.type === "paragraph" ? splitMarker(paragraph, settings) : null;
39398
+ if (split) {
39399
+ const bodyParagraph = split.body.length > 0 && inlineText(split.body).trim() !== "" ? [{ children: split.body, type: "paragraph" }] : [];
39400
+ const alert = {
39401
+ alert: split.type,
39402
+ children: [...bodyParagraph, ...rest],
39403
+ element: settings.types.get(split.type) ?? null,
39404
+ titleNodes: split.title.length > 0 ? split.title : null,
39405
+ type: ALERT_NODE
39406
+ };
39407
+ children[i] = alert;
39408
+ transformAlerts(alert, settings, onUnknown);
39409
+ continue;
39410
+ }
39411
+ const unknown = paragraph?.type === "paragraph" ? markerOf(paragraph) : null;
39412
+ if (unknown && !settings.disabled.has(unknown.type)) {
39413
+ onUnknown?.(unknown.type);
39414
+ }
39415
+ }
39416
+ transformAlerts(child, settings, onUnknown);
39417
+ }
39418
+ }
39419
+ var DEFAULT_ALERT_TITLES, ALERT_NODE = "jxAlert", TAG_NAME, MARKER2, LINE_END;
39420
+ var init_alerts = __esm(() => {
39421
+ DEFAULT_ALERT_TITLES = {
39422
+ caution: "Caution",
39423
+ important: "Important",
39424
+ note: "Note",
39425
+ tip: "Tip",
39426
+ warning: "Warning"
39427
+ };
39428
+ TAG_NAME = /^[A-Za-z][\w.-]*$/;
39429
+ MARKER2 = /^\[!([A-Za-z][\w-]*)\][+-]?/;
39430
+ LINE_END = /\r?\n/;
39431
+ });
39432
+
39291
39433
  // src/transpile.ts
39292
39434
  function jxKey(key) {
39293
39435
  if (JX_DOLLAR_KEYS.has(key)) {
@@ -39360,6 +39502,9 @@ function mdastNodeToJx(node) {
39360
39502
  if (node.type === "containerDirective" || node.type === "leafDirective" || node.type === "textDirective") {
39361
39503
  return directiveToJx(node);
39362
39504
  }
39505
+ if (node.type === ALERT_NODE) {
39506
+ return alertToJx(node);
39507
+ }
39363
39508
  if (node.type === "text") {
39364
39509
  return node.value ?? null;
39365
39510
  }
@@ -39472,6 +39617,37 @@ function mdastNodeToJx(node) {
39472
39617
  }
39473
39618
  return el;
39474
39619
  }
39620
+ function alertToJx(node) {
39621
+ const body = convertChildren(node.children ?? []);
39622
+ if (node.element) {
39623
+ const attributes = { "data-alert": node.alert };
39624
+ if (node.titleNodes) {
39625
+ attributes["data-title"] = inlineText(node.titleNodes).trim();
39626
+ }
39627
+ const el = { attributes, tagName: node.element };
39628
+ if (body.length > 0) {
39629
+ el.children = body;
39630
+ }
39631
+ return el;
39632
+ }
39633
+ const title = { className: "jx-alert-title", tagName: "p" };
39634
+ if (node.titleNodes) {
39635
+ const inline = convertChildren(node.titleNodes);
39636
+ if (inline.length === 1 && typeof inline[0] === "string") {
39637
+ [title.textContent] = inline;
39638
+ } else {
39639
+ title.children = inline;
39640
+ }
39641
+ } else {
39642
+ title.textContent = DEFAULT_ALERT_TITLES[node.alert] ?? `${node.alert.charAt(0).toUpperCase()}${node.alert.slice(1)}`;
39643
+ }
39644
+ return {
39645
+ attributes: { "data-alert": node.alert, role: "note" },
39646
+ children: [title, ...body],
39647
+ className: `jx-alert jx-alert-${node.alert}`,
39648
+ tagName: "div"
39649
+ };
39650
+ }
39475
39651
  function directiveToJx(node) {
39476
39652
  if (PROTOTYPE_DIRECTIVE_NAMES.has(node.name)) {
39477
39653
  return prototypeDirectiveToJx(node);
@@ -39665,6 +39841,7 @@ var init_transpile = __esm(() => {
39665
39841
  init_remark_gfm();
39666
39842
  init_remark_directive();
39667
39843
  init_html_to_jx();
39844
+ init_alerts();
39668
39845
  init_html_to_jx();
39669
39846
  import_remark_parse_frontmatter = __toESM(require_dist2(), 1);
39670
39847
  JX_DOLLAR_KEYS = new Set(["prototype", "ref", "component", "props", "switch", "elements"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/parser",
3
- "version": "1.8.2",
3
+ "version": "2.0.0",
4
4
  "description": "Jx markdown parser and external class integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,10 +48,10 @@
48
48
  "upgrade": "bun update --latest && bun install"
49
49
  },
50
50
  "dependencies": {
51
- "@jxsuite/markup": "^0.4.11",
52
- "@jxsuite/schema": "^2.3.0",
53
- "@shikijs/langs": "^4.4.3",
54
- "@shikijs/themes": "^4.4.3",
51
+ "@jxsuite/markup": "^0.4.12",
52
+ "@jxsuite/schema": "^3.0.0",
53
+ "@shikijs/langs": "^4.5.0",
54
+ "@shikijs/themes": "^4.5.0",
55
55
  "glob": "^13.0.6",
56
56
  "mdast-util-to-string": "^4.0.0",
57
57
  "remark-directive": "^4.0.0",
@@ -60,14 +60,14 @@
60
60
  "remark-parse": "^11.0.0",
61
61
  "remark-parse-frontmatter": "^1.0.3",
62
62
  "remark-stringify": "^11.0.0",
63
- "shiki": "^4.4.3",
63
+ "shiki": "^4.5.0",
64
64
  "unified": "^11.0.5",
65
65
  "unist-util-visit": "^5.1.0",
66
66
  "yaml": "^2.9.1"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@happy-dom/global-registrator": "^20.14.5",
70
- "@jxsuite/runtime": "^4.0.2",
70
+ "@jxsuite/runtime": "^4.0.3",
71
71
  "@types/mdast": "^4.0.4",
72
72
  "ajv": "^8.20.0"
73
73
  },
@@ -9,7 +9,7 @@
9
9
  "description": "File-based content collections. Each key names a content type; entries are loaded from the named source through the format registry.",
10
10
  "type": "object",
11
11
  "additionalProperties": {
12
- "description": "Content type definition. Defines the source directory, frontmatter schema, and element dependencies.",
12
+ "description": "Content type definition. Defines the source, how entries are filtered, identified and routed, and their schema and element dependencies.",
13
13
  "type": "object",
14
14
  "properties": {
15
15
  "$elements": {
@@ -19,10 +19,16 @@
19
19
  "oneOf": [
20
20
  {
21
21
  "type": "object",
22
- "properties": { "$ref": { "type": "string" } },
22
+ "properties": {
23
+ "$ref": {
24
+ "type": "string"
25
+ }
26
+ },
23
27
  "required": ["$ref"]
24
28
  },
25
- { "type": "string" }
29
+ {
30
+ "type": "string"
31
+ }
26
32
  ]
27
33
  }
28
34
  },
@@ -34,16 +40,22 @@
34
40
  "description": "JSON Schema for validating frontmatter of content type entries.",
35
41
  "type": "object",
36
42
  "properties": {
37
- "type": { "const": "object" },
43
+ "type": {
44
+ "const": "object"
45
+ },
38
46
  "properties": {
39
47
  "description": "Field definitions; values are field schemas from the effective project field union.",
40
48
  "type": "object",
41
- "additionalProperties": { "$ref": "https://jxsuite.com/schema/project/fields/v2" }
49
+ "additionalProperties": {
50
+ "$ref": "https://jxsuite.com/schema/project/fields/v2"
51
+ }
42
52
  },
43
53
  "required": {
44
54
  "description": "Frontmatter fields every entry must provide.",
45
55
  "type": "array",
46
- "items": { "type": "string" }
56
+ "items": {
57
+ "type": "string"
58
+ }
47
59
  }
48
60
  }
49
61
  },
@@ -51,6 +63,126 @@
51
63
  "description": "Path to the content directory relative to the project root.",
52
64
  "examples": ["./content/blog/", "./content/docs/"],
53
65
  "type": "string"
66
+ },
67
+ "exclude": {
68
+ "description": "Glob patterns, relative to the source directory, for files that are never read: `internal/**`, `STYLE.md`, `**/node_modules/**`, `.*/**`. `*` and `?` stop at `/`, `**` crosses directories, `{a,b}` alternates, and dotfiles are ordinary files. A bare name with no extension (`internal`) excludes that folder as well as a file of the name. A pattern cannot climb out of the source (`../`). Excluded files are not parsed, validated, routed, linked to, searched or published, and the collection's asset mount does not serve them either: a note that refers to one gets a warning and the reference is left as written.",
69
+ "type": "array",
70
+ "items": {
71
+ "type": "string",
72
+ "minLength": 1
73
+ },
74
+ "examples": [["internal/**", "STYLE.md", ".*/**", "**/node_modules/**"]]
75
+ },
76
+ "where": {
77
+ "description": "Declarative frontmatter filter: only entries whose frontmatter satisfies every key are kept. A value is a literal to equal (a scalar also matches an array field that contains it; an array or an object with no `$` keys is compared whole) or an operator object: `$eq`, `$ne`, `$in`, `$nin`, `$exists`, `$gt`, `$gte`, `$lt`, `$lte`. Keys combine with AND; there is no OR and nothing is evaluated. A filter that removes every entry is reported. Filtered entries are not validated, routed, linked to, queried or searched, and are not served from the collection's asset mount.",
78
+ "type": "object",
79
+ "propertyNames": {
80
+ "pattern": "^[^$]"
81
+ },
82
+ "additionalProperties": {
83
+ "anyOf": [
84
+ {
85
+ "type": "object",
86
+ "propertyNames": {
87
+ "enum": ["$eq", "$ne", "$in", "$nin", "$exists", "$gt", "$gte", "$lt", "$lte"]
88
+ },
89
+ "minProperties": 1,
90
+ "properties": {
91
+ "$eq": {},
92
+ "$ne": {},
93
+ "$in": {
94
+ "type": "array"
95
+ },
96
+ "$nin": {
97
+ "type": "array"
98
+ },
99
+ "$exists": {
100
+ "type": "boolean"
101
+ },
102
+ "$gt": {
103
+ "type": ["string", "number"]
104
+ },
105
+ "$gte": {
106
+ "type": ["string", "number"]
107
+ },
108
+ "$lt": {
109
+ "type": ["string", "number"]
110
+ },
111
+ "$lte": {
112
+ "type": ["string", "number"]
113
+ }
114
+ }
115
+ },
116
+ {
117
+ "type": ["string", "number", "boolean", "null", "array"]
118
+ },
119
+ {
120
+ "type": "object",
121
+ "propertyNames": {
122
+ "pattern": "^[^$]"
123
+ }
124
+ }
125
+ ]
126
+ },
127
+ "examples": [
128
+ {
129
+ "publish": true,
130
+ "status": {
131
+ "$ne": "draft"
132
+ }
133
+ }
134
+ ]
135
+ },
136
+ "idField": {
137
+ "description": "Frontmatter field whose value is the entry id, instead of the path-derived id. An entry without the field keeps its path id and is reported.",
138
+ "type": "string",
139
+ "minLength": 1,
140
+ "examples": ["slug"]
141
+ },
142
+ "route": {
143
+ "description": "A site-absolute path template. `{name}` inserts the entry id (`id`), the file's directory (`dir`), its name (`file`) or a frontmatter field (dotted paths allowed); `{name:slug}` slugifies it, and `lower`, `upper` and `raw` are also available.",
144
+ "examples": ["/kb/{category:slug}/{slug}/"],
145
+ "type": "string",
146
+ "pattern": "^/"
147
+ },
148
+ "indexRoute": {
149
+ "description": "The route template for a directory's `README.md` or `index.md`, the file a folder stands for, when it should differ from `route` (a section's page at `/kb/{dir:slug}/` rather than `/kb/{category:slug}/{slug}/`). It takes the same placeholders and transforms as `route`, and needs a `route` for every other entry.",
150
+ "examples": ["/kb/{dir:slug}/"],
151
+ "type": "string",
152
+ "pattern": "^/"
153
+ },
154
+ "links": {
155
+ "description": "How a relative link to an entry that has no page (excluded, filtered out by `where`, unrouted, or missing) is reported. The link renders as plain text in every mode. Relative links are only rewritten for content types that declare a `route`.",
156
+ "enum": ["warn", "error", "ignore"],
157
+ "default": "warn"
158
+ },
159
+ "alerts": {
160
+ "description": "How GitHub-style alerts (`> [!NOTE]`) in Markdown entries render. `false` turns callouts off for the whole type; `true`, or leaving the option out, renders NOTE, TIP, IMPORTANT, WARNING and CAUTION as built-in accessible markup. An object maps an alert type (case-insensitive) to the custom element that renders it, e.g. { \"NOTE\": \"doc-note\" }; `true` renders a type with the built-in markup (how a type beyond GitHub's five, such as Obsidian's `info`, is switched on); null or false leaves a type an ordinary blockquote. A well-formed marker of a type that is not enabled is reported when the file loads.",
161
+ "type": ["object", "boolean"],
162
+ "propertyNames": {
163
+ "pattern": "^[A-Za-z][\\w-]*$"
164
+ },
165
+ "additionalProperties": {
166
+ "oneOf": [
167
+ {
168
+ "type": "string",
169
+ "pattern": "^[A-Za-z][\\w.-]*$"
170
+ },
171
+ {
172
+ "type": "null"
173
+ },
174
+ {
175
+ "type": "boolean"
176
+ }
177
+ ]
178
+ },
179
+ "examples": [
180
+ {
181
+ "NOTE": "doc-note",
182
+ "TIP": "doc-tip",
183
+ "WARNING": "doc-warning"
184
+ }
185
+ ]
54
186
  }
55
187
  }
56
188
  }
@@ -115,14 +115,17 @@
115
115
  "properties": {
116
116
  "data": { "type": "object" },
117
117
  "projectConfig": { "type": "object" },
118
- "root": { "type": "string" }
118
+ "root": { "type": "string" },
119
+ "locale": { "type": ["string", "null"] },
120
+ "urlPattern": { "type": "string" },
121
+ "params": { "type": "array", "items": { "type": "string" } }
119
122
  }
120
123
  },
121
- "description": "Host context: { data, projectConfig, root } where data is the projectData result"
124
+ "description": "Host context: { data, projectConfig, root, locale, urlPattern, params } where data is the projectData result and urlPattern/params describe the dynamic page being expanded"
122
125
  }
123
126
  ],
124
127
  "returnType": { "$ref": "#/$defs/returnTypes/PathEntries" },
125
- "description": "Expand a content-type $paths source into one route-param object per entry (param defaults to \"slug\", field to \"id\")"
128
+ "description": "Expand a content-type $paths source into one route-param object per entry (param defaults to \"slug\", field to \"id\"); a content type with a route and no param or field derives each entry's parameters from the page's own URL pattern"
126
129
  },
127
130
  "assets": {
128
131
  "role": "assets",
@@ -233,12 +233,19 @@
233
233
  "identifier": "options",
234
234
  "type": {
235
235
  "type": "object",
236
- "properties": { "baseDir": { "type": "string" } }
236
+ "properties": {
237
+ "baseDir": { "type": "string" },
238
+ "exclude": {
239
+ "type": "array",
240
+ "items": { "type": "string" },
241
+ "description": "Glob patterns, relative to the source directory, for files and directories to leave out"
242
+ }
243
+ }
237
244
  }
238
245
  }
239
246
  ],
240
247
  "returnType": { "type": "array", "items": { "type": "string" } },
241
- "description": "List markdown entry files for a content-type source"
248
+ "description": "List markdown entry files for a content-type source, in sorted order"
242
249
  },
243
250
  "load": {
244
251
  "role": "load",
@@ -259,7 +266,11 @@
259
266
  "type": "object",
260
267
  "properties": {
261
268
  "directiveOptions": { "type": "object" },
262
- "directives": { "type": "boolean" }
269
+ "directives": { "type": "boolean" },
270
+ "alerts": {
271
+ "description": "GitHub alert type -> custom element name (or false to leave alerts as blockquotes); absent renders the built-in callouts",
272
+ "oneOf": [{ "const": false }, { "type": "object" }]
273
+ }
263
274
  }
264
275
  }
265
276
  }