@jxsuite/parser 1.4.1 → 1.5.2
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 +281 -23
- package/dist/content.js +117 -16
- package/dist/csv.js +10 -8
- package/dist/markdown.js +281 -162
- package/dist/serialize.js +167 -140
- package/package.json +8 -8
- package/src/Markdown.class.json +1 -1
- package/src/content-loader.ts +200 -14
- package/src/content.ts +38 -7
- package/src/dates.ts +184 -0
- package/src/markdown.ts +13 -1
- package/src/md.ts +52 -4
- package/src/serialize.ts +28 -3
- package/src/transpile.ts +25 -6
package/dist/content-loader.js
CHANGED
|
@@ -19,12 +19,14 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
}
|
|
20
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
28
30
|
if (canCache)
|
|
29
31
|
cache.set(mod, to);
|
|
30
32
|
return to;
|
|
@@ -80,6 +82,184 @@ function assetUrlFor(mounts, absolutePath) {
|
|
|
80
82
|
}
|
|
81
83
|
var URL_TERMINATOR = String.raw`"'\s<>()\\`;
|
|
82
84
|
|
|
85
|
+
// ../../packages/schema/src/locale.ts
|
|
86
|
+
var RTL_SCRIPTS = new Set([
|
|
87
|
+
"Adlm",
|
|
88
|
+
"Arab",
|
|
89
|
+
"Aran",
|
|
90
|
+
"Armi",
|
|
91
|
+
"Avst",
|
|
92
|
+
"Cprt",
|
|
93
|
+
"Egyd",
|
|
94
|
+
"Egyh",
|
|
95
|
+
"Elym",
|
|
96
|
+
"Gara",
|
|
97
|
+
"Hatr",
|
|
98
|
+
"Hebr",
|
|
99
|
+
"Hluw",
|
|
100
|
+
"Hung",
|
|
101
|
+
"Khar",
|
|
102
|
+
"Lydi",
|
|
103
|
+
"Mand",
|
|
104
|
+
"Mani",
|
|
105
|
+
"Mend",
|
|
106
|
+
"Merc",
|
|
107
|
+
"Mero",
|
|
108
|
+
"Narb",
|
|
109
|
+
"Nbat",
|
|
110
|
+
"Nkoo",
|
|
111
|
+
"Orkh",
|
|
112
|
+
"Palm",
|
|
113
|
+
"Phli",
|
|
114
|
+
"Phlp",
|
|
115
|
+
"Phnx",
|
|
116
|
+
"Prti",
|
|
117
|
+
"Rohg",
|
|
118
|
+
"Samr",
|
|
119
|
+
"Sarb",
|
|
120
|
+
"Sogd",
|
|
121
|
+
"Sogo",
|
|
122
|
+
"Syrc",
|
|
123
|
+
"Thaa",
|
|
124
|
+
"Todr",
|
|
125
|
+
"Yezi"
|
|
126
|
+
]);
|
|
127
|
+
function canonicalizeLocale(tag) {
|
|
128
|
+
if (typeof tag !== "string" || tag.trim() === "") {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return new Intl.Locale(tag.trim()).toString();
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
var DEFAULT_ROUTING = "prefix-except-default";
|
|
138
|
+
function resolveI18n(projectConfig) {
|
|
139
|
+
const raw = projectConfig.i18n;
|
|
140
|
+
const errors = [];
|
|
141
|
+
if (!raw || typeof raw !== "object") {
|
|
142
|
+
return { errors, i18n: null };
|
|
143
|
+
}
|
|
144
|
+
const declared = Array.isArray(raw.locales) ? raw.locales : [];
|
|
145
|
+
const locales = [];
|
|
146
|
+
for (const tag of declared) {
|
|
147
|
+
const canonical = canonicalizeLocale(tag);
|
|
148
|
+
if (canonical === null) {
|
|
149
|
+
errors.push(`i18n.locales: "${String(tag)}" is not a well-formed BCP 47 language tag.`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (!locales.includes(canonical)) {
|
|
153
|
+
locales.push(canonical);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const defaultLocale = canonicalizeLocale(raw.defaultLocale) ?? locales[0] ?? null;
|
|
157
|
+
if (raw.defaultLocale !== undefined && canonicalizeLocale(raw.defaultLocale) === null) {
|
|
158
|
+
errors.push(`i18n.defaultLocale: "${String(raw.defaultLocale)}" is not a well-formed BCP 47 language tag.`);
|
|
159
|
+
}
|
|
160
|
+
if (defaultLocale === null) {
|
|
161
|
+
errors.push("i18n is declared with no usable locale — set `defaultLocale` or `locales`.");
|
|
162
|
+
return { errors, i18n: null };
|
|
163
|
+
}
|
|
164
|
+
if (!locales.includes(defaultLocale)) {
|
|
165
|
+
locales.unshift(defaultLocale);
|
|
166
|
+
}
|
|
167
|
+
const routing = raw.routing === "prefix-always" || raw.routing === "prefix-except-default" ? raw.routing : DEFAULT_ROUTING;
|
|
168
|
+
return { errors, i18n: { defaultLocale, locales, routing } };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/dates.ts
|
|
172
|
+
var DATE_FORMATS = ["date", "date-time"];
|
|
173
|
+
function isDateFormat(format) {
|
|
174
|
+
return typeof format === "string" && DATE_FORMATS.includes(format);
|
|
175
|
+
}
|
|
176
|
+
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
177
|
+
var RFC_3339 = /^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?([Zz]|[+-]\d{2}:\d{2})?$/;
|
|
178
|
+
function pad(n, width = 2) {
|
|
179
|
+
return String(n).padStart(width, "0");
|
|
180
|
+
}
|
|
181
|
+
function toDateOnly(d) {
|
|
182
|
+
return `${pad(d.getUTCFullYear(), 4)}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
|
|
183
|
+
}
|
|
184
|
+
function toInstant(d) {
|
|
185
|
+
return `${toDateOnly(d)}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`;
|
|
186
|
+
}
|
|
187
|
+
function parseDateValue(value, format) {
|
|
188
|
+
if (value instanceof Date) {
|
|
189
|
+
return Number.isNaN(value.getTime()) ? null : format === "date" ? toDateOnly(value) : toInstant(value);
|
|
190
|
+
}
|
|
191
|
+
if (typeof value !== "string") {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const text = value.trim();
|
|
195
|
+
if (text === "") {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
if (DATE_ONLY.test(text)) {
|
|
199
|
+
const d = new Date(`${text}T00:00:00Z`);
|
|
200
|
+
if (Number.isNaN(d.getTime())) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return format === "date" ? text : toInstant(d);
|
|
204
|
+
}
|
|
205
|
+
if (RFC_3339.test(text)) {
|
|
206
|
+
const normalized = /[Zz]|[+-]\d{2}:\d{2}$/.test(text) ? text : `${text.replace(" ", "T")}Z`;
|
|
207
|
+
const d = new Date(normalized);
|
|
208
|
+
if (Number.isNaN(d.getTime())) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return format === "date" ? toDateOnly(d) : toInstant(d);
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
function isCoercedDate(value) {
|
|
216
|
+
return DATE_ONLY.test(value) || RFC_3339.test(value);
|
|
217
|
+
}
|
|
218
|
+
function coerceEntryDates(entries, schema, contentTypeName) {
|
|
219
|
+
const warnings = [];
|
|
220
|
+
const properties = schema?.properties;
|
|
221
|
+
if (!properties) {
|
|
222
|
+
return warnings;
|
|
223
|
+
}
|
|
224
|
+
const dateFields = Object.entries(properties).map(([field, def]) => [field, def.format]).filter((pair) => isDateFormat(pair[1]));
|
|
225
|
+
if (dateFields.length === 0) {
|
|
226
|
+
return warnings;
|
|
227
|
+
}
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
for (const [field, format] of dateFields) {
|
|
230
|
+
const raw = entry.data[field];
|
|
231
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const coerced = parseDateValue(raw, format);
|
|
235
|
+
if (coerced === null) {
|
|
236
|
+
warnings.push({
|
|
237
|
+
entryId: entry.id,
|
|
238
|
+
field,
|
|
239
|
+
message: `Content dates: "${contentTypeName}/${entry.id}" field "${field}" is ` + `${JSON.stringify(raw)}, which is not an unambiguous ${format}. Write it as ` + `${format === "date" ? "YYYY-MM-DD" : "YYYY-MM-DDTHH:MM:SSZ"} — a form like ` + `"03/04/2025" means two different days depending on who reads it, so it is left as ` + `authored rather than guessed.`,
|
|
240
|
+
value: raw
|
|
241
|
+
});
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (coerced !== raw) {
|
|
245
|
+
entry._meta ??= {};
|
|
246
|
+
entry._meta.rawDates ??= {};
|
|
247
|
+
entry._meta.rawDates[field] = raw;
|
|
248
|
+
}
|
|
249
|
+
entry.data[field] = coerced;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return warnings;
|
|
253
|
+
}
|
|
254
|
+
function parseComparable(a, b) {
|
|
255
|
+
const aDate = typeof a === "string" && (DATE_ONLY.test(a) || RFC_3339.test(a));
|
|
256
|
+
const bDate = typeof b === "string" && (DATE_ONLY.test(b) || RFC_3339.test(b));
|
|
257
|
+
if (aDate && bDate) {
|
|
258
|
+
return [a, b];
|
|
259
|
+
}
|
|
260
|
+
return [Number(a), Number(b)];
|
|
261
|
+
}
|
|
262
|
+
|
|
83
263
|
// src/content-loader.ts
|
|
84
264
|
function loadJSONEntries(filePath) {
|
|
85
265
|
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
@@ -109,19 +289,53 @@ function discoverJSONFiles(resolvedSource) {
|
|
|
109
289
|
return [];
|
|
110
290
|
}
|
|
111
291
|
}
|
|
292
|
+
var LOCALE_PLACEHOLDER = "{locale}";
|
|
293
|
+
function localesForExpansion(projectConfig) {
|
|
294
|
+
return resolveI18n(projectConfig ?? {}).i18n?.locales ?? [];
|
|
295
|
+
}
|
|
296
|
+
function localeSource(root, source, locale) {
|
|
297
|
+
const candidates = [locale, locale.toLowerCase()];
|
|
298
|
+
for (const spelling of candidates) {
|
|
299
|
+
const path = source.split(LOCALE_PLACEHOLDER).join(spelling);
|
|
300
|
+
if (existsSync(resolve(root, path))) {
|
|
301
|
+
return path;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const parent = resolve(root, source.slice(0, source.indexOf(LOCALE_PLACEHOLDER)));
|
|
305
|
+
if (existsSync(parent) && statSync(parent).isDirectory()) {
|
|
306
|
+
const match = readdirSync(parent).find((name) => name.toLowerCase() === locale.toLowerCase());
|
|
307
|
+
if (match !== undefined) {
|
|
308
|
+
return source.split(LOCALE_PLACEHOLDER).join(match);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return source.split(LOCALE_PLACEHOLDER).join(locale);
|
|
312
|
+
}
|
|
112
313
|
var SECTION_KEY = "content";
|
|
113
314
|
var SAFE_TYPE_NAME = /^[\w.~-]+$/;
|
|
114
315
|
var ASSET_KEYS = ["src", "poster"];
|
|
115
316
|
function isNonRelativeRef(value) {
|
|
116
317
|
return value === "" || value.startsWith("/") || value.startsWith("#") || value.includes("${") || /^[a-z][\w+.-]*:/i.test(value);
|
|
117
318
|
}
|
|
118
|
-
function contentAssetMounts(section, root) {
|
|
319
|
+
function contentAssetMounts(section, root, projectConfig) {
|
|
119
320
|
const mounts = [];
|
|
321
|
+
const locales = localesForExpansion(projectConfig);
|
|
120
322
|
for (const [name, contentTypeDef] of Object.entries(section ?? {})) {
|
|
121
323
|
const source = contentTypeDef?.source;
|
|
122
324
|
if (!source || source.startsWith("http://") || source.startsWith("https://")) {
|
|
123
325
|
continue;
|
|
124
326
|
}
|
|
327
|
+
if (source.includes(LOCALE_PLACEHOLDER)) {
|
|
328
|
+
for (const locale of locales) {
|
|
329
|
+
if (!SAFE_TYPE_NAME.test(locale)) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const dir = resolve(root, localeSource(root, source, locale)).split("\\").join("/");
|
|
333
|
+
if (SAFE_TYPE_NAME.test(name) && existsSync(dir) && statSync(dir).isDirectory()) {
|
|
334
|
+
mounts.push({ dir, urlPrefix: `/${SECTION_KEY}/${name}/${locale}` });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
125
339
|
if (!SAFE_TYPE_NAME.test(name)) {
|
|
126
340
|
console.warn(`Content type "${name}": name is not URL-safe — its source directory is not published ` + `at /${SECTION_KEY}/${name}, so content-relative asset references stay unresolved.`);
|
|
127
341
|
continue;
|
|
@@ -203,12 +417,35 @@ function loadContentConfig(projectRoot, projectConfig) {
|
|
|
203
417
|
const config = { content: projectConfig?.content ?? {} };
|
|
204
418
|
return { config, contentDir };
|
|
205
419
|
}
|
|
206
|
-
async function loadContentSection(section, root, formats) {
|
|
420
|
+
async function loadContentSection(section, root, formats, projectConfig) {
|
|
207
421
|
const contentTypes = new Map;
|
|
208
|
-
const mounts = new Map(contentAssetMounts(section, root).map((mount) => [
|
|
422
|
+
const mounts = new Map(contentAssetMounts(section, root, projectConfig).map((mount) => [
|
|
423
|
+
mount.urlPrefix.slice(`/${SECTION_KEY}/`.length),
|
|
424
|
+
mount
|
|
425
|
+
]));
|
|
426
|
+
const locales = localesForExpansion(projectConfig);
|
|
209
427
|
for (const [name, contentTypeDef] of Object.entries(section)) {
|
|
210
|
-
const
|
|
211
|
-
|
|
428
|
+
const source = contentTypeDef?.source;
|
|
429
|
+
if (typeof source !== "string" || !source.includes(LOCALE_PLACEHOLDER)) {
|
|
430
|
+
const entries = await loadContentType(name, contentTypeDef, root, formats, mounts.get(name));
|
|
431
|
+
contentTypes.set(name, entries);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (locales.length === 0) {
|
|
435
|
+
console.warn(`Content type "${name}": source "${source}" uses ${LOCALE_PLACEHOLDER}, but the project ` + `declares no i18n locales — nothing to expand it over, so no entries were loaded.`);
|
|
436
|
+
contentTypes.set(name, []);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const all = [];
|
|
440
|
+
for (const locale of locales) {
|
|
441
|
+
const localized = { ...contentTypeDef, source: localeSource(root, source, locale) };
|
|
442
|
+
const entries = await loadContentType(name, localized, root, formats, mounts.get(`${name}/${locale}`));
|
|
443
|
+
for (const entry of entries) {
|
|
444
|
+
entry._meta = { ...entry._meta, locale };
|
|
445
|
+
}
|
|
446
|
+
all.push(...entries);
|
|
447
|
+
}
|
|
448
|
+
contentTypes.set(name, all);
|
|
212
449
|
}
|
|
213
450
|
return contentTypes;
|
|
214
451
|
}
|
|
@@ -295,6 +532,9 @@ async function loadContentType(name, contentTypeDef, projectRoot, registry, moun
|
|
|
295
532
|
entries.push(...fileEntries);
|
|
296
533
|
}
|
|
297
534
|
if (schema) {
|
|
535
|
+
for (const warning of coerceEntryDates(entries, schema, name)) {
|
|
536
|
+
console.warn(warning.message);
|
|
537
|
+
}
|
|
298
538
|
validateEntries(entries, schema, name);
|
|
299
539
|
}
|
|
300
540
|
return entries;
|
|
@@ -319,7 +559,9 @@ function validateEntries(entries, schema, contentTypeName) {
|
|
|
319
559
|
if (value == null) {
|
|
320
560
|
continue;
|
|
321
561
|
}
|
|
322
|
-
if (def.
|
|
562
|
+
if (isDateFormat(def.format) && typeof value === "string" && !isCoercedDate(value)) {
|
|
563
|
+
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" is declared ` + `${def.format} but "${value}" was not coerced — it will sort and filter as plain text.`);
|
|
564
|
+
} else if (def.type === "string" && typeof value !== "string") {
|
|
323
565
|
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected string, got ${typeof value}`);
|
|
324
566
|
} else if (def.type === "number" && typeof value !== "number") {
|
|
325
567
|
console.warn(`Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected number, got ${typeof value}`);
|
|
@@ -389,12 +631,12 @@ function resolveContentTypeRefs(contentTypes, section) {
|
|
|
389
631
|
class Content {
|
|
390
632
|
static async projectData(sectionValue, ctx) {
|
|
391
633
|
const section = sectionValue ?? {};
|
|
392
|
-
const data = await loadContentSection(section, ctx.root, ctx.registry.formats);
|
|
634
|
+
const data = await loadContentSection(section, ctx.root, ctx.registry.formats, ctx.projectConfig);
|
|
393
635
|
resolveContentTypeRefs(data, section);
|
|
394
636
|
return data;
|
|
395
637
|
}
|
|
396
638
|
static assets(sectionValue, ctx) {
|
|
397
|
-
return contentAssetMounts(sectionValue ?? {}, ctx.root);
|
|
639
|
+
return contentAssetMounts(sectionValue ?? {}, ctx.root, ctx.projectConfig);
|
|
398
640
|
}
|
|
399
641
|
static async resolvePaths(pathsDef, ctx) {
|
|
400
642
|
const entries = ctx.data.get(pathsDef.contentType);
|
|
@@ -404,17 +646,33 @@ class Content {
|
|
|
404
646
|
}
|
|
405
647
|
const param = pathsDef.param ?? "slug";
|
|
406
648
|
const field = pathsDef.field ?? "id";
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
649
|
+
const localized = entries.some((entry) => entry._meta?.locale !== undefined);
|
|
650
|
+
const wanted = ctx.locale;
|
|
651
|
+
const scoped = localized && typeof wanted === "string" ? entries.filter((entry) => entry._meta?.locale === wanted) : entries;
|
|
652
|
+
const paths = [];
|
|
653
|
+
for (const entry of scoped) {
|
|
654
|
+
const value = field === "id" ? entry.id : entry.data[field] ?? entry.id;
|
|
655
|
+
if (!value) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const pathEntry = { [param]: value };
|
|
659
|
+
if (entry._meta !== undefined) {
|
|
660
|
+
pathEntry._meta = entry._meta;
|
|
661
|
+
}
|
|
662
|
+
paths.push(pathEntry);
|
|
663
|
+
}
|
|
664
|
+
return paths;
|
|
410
665
|
}
|
|
411
666
|
}
|
|
412
667
|
export {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
loadContentSection,
|
|
416
|
-
loadContentConfig,
|
|
417
|
-
getContentTypeElements,
|
|
668
|
+
Content,
|
|
669
|
+
LOCALE_PLACEHOLDER,
|
|
418
670
|
contentAssetMounts,
|
|
419
|
-
|
|
671
|
+
getContentTypeElements,
|
|
672
|
+
loadContentConfig,
|
|
673
|
+
loadContentSection,
|
|
674
|
+
localeSource,
|
|
675
|
+
localesForExpansion,
|
|
676
|
+
resolveContentTypeRefs,
|
|
677
|
+
rewriteEntryAssets
|
|
420
678
|
};
|
package/dist/content.js
CHANGED
|
@@ -19,12 +19,14 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
}
|
|
20
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
28
30
|
if (canCache)
|
|
29
31
|
cache.set(mod, to);
|
|
30
32
|
return to;
|
|
@@ -46,6 +48,98 @@ var __export = (target, all) => {
|
|
|
46
48
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
49
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
50
|
|
|
51
|
+
// src/dates.ts
|
|
52
|
+
var DATE_FORMATS = ["date", "date-time"];
|
|
53
|
+
function isDateFormat(format) {
|
|
54
|
+
return typeof format === "string" && DATE_FORMATS.includes(format);
|
|
55
|
+
}
|
|
56
|
+
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
57
|
+
var RFC_3339 = /^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?([Zz]|[+-]\d{2}:\d{2})?$/;
|
|
58
|
+
function pad(n, width = 2) {
|
|
59
|
+
return String(n).padStart(width, "0");
|
|
60
|
+
}
|
|
61
|
+
function toDateOnly(d) {
|
|
62
|
+
return `${pad(d.getUTCFullYear(), 4)}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
|
|
63
|
+
}
|
|
64
|
+
function toInstant(d) {
|
|
65
|
+
return `${toDateOnly(d)}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`;
|
|
66
|
+
}
|
|
67
|
+
function parseDateValue(value, format) {
|
|
68
|
+
if (value instanceof Date) {
|
|
69
|
+
return Number.isNaN(value.getTime()) ? null : format === "date" ? toDateOnly(value) : toInstant(value);
|
|
70
|
+
}
|
|
71
|
+
if (typeof value !== "string") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const text = value.trim();
|
|
75
|
+
if (text === "") {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (DATE_ONLY.test(text)) {
|
|
79
|
+
const d = new Date(`${text}T00:00:00Z`);
|
|
80
|
+
if (Number.isNaN(d.getTime())) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return format === "date" ? text : toInstant(d);
|
|
84
|
+
}
|
|
85
|
+
if (RFC_3339.test(text)) {
|
|
86
|
+
const normalized = /[Zz]|[+-]\d{2}:\d{2}$/.test(text) ? text : `${text.replace(" ", "T")}Z`;
|
|
87
|
+
const d = new Date(normalized);
|
|
88
|
+
if (Number.isNaN(d.getTime())) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return format === "date" ? toDateOnly(d) : toInstant(d);
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function isCoercedDate(value) {
|
|
96
|
+
return DATE_ONLY.test(value) || RFC_3339.test(value);
|
|
97
|
+
}
|
|
98
|
+
function coerceEntryDates(entries, schema, contentTypeName) {
|
|
99
|
+
const warnings = [];
|
|
100
|
+
const properties = schema?.properties;
|
|
101
|
+
if (!properties) {
|
|
102
|
+
return warnings;
|
|
103
|
+
}
|
|
104
|
+
const dateFields = Object.entries(properties).map(([field, def]) => [field, def.format]).filter((pair) => isDateFormat(pair[1]));
|
|
105
|
+
if (dateFields.length === 0) {
|
|
106
|
+
return warnings;
|
|
107
|
+
}
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
for (const [field, format] of dateFields) {
|
|
110
|
+
const raw = entry.data[field];
|
|
111
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const coerced = parseDateValue(raw, format);
|
|
115
|
+
if (coerced === null) {
|
|
116
|
+
warnings.push({
|
|
117
|
+
entryId: entry.id,
|
|
118
|
+
field,
|
|
119
|
+
message: `Content dates: "${contentTypeName}/${entry.id}" field "${field}" is ` + `${JSON.stringify(raw)}, which is not an unambiguous ${format}. Write it as ` + `${format === "date" ? "YYYY-MM-DD" : "YYYY-MM-DDTHH:MM:SSZ"} — a form like ` + `"03/04/2025" means two different days depending on who reads it, so it is left as ` + `authored rather than guessed.`,
|
|
120
|
+
value: raw
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (coerced !== raw) {
|
|
125
|
+
entry._meta ??= {};
|
|
126
|
+
entry._meta.rawDates ??= {};
|
|
127
|
+
entry._meta.rawDates[field] = raw;
|
|
128
|
+
}
|
|
129
|
+
entry.data[field] = coerced;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return warnings;
|
|
133
|
+
}
|
|
134
|
+
function parseComparable(a, b) {
|
|
135
|
+
const aDate = typeof a === "string" && (DATE_ONLY.test(a) || RFC_3339.test(a));
|
|
136
|
+
const bDate = typeof b === "string" && (DATE_ONLY.test(b) || RFC_3339.test(b));
|
|
137
|
+
if (aDate && bDate) {
|
|
138
|
+
return [a, b];
|
|
139
|
+
}
|
|
140
|
+
return [Number(a), Number(b)];
|
|
141
|
+
}
|
|
142
|
+
|
|
49
143
|
// src/content.ts
|
|
50
144
|
function evaluateFilterRule(rule, entry) {
|
|
51
145
|
const actual = rule.field === "id" ? entry.id : entry.data[rule.field];
|
|
@@ -75,16 +169,20 @@ function evaluateFilterRule(rule, entry) {
|
|
|
75
169
|
return typeof actual === "string" && !actual.includes(String(rule.value ?? ""));
|
|
76
170
|
}
|
|
77
171
|
case ">": {
|
|
78
|
-
|
|
172
|
+
const [a, b] = parseComparable(actual, rule.value);
|
|
173
|
+
return a > b;
|
|
79
174
|
}
|
|
80
175
|
case "<": {
|
|
81
|
-
|
|
176
|
+
const [a, b] = parseComparable(actual, rule.value);
|
|
177
|
+
return a < b;
|
|
82
178
|
}
|
|
83
179
|
case ">=": {
|
|
84
|
-
|
|
180
|
+
const [a, b] = parseComparable(actual, rule.value);
|
|
181
|
+
return a >= b;
|
|
85
182
|
}
|
|
86
183
|
case "<=": {
|
|
87
|
-
|
|
184
|
+
const [a, b] = parseComparable(actual, rule.value);
|
|
185
|
+
return a <= b;
|
|
88
186
|
}
|
|
89
187
|
default: {
|
|
90
188
|
return true;
|
|
@@ -175,16 +273,19 @@ class ContentEntry {
|
|
|
175
273
|
if (!resolvedId) {
|
|
176
274
|
return null;
|
|
177
275
|
}
|
|
276
|
+
const wanted = _document?.route?.locale;
|
|
277
|
+
const localized = entries.some((e) => e._meta?.locale !== undefined);
|
|
278
|
+
const scoped = localized && typeof wanted === "string" ? entries.filter((e) => e._meta?.locale === wanted) : entries;
|
|
178
279
|
if (field && field !== "id") {
|
|
179
|
-
return
|
|
280
|
+
return scoped.find((e) => e.data[field] === resolvedId) ?? null;
|
|
180
281
|
}
|
|
181
|
-
return findEntry(
|
|
282
|
+
return findEntry(scoped, resolvedId);
|
|
182
283
|
}
|
|
183
284
|
}
|
|
184
285
|
export {
|
|
185
|
-
|
|
186
|
-
findEntry,
|
|
187
|
-
evaluateFilterRule,
|
|
286
|
+
ContentCollection,
|
|
188
287
|
ContentEntry,
|
|
189
|
-
|
|
288
|
+
evaluateFilterRule,
|
|
289
|
+
findEntry,
|
|
290
|
+
queryContentType
|
|
190
291
|
};
|
package/dist/csv.js
CHANGED
|
@@ -19,12 +19,14 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
}
|
|
20
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
28
30
|
if (canCache)
|
|
29
31
|
cache.set(mod, to);
|
|
30
32
|
return to;
|
|
@@ -187,7 +189,7 @@ class Csv {
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
export {
|
|
190
|
-
|
|
192
|
+
Csv,
|
|
191
193
|
coerceCSVRows,
|
|
192
|
-
|
|
194
|
+
parseCSV
|
|
193
195
|
};
|