@blinkk/root-cms 3.0.1-alpha.1 → 3.0.1-beta.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/app.js +12 -6
- package/dist/chunk-ATPWU3CU.js +921 -0
- package/dist/chunk-CRK7N6RR.js +298 -0
- package/dist/{chunk-SNZ4S4IC.js → chunk-F4SODS5S.js} +87 -297
- package/dist/{chunk-R4LKO3EZ.js → chunk-MAZA5B27.js} +16 -18
- package/dist/{chunk-UTSL2E2P.js → chunk-TRM4MQHU.js} +270 -11
- package/dist/{chunk-KDXHFMIH.js → chunk-XLX37FRL.js} +3 -0
- package/dist/cli.js +3 -2
- package/dist/{client-DdB4xpM6.d.ts → client-1puwLgNx.d.ts} +194 -10
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -1
- package/dist/core.d.ts +3 -3
- package/dist/core.js +2 -1
- package/dist/functions.js +3 -2
- package/dist/{generate-types-MHWSSOWV.js → generate-types-SPV7I3A5.js} +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +284 -78
- package/dist/project.d.ts +15 -2
- package/dist/project.js +3 -1
- package/dist/richtext.d.ts +25 -18
- package/dist/richtext.js +98 -43
- package/dist/{schema-rjBOZk-j.d.ts → schema-Db_xODoi.d.ts} +5 -0
- package/dist/ui/signin.css +1 -1
- package/dist/ui/signin.js +3 -3
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +255 -177
- package/dist/ui/ui.js.LEGAL.txt +2 -1
- package/package.json +10 -7
- package/dist/ai-OZY3JXDH.js +0 -19
- package/dist/altText-RDKJNVGH.js +0 -7
- package/dist/chunk-BBOESYH7.js +0 -192
- package/dist/chunk-T5UK2H24.js +0 -419
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RootCMSClient,
|
|
3
3
|
getCmsPlugin
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-F4SODS5S.js";
|
|
5
5
|
|
|
6
6
|
// core/cron.ts
|
|
7
7
|
import { Timestamp as Timestamp3 } from "firebase-admin/firestore";
|
|
@@ -264,6 +264,181 @@ function extractDocRecords(schema, options) {
|
|
|
264
264
|
return records;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// core/search-query.ts
|
|
268
|
+
var SMART_QUOTES = /[“”„«»]/g;
|
|
269
|
+
function parseQuery(input) {
|
|
270
|
+
const out = {
|
|
271
|
+
phrases: [],
|
|
272
|
+
terms: [],
|
|
273
|
+
excluded: [],
|
|
274
|
+
excludedPhrases: []
|
|
275
|
+
};
|
|
276
|
+
if (!input) {
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
const s = input.replace(SMART_QUOTES, '"');
|
|
280
|
+
let i = 0;
|
|
281
|
+
while (i < s.length) {
|
|
282
|
+
while (i < s.length && isSpace(s[i])) {
|
|
283
|
+
i++;
|
|
284
|
+
}
|
|
285
|
+
if (i >= s.length) {
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
let negative = false;
|
|
289
|
+
if (s[i] === "-" && i + 1 < s.length && !isSpace(s[i + 1])) {
|
|
290
|
+
negative = true;
|
|
291
|
+
i++;
|
|
292
|
+
}
|
|
293
|
+
if (s[i] === '"') {
|
|
294
|
+
i++;
|
|
295
|
+
const start = i;
|
|
296
|
+
while (i < s.length && s[i] !== '"') {
|
|
297
|
+
i++;
|
|
298
|
+
}
|
|
299
|
+
const phrase = s.slice(start, i).trim();
|
|
300
|
+
if (i < s.length) {
|
|
301
|
+
i++;
|
|
302
|
+
}
|
|
303
|
+
if (phrase) {
|
|
304
|
+
if (negative) {
|
|
305
|
+
out.excludedPhrases.push(phrase);
|
|
306
|
+
} else {
|
|
307
|
+
out.phrases.push(phrase);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
const start = i;
|
|
312
|
+
while (i < s.length && !isSpace(s[i]) && s[i] !== '"') {
|
|
313
|
+
i++;
|
|
314
|
+
}
|
|
315
|
+
const term = s.slice(start, i);
|
|
316
|
+
if (term && term.replace(/^-+$/, "")) {
|
|
317
|
+
if (negative) {
|
|
318
|
+
out.excluded.push(term);
|
|
319
|
+
} else {
|
|
320
|
+
out.terms.push(term);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
327
|
+
function isSpace(ch) {
|
|
328
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
329
|
+
}
|
|
330
|
+
function tokenize(s) {
|
|
331
|
+
return s.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
|
|
332
|
+
}
|
|
333
|
+
function containsPhrase(text, phrase) {
|
|
334
|
+
const phraseTokens = tokenize(phrase);
|
|
335
|
+
if (phraseTokens.length === 0) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
const textTokens = tokenize(text);
|
|
339
|
+
if (textTokens.length < phraseTokens.length) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
outer: for (let i = 0; i <= textTokens.length - phraseTokens.length; i++) {
|
|
343
|
+
for (let j = 0; j < phraseTokens.length; j++) {
|
|
344
|
+
if (textTokens[i + j] !== phraseTokens[j]) {
|
|
345
|
+
continue outer;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
function isEmptyQuery(parsed) {
|
|
353
|
+
return parsed.phrases.length === 0 && parsed.terms.length === 0;
|
|
354
|
+
}
|
|
355
|
+
function executeQuery(index, parsed) {
|
|
356
|
+
if (isEmptyQuery(parsed)) {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
let acc = null;
|
|
360
|
+
for (const phrase of parsed.phrases) {
|
|
361
|
+
const candidates = index.search(phrase, {
|
|
362
|
+
combineWith: "AND",
|
|
363
|
+
fuzzy: false,
|
|
364
|
+
prefix: false
|
|
365
|
+
});
|
|
366
|
+
const matched = candidates.filter(
|
|
367
|
+
(r) => containsPhrase(String(r.text || ""), phrase) || containsPhrase(String(r.fieldLabel || ""), phrase)
|
|
368
|
+
);
|
|
369
|
+
acc = mergeAnd(acc, matched);
|
|
370
|
+
if (acc.size === 0) {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (parsed.terms.length > 0) {
|
|
375
|
+
const q = parsed.terms.join(" ");
|
|
376
|
+
const results = index.search(q, { combineWith: "AND" });
|
|
377
|
+
acc = mergeAnd(acc, results);
|
|
378
|
+
}
|
|
379
|
+
if (!acc || acc.size === 0) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
for (const term of parsed.excluded) {
|
|
383
|
+
for (const r of index.search(term)) {
|
|
384
|
+
acc.delete(String(r.id));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (const phrase of parsed.excludedPhrases) {
|
|
388
|
+
const candidates = index.search(phrase, {
|
|
389
|
+
combineWith: "AND",
|
|
390
|
+
fuzzy: false,
|
|
391
|
+
prefix: false
|
|
392
|
+
});
|
|
393
|
+
for (const r of candidates) {
|
|
394
|
+
if (containsPhrase(String(r.text || ""), phrase) || containsPhrase(String(r.fieldLabel || ""), phrase)) {
|
|
395
|
+
acc.delete(String(r.id));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return Array.from(acc.values()).sort((a, b) => b.score - a.score).map((entry) => ({
|
|
400
|
+
...entry.record,
|
|
401
|
+
score: entry.score,
|
|
402
|
+
terms: Array.from(entry.terms)
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
function mergeAnd(acc, results) {
|
|
406
|
+
if (acc === null) {
|
|
407
|
+
const next2 = /* @__PURE__ */ new Map();
|
|
408
|
+
for (const r of results) {
|
|
409
|
+
next2.set(String(r.id), {
|
|
410
|
+
score: r.score,
|
|
411
|
+
record: r,
|
|
412
|
+
terms: new Set(Array.isArray(r.terms) ? r.terms : [])
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return next2;
|
|
416
|
+
}
|
|
417
|
+
const byId = /* @__PURE__ */ new Map();
|
|
418
|
+
for (const r of results) {
|
|
419
|
+
byId.set(String(r.id), r);
|
|
420
|
+
}
|
|
421
|
+
const next = /* @__PURE__ */ new Map();
|
|
422
|
+
for (const [id, entry] of acc) {
|
|
423
|
+
const r = byId.get(id);
|
|
424
|
+
if (!r) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const terms = new Set(entry.terms);
|
|
428
|
+
if (Array.isArray(r.terms)) {
|
|
429
|
+
for (const t of r.terms) {
|
|
430
|
+
terms.add(t);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
next.set(id, {
|
|
434
|
+
score: entry.score + r.score,
|
|
435
|
+
record: entry.record,
|
|
436
|
+
terms
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return next;
|
|
440
|
+
}
|
|
441
|
+
|
|
267
442
|
// core/search-index.ts
|
|
268
443
|
var SHARD_MAX_CHARS = 5e5;
|
|
269
444
|
var MAX_INDEX_SIZE_BYTES = 50 * 1024 * 1024;
|
|
@@ -290,6 +465,46 @@ var MINISEARCH_OPTIONS = {
|
|
|
290
465
|
fuzzy: 0.2
|
|
291
466
|
}
|
|
292
467
|
};
|
|
468
|
+
function toSetOrNull(values) {
|
|
469
|
+
if (!values || values.length === 0) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
return new Set(values);
|
|
473
|
+
}
|
|
474
|
+
function resolveSearchIndexFilters(config) {
|
|
475
|
+
return {
|
|
476
|
+
includeCollections: toSetOrNull(config?.includeCollections),
|
|
477
|
+
excludeCollections: toSetOrNull(config?.excludeCollections),
|
|
478
|
+
includeDocIds: toSetOrNull(config?.includeDocIds),
|
|
479
|
+
excludeDocIds: toSetOrNull(config?.excludeDocIds)
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function isCollectionIndexable(filters, collectionId) {
|
|
483
|
+
if (filters.includeCollections && !filters.includeCollections.has(collectionId)) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
if (filters.excludeCollections?.has(collectionId)) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
function isDocIndexable(filters, docId) {
|
|
492
|
+
const slashIndex = docId.indexOf("/");
|
|
493
|
+
if (slashIndex < 0) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
const collectionId = docId.slice(0, slashIndex);
|
|
497
|
+
if (!isCollectionIndexable(filters, collectionId)) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
if (filters.includeDocIds && !filters.includeDocIds.has(docId)) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
if (filters.excludeDocIds?.has(docId)) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
293
508
|
function isTitleyDeepKey(deepKey) {
|
|
294
509
|
const last = deepKey.split(".").pop() || "";
|
|
295
510
|
return TITLEY_KEYS.has(last.toLowerCase());
|
|
@@ -298,7 +513,7 @@ function withWeight(rec) {
|
|
|
298
513
|
return { ...rec, weight: isTitleyDeepKey(rec.deepKey) ? 2 : 1 };
|
|
299
514
|
}
|
|
300
515
|
var SearchIndexService = class {
|
|
301
|
-
constructor(rootConfig, loadSchema) {
|
|
516
|
+
constructor(rootConfig, loadSchema, filtersOverride) {
|
|
302
517
|
this.cached = null;
|
|
303
518
|
this.rootConfig = rootConfig;
|
|
304
519
|
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
@@ -306,6 +521,20 @@ var SearchIndexService = class {
|
|
|
306
521
|
this.projectId = cmsPluginOptions.id || "default";
|
|
307
522
|
this.db = cmsPlugin.getFirestore();
|
|
308
523
|
this.loadSchema = loadSchema || ((id) => this.loadCollectionSchemaFromDist(id));
|
|
524
|
+
this.filters = resolveSearchIndexFilters(
|
|
525
|
+
filtersOverride ?? cmsPluginOptions.searchIndex
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
/** Returns true if the given collection passes the include/exclude filter. */
|
|
529
|
+
isCollectionIndexable(collectionId) {
|
|
530
|
+
return isCollectionIndexable(this.filters, collectionId);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Returns true if the given doc id passes the include/exclude filter (and
|
|
534
|
+
* its collection is itself indexable). Doc ids are `<collectionId>/<slug>`.
|
|
535
|
+
*/
|
|
536
|
+
isDocIndexable(docId) {
|
|
537
|
+
return isDocIndexable(this.filters, docId);
|
|
309
538
|
}
|
|
310
539
|
/** Returns the MiniSearch options used by both writers and readers. */
|
|
311
540
|
static getMiniSearchOptions() {
|
|
@@ -317,14 +546,17 @@ var SearchIndexService = class {
|
|
|
317
546
|
indexCollectionPath() {
|
|
318
547
|
return `Projects/${this.projectId}/${SEARCH_INDEX_SUBCOLLECTION}`;
|
|
319
548
|
}
|
|
320
|
-
/**
|
|
549
|
+
/**
|
|
550
|
+
* Lists collection ids by globbing `<rootDir>/collections/*.schema.ts`,
|
|
551
|
+
* filtered by the include/exclude config.
|
|
552
|
+
*/
|
|
321
553
|
async listCollectionIds() {
|
|
322
554
|
const collectionsDir = path.join(this.rootConfig.rootDir, "collections");
|
|
323
555
|
if (!fs.existsSync(collectionsDir)) {
|
|
324
556
|
return [];
|
|
325
557
|
}
|
|
326
558
|
const fileNames = await glob("*.schema.ts", { cwd: collectionsDir });
|
|
327
|
-
return fileNames.map((f) => f.slice(0, -10));
|
|
559
|
+
return fileNames.map((f) => f.slice(0, -10)).filter((id) => this.isCollectionIndexable(id));
|
|
328
560
|
}
|
|
329
561
|
/**
|
|
330
562
|
* Default schema loader: reads `dist/collections/<id>.schema.json`. These
|
|
@@ -438,23 +670,33 @@ var SearchIndexService = class {
|
|
|
438
670
|
snap.forEach((d) => batch.delete(d.ref));
|
|
439
671
|
await batch.commit();
|
|
440
672
|
}
|
|
441
|
-
/**
|
|
673
|
+
/**
|
|
674
|
+
* Lists the full set of live (collection, slug, doc) tuples for `Drafts`,
|
|
675
|
+
* filtered by the include/exclude config.
|
|
676
|
+
*/
|
|
442
677
|
async listAllDocs(collectionIds) {
|
|
443
678
|
const all = [];
|
|
444
679
|
for (const collectionId of collectionIds) {
|
|
445
680
|
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
446
681
|
const snap = await this.db.collection(path3).get();
|
|
447
682
|
snap.forEach((d) => {
|
|
683
|
+
const docId = `${collectionId}/${d.id}`;
|
|
684
|
+
if (!this.isDocIndexable(docId)) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
448
687
|
const data = d.data() || {};
|
|
449
688
|
if (!data.collection) data.collection = collectionId;
|
|
450
689
|
if (!data.slug) data.slug = d.id;
|
|
451
|
-
if (!data.id) data.id =
|
|
690
|
+
if (!data.id) data.id = docId;
|
|
452
691
|
all.push(data);
|
|
453
692
|
});
|
|
454
693
|
}
|
|
455
694
|
return all;
|
|
456
695
|
}
|
|
457
|
-
/**
|
|
696
|
+
/**
|
|
697
|
+
* Lists docs modified since `lastRun` across every collection, filtered by
|
|
698
|
+
* the include/exclude config.
|
|
699
|
+
*/
|
|
458
700
|
async listChangedDocs(collectionIds, lastRun) {
|
|
459
701
|
const all = [];
|
|
460
702
|
for (const collectionId of collectionIds) {
|
|
@@ -462,10 +704,14 @@ var SearchIndexService = class {
|
|
|
462
704
|
const q = this.db.collection(path3).where("sys.modifiedAt", ">=", lastRun);
|
|
463
705
|
const snap = await q.get();
|
|
464
706
|
snap.forEach((d) => {
|
|
707
|
+
const docId = `${collectionId}/${d.id}`;
|
|
708
|
+
if (!this.isDocIndexable(docId)) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
465
711
|
const data = d.data() || {};
|
|
466
712
|
if (!data.collection) data.collection = collectionId;
|
|
467
713
|
if (!data.slug) data.slug = d.id;
|
|
468
|
-
if (!data.id) data.id =
|
|
714
|
+
if (!data.id) data.id = docId;
|
|
469
715
|
all.push(data);
|
|
470
716
|
});
|
|
471
717
|
}
|
|
@@ -495,7 +741,12 @@ var SearchIndexService = class {
|
|
|
495
741
|
for (const collectionId of collectionIds) {
|
|
496
742
|
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
497
743
|
const snap = await this.db.collection(path3).select().get();
|
|
498
|
-
snap.forEach((d) =>
|
|
744
|
+
snap.forEach((d) => {
|
|
745
|
+
const docId = `${collectionId}/${d.id}`;
|
|
746
|
+
if (this.isDocIndexable(docId)) {
|
|
747
|
+
liveIds.add(docId);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
499
750
|
}
|
|
500
751
|
for (const id of knownIds) {
|
|
501
752
|
if (!liveIds.has(id)) {
|
|
@@ -561,6 +812,9 @@ var SearchIndexService = class {
|
|
|
561
812
|
snap.forEach((d) => {
|
|
562
813
|
const data = d.data() || {};
|
|
563
814
|
const slug = data.slug || d.id;
|
|
815
|
+
if (!this.isDocIndexable(`${collectionId}/${slug}`)) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
564
818
|
const records = extractDocRecords(schema, {
|
|
565
819
|
collection: collectionId,
|
|
566
820
|
slug,
|
|
@@ -632,7 +886,12 @@ var SearchIndexService = class {
|
|
|
632
886
|
for (const collectionId of collectionIds) {
|
|
633
887
|
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
634
888
|
const snap = await this.db.collection(path3).select().get();
|
|
635
|
-
snap.forEach((d) =>
|
|
889
|
+
snap.forEach((d) => {
|
|
890
|
+
const docId = `${collectionId}/${d.id}`;
|
|
891
|
+
if (this.isDocIndexable(docId)) {
|
|
892
|
+
liveIds.add(docId);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
636
895
|
}
|
|
637
896
|
let deletions = 0;
|
|
638
897
|
for (const docId of Object.keys(docMap)) {
|
|
@@ -712,7 +971,7 @@ var SearchIndexService = class {
|
|
|
712
971
|
if (!cached || !q.trim()) {
|
|
713
972
|
return { hits: [], meta: status };
|
|
714
973
|
}
|
|
715
|
-
const raw = cached.index
|
|
974
|
+
const raw = executeQuery(cached.index, parseQuery(q));
|
|
716
975
|
const hits = raw.slice(0, limit).map((r) => ({
|
|
717
976
|
id: String(r.id || ""),
|
|
718
977
|
docId: String(r.docId || ""),
|
|
@@ -222,6 +222,9 @@ function fieldType(field, options) {
|
|
|
222
222
|
if (field.type === "multiselect") {
|
|
223
223
|
return dom.type.array(dom.type.string);
|
|
224
224
|
}
|
|
225
|
+
if (field.type === "number") {
|
|
226
|
+
return dom.type.number;
|
|
227
|
+
}
|
|
225
228
|
if (field.type === "oneof") {
|
|
226
229
|
const oneOf = dom.create.namedTypeReference("RootCMSOneOf");
|
|
227
230
|
if (field.types && Array.isArray(field.types)) {
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateTypes
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XLX37FRL.js";
|
|
4
4
|
import {
|
|
5
5
|
RootCMSClient,
|
|
6
6
|
getCmsPlugin,
|
|
7
7
|
unmarshalData
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-F4SODS5S.js";
|
|
9
|
+
import "./chunk-CRK7N6RR.js";
|
|
9
10
|
import "./chunk-MLKGABMK.js";
|
|
10
11
|
|
|
11
12
|
// cli/cli.ts
|