1
0

Avoid builtin page attributes to be overridden

This commit is contained in:
Zef Hemel 2023-12-22 11:27:07 +01:00
parent 8577fb95db
commit 82391682f6
11 changed files with 38 additions and 48 deletions

View File

@ -192,7 +192,11 @@ export async function objectSourceProvider({
} }
export async function discoverSources() { 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 },
) => value.name); ) => value.name);
} }

View File

@ -48,6 +48,7 @@ export const builtins: Record<string, Record<string, string>> = {
attributeType: "!string", attributeType: "!string",
type: "!string", type: "!string",
page: "!string", page: "!string",
readOnly: "!boolean",
}, },
anchor: { anchor: {
ref: "!string", ref: "!string",

View File

@ -13,11 +13,12 @@ import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
export async function lintYAML({ tree }: LintEvent): Promise<LintDiagnostic[]> { export async function lintYAML({ tree }: LintEvent): Promise<LintDiagnostic[]> {
const diagnostics: LintDiagnostic[] = []; const diagnostics: LintDiagnostic[] = [];
const frontmatter = await extractFrontmatter(tree); const frontmatter = await extractFrontmatter(tree);
const tags = ["page", ...frontmatter.tags || []];
// Query all readOnly attributes for pages with this tag set // Query all readOnly attributes for pages with this tag set
const readOnlyAttributes = await queryObjects<AttributeObject>("attribute", { const readOnlyAttributes = await queryObjects<AttributeObject>("attribute", {
filter: ["and", ["=", ["attr", "tag"], [ filter: ["and", ["=", ["attr", "tag"], [
"array", "array",
frontmatter.tags.map((tag): QueryExpression => ["string", tag]), tags.map((tag): QueryExpression => ["string", tag]),
]], [ ]], [
"=", "=",
["attr", "readOnly"], ["attr", "readOnly"],
@ -26,7 +27,6 @@ export async function lintYAML({ tree }: LintEvent): Promise<LintDiagnostic[]> {
distinct: true, distinct: true,
select: [{ name: "name" }], select: [{ name: "name" }],
}); });
// console.log("All read only attributes", readOnlyAttributes);
await traverseTreeAsync(tree, async (node) => { await traverseTreeAsync(tree, async (node) => {
if (node.type === "FrontMatterCode") { if (node.type === "FrontMatterCode") {
const lintResult = await lintYaml( const lintResult = await lintYaml(
@ -75,17 +75,17 @@ const errorRegex = /\((\d+):(\d+)\)/;
async function lintYaml( async function lintYaml(
yamlText: string, yamlText: string,
from: number, from: number,
disallowedKeys: string[] = [], readOnlyKeys: string[] = [],
): Promise<LintDiagnostic | undefined> { ): Promise<LintDiagnostic | undefined> {
try { try {
const parsed = await YAML.parse(yamlText); const parsed = await YAML.parse(yamlText);
for (const key of disallowedKeys) { for (const key of readOnlyKeys) {
if (parsed[key]) { if (parsed[key]) {
return { return {
from, from,
to: from + yamlText.length, to: from + yamlText.length,
severity: "error", severity: "error",
message: `Disallowed key "${key}"`, message: `Cannot set read-only attribute "${key}"`,
}; };
} }
} }

View File

@ -22,7 +22,9 @@ export async function indexPage({ name, tree }: IndexTreeEvent) {
const toplevelAttributes = await extractAttributes(tree, false); const toplevelAttributes = await extractAttributes(tree, false);
// Push them all into the page object // 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 || []])]; pageMeta.tags = [...new Set(["page", ...pageMeta.tags || []])];

View File

@ -167,5 +167,7 @@ async function allQuerySources(): Promise<string[]> {
const allObjectTypes: string[] = (await events.dispatchEvent("query_", {})) const allObjectTypes: string[] = (await events.dispatchEvent("query_", {}))
.flat(); .flat();
// console.log("All object types", allObjectTypes);
return [...allSources, ...allObjectTypes]; return [...allSources, ...allObjectTypes];
} }

View File

@ -137,7 +137,7 @@ export class ServerSystem {
); );
this.listInterval = setInterval(() => { this.listInterval = setInterval(() => {
space.updatePageList().catch(console.error); space.updatePageListCache().catch(console.error);
}, fileListInterval); }, fileListInterval);
eventHook.addLocalListener("file:changed", (path, localChange) => { eventHook.addLocalListener("file:changed", (path, localChange) => {

View File

@ -235,7 +235,7 @@ export class Client {
// console.log("Operations", operations); // console.log("Operations", operations);
if (operations > 0) { if (operations > 0) {
// Update the page list // Update the page list
await this.space.updatePageList(); await this.space.updatePageListCache();
} }
if (operations !== undefined) { if (operations !== undefined) {
// "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages // "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(); this.space.watch();
return localSpacePrimitives; return localSpacePrimitives;

View File

@ -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() { async init() {
@ -205,7 +188,7 @@ export class ClientSystem {
async reloadPlugsFromSpace(space: Space) { async reloadPlugsFromSpace(space: Space) {
console.log("Loading plugs"); console.log("Loading plugs");
await space.updatePageList(); // await space.updatePageList();
await this.system.unloadAll(); await this.system.unloadAll();
console.log("(Re)loading plugs"); console.log("(Re)loading plugs");
await Promise.all((await space.listPlugs()).map(async (plugMeta) => { await Promise.all((await space.listPlugs()).map(async (plugMeta) => {

View File

@ -23,6 +23,10 @@ export function PageNavigator({
}) { }) {
const options: FilterOption[] = []; const options: FilterOption[] = [];
for (const pageMeta of allPages) { for (const pageMeta of allPages) {
// Sanitize the page name
if (!pageMeta.name) {
pageMeta.name = pageMeta.ref;
}
// Order by last modified date in descending order // Order by last modified date in descending order
let orderId = -new Date(pageMeta.lastModified).getTime(); let orderId = -new Date(pageMeta.lastModified).getTime();
// Unless it was opened in this session // Unless it was opened in this session

View File

@ -13,7 +13,9 @@ const pageWatchInterval = 5000;
export class Space { export class Space {
imageHeightCache = new LimitedMap<number>(100); // url -> height imageHeightCache = new LimitedMap<number>(100); // url -> height
widgetHeightCache = new LimitedMap<number>(100); // bodytext -> height widgetHeightCache = new LimitedMap<number>(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(() => { debouncedImageCacheFlush = throttle(() => {
this.ds.set(["cache", "imageHeight"], this.imageHeightCache).catch( this.ds.set(["cache", "imageHeight"], this.imageHeightCache).catch(
@ -57,7 +59,7 @@ export class Space {
constructor( constructor(
readonly spacePrimitives: SpacePrimitives, readonly spacePrimitives: SpacePrimitives,
private ds: DataStore, private ds: DataStore,
private eventHook: EventHook, eventHook: EventHook,
) { ) {
// super(); // super();
this.ds.batchGet([["cache", "imageHeight"], ["cache", "widgetHeight"]]) this.ds.batchGet([["cache", "imageHeight"], ["cache", "widgetHeight"]])
@ -70,23 +72,25 @@ export class Space {
this.widgetHeightCache = new LimitedMap(100, widgetCache); this.widgetHeightCache = new LimitedMap(100, widgetCache);
} }
}); });
eventHook.addLocalListener("file:listed", (files: FileMeta[]) => { // eventHook.addLocalListener("file:listed", (files: FileMeta[]) => {
// console.log("Files listed", files); // // console.log("Files listed", files);
this.cachedPageList = files.filter(this.isListedPage).map( // this.cachedPageList = files.filter(this.isListedPage).map(
fileMetaToPageMeta, // fileMetaToPageMeta,
); // );
}); // });
eventHook.addLocalListener("page:deleted", (pageName: string) => { eventHook.addLocalListener("page:deleted", (pageName: string) => {
if (this.watchedPages.has(pageName)) { if (this.watchedPages.has(pageName)) {
// Stop watching deleted pages already // Stop watching deleted pages already
this.watchedPages.delete(pageName); 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 // This will trigger appropriate events automatically
await this.fetchPageList(); this.cachedPageList = await this.fetchPageList();
} }
async deletePage(name: string): Promise<void> { async deletePage(name: string): Promise<void> {
@ -224,7 +228,6 @@ export class Space {
} }
}); });
}, pageWatchInterval); }, pageWatchInterval);
this.updatePageList().catch(console.error);
} }
unwatch() { unwatch() {

View File

@ -382,7 +382,7 @@ export class NoSyncSyncService implements ISyncService {
start() { start() {
setInterval(() => { setInterval(() => {
// Trigger a page upload for change events // Trigger a page upload for change events
this.space.updatePageList().catch(console.error); this.space.updatePageListCache().catch(console.error);
}, spaceSyncInterval); }, spaceSyncInterval);
} }