From 82391682f6a313ab28056f92466a88c8946bb4f7 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Fri, 22 Dec 2023 11:27:07 +0100 Subject: [PATCH] Avoid builtin page attributes to be overridden --- plugs/index/api.ts | 6 +++++- plugs/index/builtins.ts | 1 + plugs/index/lint.ts | 10 +++++----- plugs/index/page.ts | 4 +++- plugs/query/query.ts | 2 ++ server/server_system.ts | 2 +- web/client.ts | 11 +---------- web/client_system.ts | 19 +------------------ web/components/page_navigator.tsx | 4 ++++ web/space.ts | 25 ++++++++++++++----------- web/sync_service.ts | 2 +- 11 files changed, 38 insertions(+), 48 deletions(-) diff --git a/plugs/index/api.ts b/plugs/index/api.ts index ff111f9..6aee939 100644 --- a/plugs/index/api.ts +++ b/plugs/index/api.ts @@ -192,7 +192,11 @@ export async function objectSourceProvider({ } export async function discoverSources() { - return (await datastore.query({ prefix: [indexKey, "tag"] })).map(( + return (await datastore.query({ + prefix: [indexKey, "tag"], + select: [{ name: "name" }], + distinct: true, + })).map(( { value }, ) => value.name); } diff --git a/plugs/index/builtins.ts b/plugs/index/builtins.ts index c444554..8a291ba 100644 --- a/plugs/index/builtins.ts +++ b/plugs/index/builtins.ts @@ -48,6 +48,7 @@ export const builtins: Record> = { attributeType: "!string", type: "!string", page: "!string", + readOnly: "!boolean", }, anchor: { ref: "!string", diff --git a/plugs/index/lint.ts b/plugs/index/lint.ts index 4240bbf..99ff3f0 100644 --- a/plugs/index/lint.ts +++ b/plugs/index/lint.ts @@ -13,11 +13,12 @@ import { extractFrontmatter } from "$sb/lib/frontmatter.ts"; export async function lintYAML({ tree }: LintEvent): Promise { const diagnostics: LintDiagnostic[] = []; const frontmatter = await extractFrontmatter(tree); + const tags = ["page", ...frontmatter.tags || []]; // Query all readOnly attributes for pages with this tag set const readOnlyAttributes = await queryObjects("attribute", { filter: ["and", ["=", ["attr", "tag"], [ "array", - frontmatter.tags.map((tag): QueryExpression => ["string", tag]), + tags.map((tag): QueryExpression => ["string", tag]), ]], [ "=", ["attr", "readOnly"], @@ -26,7 +27,6 @@ export async function lintYAML({ tree }: LintEvent): Promise { distinct: true, select: [{ name: "name" }], }); - // console.log("All read only attributes", readOnlyAttributes); await traverseTreeAsync(tree, async (node) => { if (node.type === "FrontMatterCode") { const lintResult = await lintYaml( @@ -75,17 +75,17 @@ const errorRegex = /\((\d+):(\d+)\)/; async function lintYaml( yamlText: string, from: number, - disallowedKeys: string[] = [], + readOnlyKeys: string[] = [], ): Promise { try { const parsed = await YAML.parse(yamlText); - for (const key of disallowedKeys) { + for (const key of readOnlyKeys) { if (parsed[key]) { return { from, to: from + yamlText.length, severity: "error", - message: `Disallowed key "${key}"`, + message: `Cannot set read-only attribute "${key}"`, }; } } diff --git a/plugs/index/page.ts b/plugs/index/page.ts index 77c470f..44a5a76 100644 --- a/plugs/index/page.ts +++ b/plugs/index/page.ts @@ -22,7 +22,9 @@ export async function indexPage({ name, tree }: IndexTreeEvent) { const toplevelAttributes = await extractAttributes(tree, false); // Push them all into the page object - pageMeta = { ...pageMeta, ...frontmatter, ...toplevelAttributes }; + // Note the order here, making sure that the actual page meta data overrules + // any attempt to manually set built-in attributes like 'name' or 'lastModified' + pageMeta = { ...frontmatter, ...toplevelAttributes, ...pageMeta }; pageMeta.tags = [...new Set(["page", ...pageMeta.tags || []])]; diff --git a/plugs/query/query.ts b/plugs/query/query.ts index 08d5ee1..410d393 100644 --- a/plugs/query/query.ts +++ b/plugs/query/query.ts @@ -167,5 +167,7 @@ async function allQuerySources(): Promise { const allObjectTypes: string[] = (await events.dispatchEvent("query_", {})) .flat(); + // console.log("All object types", allObjectTypes); + return [...allSources, ...allObjectTypes]; } diff --git a/server/server_system.ts b/server/server_system.ts index 1a0a2c8..e20bcd4 100644 --- a/server/server_system.ts +++ b/server/server_system.ts @@ -137,7 +137,7 @@ export class ServerSystem { ); this.listInterval = setInterval(() => { - space.updatePageList().catch(console.error); + space.updatePageListCache().catch(console.error); }, fileListInterval); eventHook.addLocalListener("file:changed", (path, localChange) => { diff --git a/web/client.ts b/web/client.ts index fef1e74..2215be9 100644 --- a/web/client.ts +++ b/web/client.ts @@ -235,7 +235,7 @@ export class Client { // console.log("Operations", operations); if (operations > 0) { // Update the page list - await this.space.updatePageList(); + await this.space.updatePageListCache(); } if (operations !== undefined) { // "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages @@ -499,15 +499,6 @@ export class Client { }, ); - // this.eventHook.addLocalListener("file:listed", (fileList: FileMeta[]) => { - // this.ui.viewDispatch({ - // type: "update-all-pages", - // pages: fileList.filter(this.space.isListedPage).map( - // fileMetaToPageMeta, - // ), - // }); - // }); - this.space.watch(); return localSpacePrimitives; diff --git a/web/client_system.ts b/web/client_system.ts index d80e658..be53e5f 100644 --- a/web/client_system.ts +++ b/web/client_system.ts @@ -140,23 +140,6 @@ export class ClientSystem { } }, ); - - // Debugging - // this.eventHook.addLocalListener("file:listed", (files) => { - // console.log("New file list", files); - // }); - - // this.eventHook.addLocalListener("file:changed", (file) => { - // console.log("File changed", file); - // }); - - // this.eventHook.addLocalListener("file:created", (file) => { - // console.log("File created", file); - // }); - - // this.eventHook.addLocalListener("file:deleted", (file) => { - // console.log("File deleted", file); - // }); } async init() { @@ -205,7 +188,7 @@ export class ClientSystem { async reloadPlugsFromSpace(space: Space) { console.log("Loading plugs"); - await space.updatePageList(); + // await space.updatePageList(); await this.system.unloadAll(); console.log("(Re)loading plugs"); await Promise.all((await space.listPlugs()).map(async (plugMeta) => { diff --git a/web/components/page_navigator.tsx b/web/components/page_navigator.tsx index fa3926b..b8f7d01 100644 --- a/web/components/page_navigator.tsx +++ b/web/components/page_navigator.tsx @@ -23,6 +23,10 @@ export function PageNavigator({ }) { const options: FilterOption[] = []; for (const pageMeta of allPages) { + // Sanitize the page name + if (!pageMeta.name) { + pageMeta.name = pageMeta.ref; + } // Order by last modified date in descending order let orderId = -new Date(pageMeta.lastModified).getTime(); // Unless it was opened in this session diff --git a/web/space.ts b/web/space.ts index bf02c64..876131f 100644 --- a/web/space.ts +++ b/web/space.ts @@ -13,7 +13,9 @@ const pageWatchInterval = 5000; export class Space { imageHeightCache = new LimitedMap(100); // url -> height widgetHeightCache = new LimitedMap(100); // bodytext -> height - cachedPageList: PageMeta[] = []; + + // Note: this is "clean" PageMeta, it doesn't contain custom attributes (it's fetched from the store) + private cachedPageList: PageMeta[] = []; debouncedImageCacheFlush = throttle(() => { this.ds.set(["cache", "imageHeight"], this.imageHeightCache).catch( @@ -57,7 +59,7 @@ export class Space { constructor( readonly spacePrimitives: SpacePrimitives, private ds: DataStore, - private eventHook: EventHook, + eventHook: EventHook, ) { // super(); this.ds.batchGet([["cache", "imageHeight"], ["cache", "widgetHeight"]]) @@ -70,23 +72,25 @@ export class Space { this.widgetHeightCache = new LimitedMap(100, widgetCache); } }); - eventHook.addLocalListener("file:listed", (files: FileMeta[]) => { - // console.log("Files listed", files); - this.cachedPageList = files.filter(this.isListedPage).map( - fileMetaToPageMeta, - ); - }); + // eventHook.addLocalListener("file:listed", (files: FileMeta[]) => { + // // console.log("Files listed", files); + // this.cachedPageList = files.filter(this.isListedPage).map( + // fileMetaToPageMeta, + // ); + // }); eventHook.addLocalListener("page:deleted", (pageName: string) => { if (this.watchedPages.has(pageName)) { // Stop watching deleted pages already this.watchedPages.delete(pageName); } }); + this.updatePageListCache().catch(console.error); } - public async updatePageList() { + public async updatePageListCache() { + console.log("Updating page list cache"); // This will trigger appropriate events automatically - await this.fetchPageList(); + this.cachedPageList = await this.fetchPageList(); } async deletePage(name: string): Promise { @@ -224,7 +228,6 @@ export class Space { } }); }, pageWatchInterval); - this.updatePageList().catch(console.error); } unwatch() { diff --git a/web/sync_service.ts b/web/sync_service.ts index 1749f30..2372957 100644 --- a/web/sync_service.ts +++ b/web/sync_service.ts @@ -382,7 +382,7 @@ export class NoSyncSyncService implements ISyncService { start() { setInterval(() => { // Trigger a page upload for change events - this.space.updatePageList().catch(console.error); + this.space.updatePageListCache().catch(console.error); }, spaceSyncInterval); }