Avoid builtin page attributes to be overridden
This commit is contained in:
parent
8577fb95db
commit
82391682f6
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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}"`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 || []])];
|
||||||
|
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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
|
||||||
|
25
web/space.ts
25
web/space.ts
@ -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() {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user