@jxsuite/parser 0.35.0 → 1.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 +271 -0
- package/dist/content.js +2 -2
- package/dist/markdown.js +2 -2
- package/dist/serialize.js +2 -2
- package/jx-extension.json +17 -0
- package/package.json +19 -16
- package/schemas/document.fragment.schema.json +27 -0
- package/schemas/project.fragment.schema.json +59 -0
- package/src/Content.class.json +117 -0
- package/src/ContentCollection.class.json +3 -3
- package/src/ContentEntry.class.json +1 -1
- package/src/content-loader.ts +480 -0
- package/src/content.ts +6 -4
- package/src/html-to-jx.ts +8 -117
- package/src/md-html.ts +5 -42
- package/src/serialize.ts +1 -1
- package/src/transpile.ts +2 -2
- package/src/types.ts +15 -18
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, {
|
|
40
|
+
get: all[name],
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
|
+
|
|
49
|
+
// src/content-loader.ts
|
|
50
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
51
|
+
import { basename, extname, resolve } from "node:path";
|
|
52
|
+
function loadJSONEntries(filePath) {
|
|
53
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
54
|
+
if (Array.isArray(raw)) {
|
|
55
|
+
return raw.map((item, i) => ({
|
|
56
|
+
body: null,
|
|
57
|
+
data: item,
|
|
58
|
+
id: item.id ?? `${basename(filePath, ".json")}-${i}`
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
const rawObj = raw;
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
body: null,
|
|
65
|
+
data: rawObj,
|
|
66
|
+
id: rawObj.id ?? basename(filePath, ".json")
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
function discoverJSONFiles(resolvedSource) {
|
|
71
|
+
if (extname(resolvedSource)) {
|
|
72
|
+
return existsSync(resolvedSource) ? [resolvedSource] : [];
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return readdirSync(resolvedSource, { recursive: true }).filter((f) => String(f).endsWith(".json")).map((f) => resolve(resolvedSource, String(f)));
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function loadContentConfig(projectRoot, projectConfig) {
|
|
81
|
+
const contentDir = resolve(projectRoot, "content");
|
|
82
|
+
const config = { content: projectConfig?.content ?? {} };
|
|
83
|
+
return { config, contentDir };
|
|
84
|
+
}
|
|
85
|
+
async function loadContentSection(section, root, formats) {
|
|
86
|
+
const contentTypes = new Map;
|
|
87
|
+
for (const [name, contentTypeDef] of Object.entries(section)) {
|
|
88
|
+
const entries = await loadContentType(name, contentTypeDef, root, formats);
|
|
89
|
+
contentTypes.set(name, entries);
|
|
90
|
+
}
|
|
91
|
+
return contentTypes;
|
|
92
|
+
}
|
|
93
|
+
function getContentTypeElements(projectRoot, contentTypeName, projectConfig) {
|
|
94
|
+
const { config } = loadContentConfig(projectRoot, projectConfig);
|
|
95
|
+
const def = config.content[contentTypeName];
|
|
96
|
+
return def?.$elements;
|
|
97
|
+
}
|
|
98
|
+
async function loadContentType(name, contentTypeDef, projectRoot, registry) {
|
|
99
|
+
const { source } = contentTypeDef;
|
|
100
|
+
if (!source) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const schema = contentTypeDef.schema;
|
|
104
|
+
const directiveOptions = contentTypeDef.$elements?.length ? {
|
|
105
|
+
allowedNames: contentTypeDef.$elements.filter((e) => typeof e === "string" || typeof e === "object" && e?.$ref).map((e) => typeof e === "string" ? e : e.$ref)
|
|
106
|
+
} : undefined;
|
|
107
|
+
const formatName = contentTypeDef.format;
|
|
108
|
+
let entry;
|
|
109
|
+
if (formatName && formatName !== "json") {
|
|
110
|
+
entry = registry.byName(formatName);
|
|
111
|
+
if (!entry) {
|
|
112
|
+
throw new Error(`Content type "${name}": format "${formatName}" is not a registered format class. ` + `Enable an extension providing it in project.json "extensions", e.g. "@jxsuite/parser".`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
116
|
+
if (!entry) {
|
|
117
|
+
throw new Error(`Content type "${name}": remote sources require an explicit "format" naming a ` + `format class with "remote": true (e.g. "Csv").`);
|
|
118
|
+
}
|
|
119
|
+
if (!entry.remote) {
|
|
120
|
+
throw new Error(`Content type "${name}": format "${entry.name}" does not support remote sources ` + `(its format block lacks "remote": true).`);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const entries2 = await entry.call("load", source, {
|
|
124
|
+
directiveOptions,
|
|
125
|
+
schema
|
|
126
|
+
});
|
|
127
|
+
if (schema) {
|
|
128
|
+
validateEntries(entries2, schema, name);
|
|
129
|
+
}
|
|
130
|
+
return entries2;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.warn(`Content type "${name}": ${errorMessage(error)}`);
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const resolvedSource = resolve(projectRoot, source).split("\\").join("/");
|
|
137
|
+
const ext = extname(source).toLowerCase();
|
|
138
|
+
if (formatName === "json" || !entry && ext === ".json") {
|
|
139
|
+
const files2 = discoverJSONFiles(resolvedSource);
|
|
140
|
+
const entries2 = [];
|
|
141
|
+
for (const filePath of files2) {
|
|
142
|
+
entries2.push(...loadJSONEntries(filePath));
|
|
143
|
+
}
|
|
144
|
+
if (schema) {
|
|
145
|
+
validateEntries(entries2, schema, name);
|
|
146
|
+
}
|
|
147
|
+
return entries2;
|
|
148
|
+
}
|
|
149
|
+
if (!entry && ext) {
|
|
150
|
+
entry = registry.byExtension(ext, "load");
|
|
151
|
+
}
|
|
152
|
+
if (!entry) {
|
|
153
|
+
throw unknownFormatError(source, ext || `content type "${name}" (directory sources need an explicit "format")`);
|
|
154
|
+
}
|
|
155
|
+
const files = entry.capabilities.discover ? await entry.call("discover", source, {
|
|
156
|
+
baseDir: projectRoot
|
|
157
|
+
}) : [resolvedSource];
|
|
158
|
+
const entries = [];
|
|
159
|
+
for (const filePath of files) {
|
|
160
|
+
entries.push(...await entry.call("load", filePath, {
|
|
161
|
+
directiveOptions,
|
|
162
|
+
schema
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
if (schema) {
|
|
166
|
+
validateEntries(entries, schema, name);
|
|
167
|
+
}
|
|
168
|
+
return entries;
|
|
169
|
+
}
|
|
170
|
+
function unknownFormatError(path, ext) {
|
|
171
|
+
return new Error(`No format class registered for "${ext}" (${path}). ` + `Enable an extension providing this format in project.json "extensions", ` + `e.g. "@jxsuite/parser".`);
|
|
172
|
+
}
|
|
173
|
+
function errorMessage(error) {
|
|
174
|
+
return error instanceof Error ? error.message : String(error);
|
|
175
|
+
}
|
|
176
|
+
function validateEntries(entries, schema, contentTypeName) {
|
|
177
|
+
const required = schema.required ?? [];
|
|
178
|
+
const properties = schema.properties ?? {};
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
for (const field of required) {
|
|
181
|
+
if (!(field in entry.data) || entry.data[field] == null) {
|
|
182
|
+
console.warn(`Content validation: "${contentTypeName}/${entry.id}" missing required field "${field}"`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const [field, def] of Object.entries(properties)) {
|
|
186
|
+
const value = entry.data[field];
|
|
187
|
+
if (value == null) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (def.type === "string" && typeof value !== "string") {
|
|
191
|
+
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected string, got ${typeof value}`);
|
|
192
|
+
} else if (def.type === "number" && typeof value !== "number") {
|
|
193
|
+
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected number, got ${typeof value}`);
|
|
194
|
+
} else if (def.type === "boolean" && typeof value !== "boolean") {
|
|
195
|
+
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected boolean, got ${typeof value}`);
|
|
196
|
+
} else if (def.type === "array" && !Array.isArray(value)) {
|
|
197
|
+
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected array, got ${typeof value}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
var CONTENT_REF_PREFIX = "#/content/";
|
|
203
|
+
function refTargetName(ref) {
|
|
204
|
+
if (!ref?.startsWith(CONTENT_REF_PREFIX)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
return ref.slice(CONTENT_REF_PREFIX.length);
|
|
208
|
+
}
|
|
209
|
+
function resolveContentTypeRefs(contentTypes, section) {
|
|
210
|
+
for (const [name, contentTypeDef] of Object.entries(section)) {
|
|
211
|
+
const schema = contentTypeDef.schema;
|
|
212
|
+
if (!schema?.properties) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const entries = contentTypes.get(name);
|
|
216
|
+
if (!entries) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
for (const [field, def] of Object.entries(schema.properties)) {
|
|
220
|
+
const toOne = refTargetName(def.$ref);
|
|
221
|
+
const toMany = toOne ? undefined : refTargetName(def.items?.$ref);
|
|
222
|
+
const target = toOne ?? toMany;
|
|
223
|
+
if (!target) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const refEntries = contentTypes.get(target);
|
|
227
|
+
if (!refEntries) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
const value = entry.data[field];
|
|
232
|
+
if (toOne && typeof value === "string") {
|
|
233
|
+
const resolved = refEntries.find((e) => e.id === value);
|
|
234
|
+
if (resolved) {
|
|
235
|
+
entry.data[field] = resolved;
|
|
236
|
+
}
|
|
237
|
+
} else if (toMany && Array.isArray(value)) {
|
|
238
|
+
entry.data[field] = value.map((id) => typeof id === "string" ? refEntries.find((e) => e.id === id) ?? id : id);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
class Content {
|
|
246
|
+
static async projectData(sectionValue, ctx) {
|
|
247
|
+
const section = sectionValue ?? {};
|
|
248
|
+
const data = await loadContentSection(section, ctx.root, ctx.registry.formats);
|
|
249
|
+
resolveContentTypeRefs(data, section);
|
|
250
|
+
return data;
|
|
251
|
+
}
|
|
252
|
+
static async resolvePaths(pathsDef, ctx) {
|
|
253
|
+
const entries = ctx.data.get(pathsDef.contentType);
|
|
254
|
+
if (!entries || entries.length === 0) {
|
|
255
|
+
console.warn(`Warning: $paths references content type "${pathsDef.contentType}" but it has no entries`);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
const param = pathsDef.param ?? "slug";
|
|
259
|
+
const field = pathsDef.field ?? "id";
|
|
260
|
+
return entries.map((entry) => ({
|
|
261
|
+
[param]: field === "id" ? entry.id : entry.data[field] ?? entry.id
|
|
262
|
+
})).filter((p) => p[param]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
export {
|
|
266
|
+
resolveContentTypeRefs,
|
|
267
|
+
loadContentSection,
|
|
268
|
+
loadContentConfig,
|
|
269
|
+
getContentTypeElements,
|
|
270
|
+
Content
|
|
271
|
+
};
|
package/dist/content.js
CHANGED
|
@@ -140,7 +140,7 @@ class ContentCollection {
|
|
|
140
140
|
}
|
|
141
141
|
resolve() {
|
|
142
142
|
const { contentType, filter, sort, limit, _project } = this.config;
|
|
143
|
-
const entries = _project?.
|
|
143
|
+
const entries = _project?.content?.get(contentType ?? "");
|
|
144
144
|
if (!entries) {
|
|
145
145
|
return [];
|
|
146
146
|
}
|
|
@@ -163,7 +163,7 @@ class ContentEntry {
|
|
|
163
163
|
}
|
|
164
164
|
resolve() {
|
|
165
165
|
const { contentType, id, field, _project, _document } = this.config;
|
|
166
|
-
const entries = _project?.
|
|
166
|
+
const entries = _project?.content?.get(contentType ?? "");
|
|
167
167
|
if (!entries) {
|
|
168
168
|
return null;
|
|
169
169
|
}
|
package/dist/markdown.js
CHANGED
|
@@ -858,7 +858,7 @@ var init_unified = __esm(() => {
|
|
|
858
858
|
init_lib3();
|
|
859
859
|
});
|
|
860
860
|
|
|
861
|
-
//
|
|
861
|
+
// ../../packages/schema/src/guards.ts
|
|
862
862
|
function isJsonObject(value) {
|
|
863
863
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
864
864
|
}
|
|
@@ -30939,7 +30939,7 @@ var init_property_information3 = __esm(() => {
|
|
|
30939
30939
|
svg6 = merge3([aria3, svg5, xlink3, xmlns3, xml3], "svg");
|
|
30940
30940
|
});
|
|
30941
30941
|
|
|
30942
|
-
// src/html-to-jx.ts
|
|
30942
|
+
// ../../packages/markup/src/html-to-jx.ts
|
|
30943
30943
|
function htmlToJx(htmlString) {
|
|
30944
30944
|
const hast = fromHtml(htmlString, { fragment: true });
|
|
30945
30945
|
return convertHastChildren(hast.children);
|
package/dist/serialize.js
CHANGED
|
@@ -858,7 +858,7 @@ var init_unified = __esm(() => {
|
|
|
858
858
|
init_lib3();
|
|
859
859
|
});
|
|
860
860
|
|
|
861
|
-
//
|
|
861
|
+
// ../../packages/schema/src/guards.ts
|
|
862
862
|
function isJsonObject(value) {
|
|
863
863
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
864
864
|
}
|
|
@@ -30939,7 +30939,7 @@ var init_property_information3 = __esm(() => {
|
|
|
30939
30939
|
svg6 = merge3([aria3, svg5, xlink3, xmlns3, xml3], "svg");
|
|
30940
30940
|
});
|
|
30941
30941
|
|
|
30942
|
-
// src/html-to-jx.ts
|
|
30942
|
+
// ../../packages/markup/src/html-to-jx.ts
|
|
30943
30943
|
function htmlToJx(htmlString) {
|
|
30944
30944
|
const hast = fromHtml(htmlString, { fragment: true });
|
|
30945
30945
|
return convertHastChildren(hast.children);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jxsuite/parser",
|
|
3
|
+
"title": "Content & Markdown",
|
|
4
|
+
"description": "File-based content collections with Markdown and CSV formats",
|
|
5
|
+
"classes": {
|
|
6
|
+
"Markdown": "./src/Markdown.class.json",
|
|
7
|
+
"Csv": "./src/Csv.class.json",
|
|
8
|
+
"MarkdownCollection": "./src/MarkdownCollection.class.json",
|
|
9
|
+
"ContentCollection": "./src/ContentCollection.class.json",
|
|
10
|
+
"ContentEntry": "./src/ContentEntry.class.json",
|
|
11
|
+
"Content": "./src/Content.class.json"
|
|
12
|
+
},
|
|
13
|
+
"schemas": {
|
|
14
|
+
"project": "./schemas/project.fragment.schema.json",
|
|
15
|
+
"document": "./schemas/document.fragment.schema.json"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Jx markdown parser and external class integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/jxsuite/jx.git",
|
|
9
|
-
"directory": "
|
|
9
|
+
"directory": "extensions/parser"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"src/",
|
|
13
|
-
"dist"
|
|
13
|
+
"dist",
|
|
14
|
+
"jx-extension.json",
|
|
15
|
+
"schemas/"
|
|
14
16
|
],
|
|
15
17
|
"type": "module",
|
|
16
18
|
"exports": {
|
|
17
19
|
".": "./src/md.ts",
|
|
18
20
|
"./content": "./src/content.ts",
|
|
21
|
+
"./content-loader": "./src/content-loader.ts",
|
|
19
22
|
"./markdown": "./src/markdown.ts",
|
|
20
23
|
"./md-html": "./src/md-html.ts",
|
|
21
24
|
"./csv": "./src/csv.ts",
|
|
22
25
|
"./serialize": "./src/serialize.ts",
|
|
26
|
+
"./jx-extension.json": "./jx-extension.json",
|
|
27
|
+
"./schemas/project.fragment.schema.json": "./schemas/project.fragment.schema.json",
|
|
28
|
+
"./schemas/document.fragment.schema.json": "./schemas/document.fragment.schema.json",
|
|
29
|
+
"./Content.class.json": "./src/Content.class.json",
|
|
23
30
|
"./ContentCollection.class.json": "./src/ContentCollection.class.json",
|
|
24
31
|
"./ContentEntry.class.json": "./src/ContentEntry.class.json",
|
|
25
32
|
"./MarkdownCollection.class.json": "./src/MarkdownCollection.class.json",
|
|
@@ -33,28 +40,23 @@
|
|
|
33
40
|
"provenance": true
|
|
34
41
|
},
|
|
35
42
|
"scripts": {
|
|
36
|
-
"prepare": "bun build src/content.ts src/markdown.ts src/csv.ts src/serialize.ts --outdir=dist --format=esm --target=node",
|
|
43
|
+
"prepare": "bun build src/content.ts src/content-loader.ts src/markdown.ts src/csv.ts src/serialize.ts --outdir=dist --format=esm --target=node",
|
|
37
44
|
"build": "bun run prepare",
|
|
38
|
-
"build:metafile": "bun build src/content.ts src/markdown.ts src/csv.ts src/serialize.ts --outdir=dist --format=esm --target=node --metafile=metafile.json",
|
|
45
|
+
"build:metafile": "bun build src/content.ts src/content-loader.ts src/markdown.ts src/csv.ts src/serialize.ts --outdir=dist --format=esm --target=node --metafile=metafile.json",
|
|
39
46
|
"test": "bun test --isolate",
|
|
40
47
|
"test:coverage": "bun test --isolate --coverage",
|
|
41
48
|
"upgrade": "bunx npm-check-updates -u && bun install"
|
|
42
49
|
},
|
|
43
50
|
"dependencies": {
|
|
44
|
-
"@jxsuite/
|
|
51
|
+
"@jxsuite/markup": "^0.2.0",
|
|
52
|
+
"@jxsuite/schema": "^1.0.0",
|
|
45
53
|
"glob": "^13.0.6",
|
|
46
|
-
"hast-util-from-html": "^2.0.3",
|
|
47
|
-
"hast-util-whitespace": "^3.0.0",
|
|
48
54
|
"mdast-util-to-string": "^4.0.0",
|
|
49
|
-
"property-information": "^7.2.0",
|
|
50
|
-
"rehype-sanitize": "^6.0.0",
|
|
51
|
-
"rehype-stringify": "^10.0.1",
|
|
52
55
|
"remark-directive": "^4.0.0",
|
|
53
56
|
"remark-frontmatter": "^5.0.0",
|
|
54
57
|
"remark-gfm": "^4.0.1",
|
|
55
58
|
"remark-parse": "^11.0.0",
|
|
56
59
|
"remark-parse-frontmatter": "^1.0.3",
|
|
57
|
-
"remark-rehype": "^11.1.2",
|
|
58
60
|
"remark-stringify": "^11.0.0",
|
|
59
61
|
"unified": "^11.0.5",
|
|
60
62
|
"unist-util-visit": "^5.1.0",
|
|
@@ -62,8 +64,9 @@
|
|
|
62
64
|
},
|
|
63
65
|
"devDependencies": {
|
|
64
66
|
"@happy-dom/global-registrator": "^20.10.6",
|
|
65
|
-
"@jxsuite/runtime": "^0.
|
|
66
|
-
"@types/
|
|
67
|
-
"
|
|
68
|
-
}
|
|
67
|
+
"@jxsuite/runtime": "^1.0.0",
|
|
68
|
+
"@types/mdast": "^4.0.4",
|
|
69
|
+
"ajv": "^8.20.0"
|
|
70
|
+
},
|
|
71
|
+
"jx": "./jx-extension.json"
|
|
69
72
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://jxsuite.com/schema/ext/parser/document/v1",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "Parser document fragment",
|
|
5
|
+
"description": "Document-schema fragment contributed by @jxsuite/parser: the content-type `$paths` source shape unioned into the document paths resource.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"ContentPathsSource": {
|
|
8
|
+
"description": "Content-type $paths source: expands a dynamic route from the entries of a loaded content type.",
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"contentType": {
|
|
12
|
+
"description": "Name of the content type (a key of the project.json `content` section).",
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"param": {
|
|
16
|
+
"description": "Route parameter to fill from each entry (default \"slug\").",
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"field": {
|
|
20
|
+
"description": "Entry field providing the parameter value; \"id\" (the default) uses the entry id.",
|
|
21
|
+
"type": "string"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"required": ["contentType"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "https://jxsuite.com/schema/ext/parser/project/v1",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "Parser project fragment",
|
|
5
|
+
"description": "Project-schema fragment contributed by @jxsuite/parser: the `content` section declaring file-based content collections.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"content": {
|
|
9
|
+
"description": "File-based content collections. Each key names a content type; entries are loaded from the named source through the format registry.",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": {
|
|
12
|
+
"description": "Content type definition. Defines the source directory, frontmatter schema, and element dependencies.",
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"$elements": {
|
|
16
|
+
"description": "Custom elements available in markdown directives for this content type.",
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": {
|
|
19
|
+
"oneOf": [
|
|
20
|
+
{
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": { "$ref": { "type": "string" } },
|
|
23
|
+
"required": ["$ref"]
|
|
24
|
+
},
|
|
25
|
+
{ "type": "string" }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"format": {
|
|
30
|
+
"description": "Name of a format class provided by an enabled extension (e.g. \"Markdown\", \"Csv\"). When omitted, the format is derived from the source file extension via the format registry. \"json\" is the only built-in.",
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
"schema": {
|
|
34
|
+
"description": "JSON Schema for validating frontmatter of content type entries.",
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"type": { "const": "object" },
|
|
38
|
+
"properties": {
|
|
39
|
+
"description": "Field definitions; values are field schemas from the effective project field union.",
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": { "$ref": "https://jxsuite.com/schema/project/fields/v2" }
|
|
42
|
+
},
|
|
43
|
+
"required": {
|
|
44
|
+
"description": "Frontmatter fields every entry must provide.",
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": { "type": "string" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"source": {
|
|
51
|
+
"description": "Path to the content directory relative to the project root.",
|
|
52
|
+
"examples": ["./content/blog/", "./content/docs/"],
|
|
53
|
+
"type": "string"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://jxsuite.com/schemas/Content.class.json",
|
|
4
|
+
"title": "Content",
|
|
5
|
+
"description": "Content-collections project section for Jx: loads the project.json `content` section into _project.content and expands content-type $paths into route params",
|
|
6
|
+
"$prototype": "Class",
|
|
7
|
+
"extends": "Object",
|
|
8
|
+
"$implementation": "./content-loader.js",
|
|
9
|
+
|
|
10
|
+
"project": {
|
|
11
|
+
"key": "content",
|
|
12
|
+
"title": "Content Types",
|
|
13
|
+
"description": "File-based content collections loaded at build and dev-serve time",
|
|
14
|
+
"referenceable": true
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"$studio": {
|
|
18
|
+
"settings": {
|
|
19
|
+
"icon": "sp-icon-view-grid",
|
|
20
|
+
"label": "Content Types",
|
|
21
|
+
"order": 50,
|
|
22
|
+
"layout": "map",
|
|
23
|
+
"entry": {
|
|
24
|
+
"ui": { "schema": { "control": "schema-builder" } },
|
|
25
|
+
"newEntry": {
|
|
26
|
+
"source": "./content/${key}/",
|
|
27
|
+
"schema": { "type": "object", "properties": {}, "required": [] }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"$defs": {
|
|
34
|
+
"returnTypes": {
|
|
35
|
+
"SectionData": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Map of content type name → ContentLoaderEntry[], with relationship references resolved"
|
|
38
|
+
},
|
|
39
|
+
"PathEntries": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"description": "Route-param objects, one per content entry: { [param]: value }",
|
|
42
|
+
"items": { "type": "object" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"methods": {
|
|
47
|
+
"projectData": {
|
|
48
|
+
"role": "projectData",
|
|
49
|
+
"$prototype": "Function",
|
|
50
|
+
"access": "public",
|
|
51
|
+
"scope": "static",
|
|
52
|
+
"identifier": "projectData",
|
|
53
|
+
"timing": ["compiler", "server"],
|
|
54
|
+
"parameters": [
|
|
55
|
+
{
|
|
56
|
+
"identifier": "sectionValue",
|
|
57
|
+
"type": { "type": "object" },
|
|
58
|
+
"description": "The project.json `content` section value (content type name → definition)"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"identifier": "ctx",
|
|
62
|
+
"type": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"properties": {
|
|
65
|
+
"projectConfig": { "type": "object" },
|
|
66
|
+
"root": { "type": "string" },
|
|
67
|
+
"registry": { "type": "object" },
|
|
68
|
+
"io": { "type": "object" }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"description": "Host context: { projectConfig, root, registry, io }"
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"returnType": { "$ref": "#/$defs/returnTypes/SectionData" },
|
|
75
|
+
"description": "Load every content type in the section through the format registry, resolving relationship references"
|
|
76
|
+
},
|
|
77
|
+
"resolvePaths": {
|
|
78
|
+
"role": "resolvePaths",
|
|
79
|
+
"$prototype": "Function",
|
|
80
|
+
"access": "public",
|
|
81
|
+
"scope": "static",
|
|
82
|
+
"identifier": "resolvePaths",
|
|
83
|
+
"discriminator": "contentType",
|
|
84
|
+
"timing": ["compiler", "server"],
|
|
85
|
+
"parameters": [
|
|
86
|
+
{
|
|
87
|
+
"identifier": "pathsDef",
|
|
88
|
+
"type": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"contentType": { "type": "string" },
|
|
92
|
+
"param": { "type": "string", "default": "slug" },
|
|
93
|
+
"field": { "type": "string", "default": "id" }
|
|
94
|
+
},
|
|
95
|
+
"required": ["contentType"]
|
|
96
|
+
},
|
|
97
|
+
"description": "The $paths value carrying the contentType discriminator"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"identifier": "ctx",
|
|
101
|
+
"type": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"data": { "type": "object" },
|
|
105
|
+
"projectConfig": { "type": "object" },
|
|
106
|
+
"root": { "type": "string" }
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"description": "Host context: { data, projectConfig, root } where data is the projectData result"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"returnType": { "$ref": "#/$defs/returnTypes/PathEntries" },
|
|
113
|
+
"description": "Expand a content-type $paths source into one route-param object per entry (param defaults to \"slug\", field to \"id\")"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"parameters": {
|
|
11
11
|
"contentType": {
|
|
12
12
|
"identifier": "contentType",
|
|
13
|
-
"type": { "type": "string", "enum": { "$ref": "#/$context/
|
|
13
|
+
"type": { "type": "string", "enum": { "$ref": "#/$context/content" } },
|
|
14
14
|
"description": "Content type to query"
|
|
15
15
|
},
|
|
16
16
|
"filter": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"properties": {
|
|
24
24
|
"field": {
|
|
25
25
|
"type": "string",
|
|
26
|
-
"enum": { "$ref": "#/$context/
|
|
26
|
+
"enum": { "$ref": "#/$context/content/{@contentType}/schema/properties" }
|
|
27
27
|
},
|
|
28
28
|
"op": {
|
|
29
29
|
"type": "string",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"properties": {
|
|
58
58
|
"field": {
|
|
59
59
|
"type": "string",
|
|
60
|
-
"enum": { "$ref": "#/$context/
|
|
60
|
+
"enum": { "$ref": "#/$context/content/{@contentType}/schema/properties" }
|
|
61
61
|
},
|
|
62
62
|
"order": { "type": "string", "enum": ["asc", "desc"], "default": "asc" }
|
|
63
63
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"parameters": {
|
|
11
11
|
"contentType": {
|
|
12
12
|
"identifier": "contentType",
|
|
13
|
-
"type": { "type": "string", "enum": { "$ref": "#/$context/
|
|
13
|
+
"type": { "type": "string", "enum": { "$ref": "#/$context/content" } },
|
|
14
14
|
"description": "Content type to query"
|
|
15
15
|
},
|
|
16
16
|
"id": {
|