@jxsuite/parser 1.0.0 → 1.2.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/README.md +107 -66
- package/dist/content-loader.js +16 -2
- package/dist/markdown.js +10650 -777
- package/dist/serialize.js +991 -579
- package/package.json +7 -4
- package/src/Csv.class.json +1 -1
- package/src/content-loader.ts +23 -4
- package/src/highlight.ts +112 -0
- package/src/markdown.ts +7 -0
- package/src/md.ts +38 -27
- package/src/transpile.ts +102 -1
package/README.md
CHANGED
|
@@ -1,115 +1,156 @@
|
|
|
1
1
|
# `@jxsuite/parser`
|
|
2
2
|
|
|
3
|
-
> Markdown
|
|
3
|
+
> Content & Markdown extension for Jx — file-based content collections with Markdown and CSV formats.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
`@jxsuite/parser`
|
|
7
|
+
`@jxsuite/parser` is the first-party content extension. Its `jx-extension.json` manifest exposes six classes plus project/document schema fragments:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| Class | Kind | Purpose |
|
|
10
|
+
| -------------------- | --------------- | -------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `Markdown` | Format (`.md`) | Parse/serialize Jx Markdown documents, load markdown content entries, resolve a file at runtime |
|
|
12
|
+
| `Csv` | Format (`.csv`) | Parse local CSV files or remote CSV URLs into typed content entries |
|
|
13
|
+
| `Content` | Project section | Loads the project.json `content` section into `_project.content` and expands `$paths` route params |
|
|
14
|
+
| `ContentCollection` | Runtime class | Query a content type with filter, sort, and limit |
|
|
15
|
+
| `ContentEntry` | Runtime class | Fetch a single entry from a content type by ID (or another field) |
|
|
16
|
+
| `MarkdownCollection` | Runtime class | Glob markdown files into a sorted, filterable array |
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
Built on the [`unified`](https://unifiedjs.com/) / remark ecosystem.
|
|
19
|
+
|
|
20
|
+
## Installation & registration
|
|
12
21
|
|
|
13
22
|
```bash
|
|
14
23
|
bun add @jxsuite/parser
|
|
15
24
|
```
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Parses a single Markdown file into structured data.
|
|
26
|
+
Registration is manifest-based — add the package to `project.json`:
|
|
20
27
|
|
|
21
28
|
```json
|
|
22
29
|
{
|
|
23
|
-
"
|
|
24
|
-
"post": {
|
|
25
|
-
"$prototype": "MarkdownFile",
|
|
26
|
-
"$src": "@jxsuite/parser/MarkdownFile.class.json",
|
|
27
|
-
"src": "./content/posts/hello-world.md"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
+
"extensions": ["@jxsuite/parser"]
|
|
30
31
|
}
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
That one entry registers the `.md`/`.csv` formats, the `content` project section, and the classes: `$prototype: "ContentEntry"` (etc.) resolves by name through the project's extension registry — no `$src` or imports wiring. There are no implicit defaults: a project without the extension supports only `.json` documents.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
| -------------- | -------- | ----------------------------------- |
|
|
37
|
-
| `slug` | `string` | Filename without extension |
|
|
38
|
-
| `path` | `string` | Full file path |
|
|
39
|
-
| `frontmatter` | `object` | Parsed YAML frontmatter |
|
|
40
|
-
| `$body` | `string` | Rendered HTML body |
|
|
41
|
-
| `$excerpt` | `string` | First paragraph as HTML |
|
|
42
|
-
| `$toc` | `array` | Table of contents (id, text, depth) |
|
|
43
|
-
| `$readingTime` | `number` | Estimated reading time in minutes |
|
|
44
|
-
| `$wordCount` | `number` | Word count |
|
|
36
|
+
## Content collections
|
|
45
37
|
|
|
46
|
-
|
|
38
|
+
Declare content types in the `content` section (validated by this package's project schema fragment):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"content": {
|
|
43
|
+
"docs": {
|
|
44
|
+
"source": "./content/docs",
|
|
45
|
+
"format": "Markdown",
|
|
46
|
+
"schema": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": { "title": { "type": "string" }, "date": { "type": "string" } }
|
|
49
|
+
},
|
|
50
|
+
"$elements": [{ "$ref": "./components/doc-note.json" }]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
Query them from any page or component:
|
|
49
57
|
|
|
50
58
|
```json
|
|
51
59
|
{
|
|
52
60
|
"state": {
|
|
53
61
|
"posts": {
|
|
54
|
-
"$prototype": "
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"sortBy": "date",
|
|
58
|
-
"sortOrder": "desc",
|
|
62
|
+
"$prototype": "ContentCollection",
|
|
63
|
+
"contentType": "docs",
|
|
64
|
+
"filter": [{ "field": "draft", "op": "!=", "value": true }],
|
|
59
65
|
"limit": 10
|
|
66
|
+
},
|
|
67
|
+
"page": {
|
|
68
|
+
"$prototype": "ContentEntry",
|
|
69
|
+
"contentType": "docs",
|
|
70
|
+
"id": { "$ref": "#/$params/slug" }
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
74
|
```
|
|
64
75
|
|
|
65
|
-
|
|
76
|
+
`ContentEntry` matches on the entry id (slug) by default; set `field` to match a frontmatter field (e.g. `"sku"`) instead.
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
| ----------- | -------- | -------- | ----------------------------------- |
|
|
69
|
-
| `src` | `string` | — | Glob pattern for Markdown files |
|
|
70
|
-
| `sortBy` | `string` | `"date"` | Frontmatter field to sort by |
|
|
71
|
-
| `sortOrder` | `string` | `"desc"` | `"asc"` or `"desc"` |
|
|
72
|
-
| `limit` | `number` | — | Maximum number of results |
|
|
73
|
-
| `filter` | `string` | — | Frontmatter field filter expression |
|
|
78
|
+
## `Markdown`
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
One class carries every format capability:
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
- **static `parse`** — Jx Markdown source → Jx document (browser-safe; Studio opens `.md` files with it)
|
|
83
|
+
- **static `serialize`** — Jx document → markdown, `roundtrip` (lossless, default) or `export` (lossy clean markdown)
|
|
84
|
+
- **static `discover` / `load`** — compile-time content access: frontmatter → `data`, source preserved as `body`, parsed `$children`, and `_meta` (excerpt, toc, reading time, word count)
|
|
85
|
+
- **instance `resolve`** — runtime on-demand access for a `$prototype: "Markdown"` state entry
|
|
78
86
|
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"state": {
|
|
90
|
+
"post": { "$prototype": "Markdown", "src": "./content/posts/hello-world.md" }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
83
93
|
```
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
**Resolved value** (`MarkdownFileResult`):
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
| Property | Type | Description |
|
|
98
|
+
| -------------- | -------- | ------------------------------------------- |
|
|
99
|
+
| `slug` | `string` | Entry id (path-based under the source root) |
|
|
100
|
+
| `path` | `string` | Full file path |
|
|
101
|
+
| `frontmatter` | `object` | Parsed YAML frontmatter |
|
|
102
|
+
| `$children` | `array` | Body as Jx element nodes |
|
|
103
|
+
| `$excerpt` | `string` | First paragraph |
|
|
104
|
+
| `$toc` | `array` | Table of contents (id, text, depth) |
|
|
105
|
+
| `$readingTime` | `number` | Estimated reading time in minutes |
|
|
106
|
+
| `$wordCount` | `number` | Word count |
|
|
107
|
+
|
|
108
|
+
### Directives
|
|
109
|
+
|
|
110
|
+
`:::name{attrs}` container syntax converts directly to Jx element nodes — `:::my-card{title="Hello"}` becomes a `my-card` element with its attributes routed to the right Jx locations (props, `$`-annotations, styles). Capitalized directives (e.g. `:::Array`) become tagName-less `$prototype` nodes. A content type's `$elements` list declares which components its entries may use.
|
|
111
|
+
|
|
112
|
+
## `Csv`
|
|
92
113
|
|
|
93
|
-
|
|
114
|
+
CSV content format (`documentKinds: ["content"]`; Studio opens `.csv` in grid/source modes).
|
|
94
115
|
|
|
95
|
-
|
|
116
|
+
| Option | Type | Description |
|
|
117
|
+
| --------- | -------- | ----------------------------------------------------------------- |
|
|
118
|
+
| `src` | `string` | Path or http(s) URL to a CSV file (remote sources supported) |
|
|
119
|
+
| `schema` | `object` | Per-column type schema for coercion (number, boolean, array) |
|
|
120
|
+
| `idField` | `string` | Column used as entry id (default chain: id, sku, slug, row index) |
|
|
96
121
|
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
## `MarkdownCollection`
|
|
123
|
+
|
|
124
|
+
Globs markdown files into a sorted array of `MarkdownFileResult`s (runtime alternative to the content section):
|
|
99
125
|
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"state": {
|
|
129
|
+
"posts": {
|
|
130
|
+
"$prototype": "MarkdownCollection",
|
|
131
|
+
"src": "./content/posts/*.md",
|
|
132
|
+
"sortBy": "frontmatter.date",
|
|
133
|
+
"sortOrder": "desc",
|
|
134
|
+
"limit": 10
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
102
138
|
```
|
|
103
139
|
|
|
104
|
-
|
|
140
|
+
Options: `src` (glob), `sortBy` (dot-path, default `"frontmatter.date"`), `sortOrder` (`"asc"`/`"desc"`), `limit`, `filter` (predicate), `basePath`, `directives`.
|
|
141
|
+
|
|
142
|
+
## Module exports
|
|
105
143
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
144
|
+
| Export | Contents |
|
|
145
|
+
| -------------------------------- | ---------------------------------------------------------------------- |
|
|
146
|
+
| `@jxsuite/parser` | `processMarkdown`, `Markdown`, `MarkdownCollection` |
|
|
147
|
+
| `@jxsuite/parser/markdown` | The `Markdown` format class (browser-safe parse/serialize) |
|
|
148
|
+
| `@jxsuite/parser/csv` | `Csv`, `parseCSV`, `coerceCSVRows` |
|
|
149
|
+
| `@jxsuite/parser/content` | `ContentCollection`, `ContentEntry`, pure query functions |
|
|
150
|
+
| `@jxsuite/parser/content-loader` | `Content` project-section loader |
|
|
151
|
+
| `@jxsuite/parser/transpile` | `transpileJxMarkdown` (markdown source → Jx document), `isJxMarkdown` |
|
|
152
|
+
| `@jxsuite/parser/serialize` | `serializeJxMarkdown`, `jxToMdast`, `mdastToJx`, markdown element sets |
|
|
153
|
+
| `@jxsuite/parser/*.class.json` | The class descriptors named by the manifest |
|
|
113
154
|
|
|
114
155
|
## License
|
|
115
156
|
|
package/dist/content-loader.js
CHANGED
|
@@ -155,11 +155,13 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry) {
|
|
|
155
155
|
const files = entry.capabilities.discover ? await entry.call("discover", source, {
|
|
156
156
|
baseDir: projectRoot
|
|
157
157
|
}) : [resolvedSource];
|
|
158
|
+
const sourceRoot = extname(source) ? undefined : resolvedSource;
|
|
158
159
|
const entries = [];
|
|
159
160
|
for (const filePath of files) {
|
|
160
161
|
entries.push(...await entry.call("load", filePath, {
|
|
161
162
|
directiveOptions,
|
|
162
|
-
schema
|
|
163
|
+
schema,
|
|
164
|
+
...sourceRoot !== undefined && { sourceRoot }
|
|
163
165
|
}));
|
|
164
166
|
}
|
|
165
167
|
if (schema) {
|
|
@@ -225,6 +227,7 @@ function resolveContentTypeRefs(contentTypes, section) {
|
|
|
225
227
|
}
|
|
226
228
|
const refEntries = contentTypes.get(target);
|
|
227
229
|
if (!refEntries) {
|
|
230
|
+
console.warn(`Content relationships: "${name}" field "${field}" references unknown content type "${target}"`);
|
|
228
231
|
continue;
|
|
229
232
|
}
|
|
230
233
|
for (const entry of entries) {
|
|
@@ -233,9 +236,20 @@ function resolveContentTypeRefs(contentTypes, section) {
|
|
|
233
236
|
const resolved = refEntries.find((e) => e.id === value);
|
|
234
237
|
if (resolved) {
|
|
235
238
|
entry.data[field] = resolved;
|
|
239
|
+
} else {
|
|
240
|
+
console.warn(`Content relationships: "${name}/${entry.id}" field "${field}" references missing "${target}" entry "${value}"`);
|
|
236
241
|
}
|
|
237
242
|
} else if (toMany && Array.isArray(value)) {
|
|
238
|
-
entry.data[field] = value.map((id) =>
|
|
243
|
+
entry.data[field] = value.map((id) => {
|
|
244
|
+
if (typeof id !== "string") {
|
|
245
|
+
return id;
|
|
246
|
+
}
|
|
247
|
+
const resolved = refEntries.find((e) => e.id === id);
|
|
248
|
+
if (!resolved) {
|
|
249
|
+
console.warn(`Content relationships: "${name}/${entry.id}" field "${field}" references missing "${target}" entry "${id}"`);
|
|
250
|
+
}
|
|
251
|
+
return resolved ?? id;
|
|
252
|
+
});
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
}
|