From 3ca132e16ad4607adbbbed23897e8c5db2c8c5a5 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Thu, 25 Jul 2024 15:18:58 +0200 Subject: [PATCH] Fixes #154 --- plugs/editor/complete.ts | 40 ++++++++++++++++++++++++------- web/client.ts | 21 +++++++++++++++- web/components/page_navigator.tsx | 15 ++++++++---- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/plugs/editor/complete.ts b/plugs/editor/complete.ts index 717385f1..cb29f2cd 100644 --- a/plugs/editor/complete.ts +++ b/plugs/editor/complete.ts @@ -9,6 +9,7 @@ import { listFilesCached } from "../federation/federation.ts"; import { queryObjects } from "../index/plug_api.ts"; import { folderName } from "$sb/lib/resolve.ts"; import { decoration } from "$sb/syscalls.ts"; +import type { LinkObject } from "../index/page_links.ts"; // A meta page is a page tagged with either #template or #meta const isMetaPageFilter: QueryExpression = ["or", ["=", ["attr", "tags"], [ @@ -66,14 +67,34 @@ export async function pageComplete(completeEvent: CompleteEvent) { // Include both pages and meta in page completion in ```include and ```template blocks allPages = await queryObjects("page", {}, 5); } else { - // Otherwise, just complete non-meta pages - allPages = await queryObjects("page", { - filter: ["not", isMetaPageFilter], - }, 5); - // and attachments - allPages = allPages.concat( - await queryObjects("attachment", {}, 5), - ); + // This is the most common case, we're combining three types of completions here: + allPages = (await Promise.all([ + // All non-meta pages + queryObjects("page", { + filter: ["not", isMetaPageFilter], + }, 5), + // All attachments + queryObjects("attachment", {}, 5), + // And all links to non-existing pages (to augment the existing ones) + queryObjects("link", { + filter: ["and", ["attr", "toPage"], ["not", ["call", "pageExists", [[ + "attr", + "toPage", + ]]]]], + select: [{ name: "toPage" }], + }, 5).then((brokenLinks) => + // Rewrite them to PageMeta shaped objects + brokenLinks.map((link): PageMeta => ({ + ref: link.toPage!, + tag: "page", + tags: ["non-existing"], // Picked up later in completion + name: link.toPage!, + created: "", + lastModified: "", + perm: "rw", + })) + ), + ])).flat(); } // Don't complete hidden pages @@ -143,6 +164,9 @@ export async function pageComplete(completeEvent: CompleteEvent) { label: pageMeta.name, displayLabel: decoratedName, boost: new Date(pageMeta.lastModified).getTime(), + detail: pageMeta.tags?.includes("non-existing") + ? "Linked but not created" + : undefined, type: "page", }); } else { diff --git a/web/client.ts b/web/client.ts index 83a39de7..c2c4d545 100644 --- a/web/client.ts +++ b/web/client.ts @@ -62,6 +62,7 @@ import { LimitedMap } from "$lib/limited_map.ts"; import { plugPrefix } from "$common/spaces/constants.ts"; import { lezerToParseTree } from "$common/markdown_parser/parse_tree.ts"; import { findNodeMatching } from "$sb/lib/tree.ts"; +import type { LinkObject } from "../plugs/index/page_links.ts"; const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/; @@ -787,9 +788,27 @@ export class Client { return; } const allPages = await this.clientSystem.queryObjects("page", {}); + const allBrokenLinkPages = (await this.clientSystem.queryObjects< + LinkObject + >("link", { + filter: ["and", ["attr", "toPage"], ["not", ["call", "pageExists", [[ + "attr", + "toPage", + ]]]]], + select: [{ name: "toPage" }], + })).map((link): PageMeta => ({ + ref: link.toPage!, + tag: "page", + _isBrokenLink: true, + name: link.toPage!, + created: "", + lastModified: "", + perm: "rw", + })); + this.ui.viewDispatch({ type: "update-page-list", - allPages, + allPages: allPages.concat(allBrokenLinkPages), }); } diff --git a/web/components/page_navigator.tsx b/web/components/page_navigator.tsx index 262f3d10..0dec06cf 100644 --- a/web/components/page_navigator.tsx +++ b/web/components/page_navigator.tsx @@ -65,6 +65,7 @@ export function PageNavigator({ name: (pageMeta.pageDecoration?.prefix ?? "") + pageMeta.name, description, orderId: orderId, + hint: pageMeta._isBrokenLink ? "Create page" : undefined, }); } else if (mode === "meta") { // Special behavior for #template and #meta pages @@ -99,11 +100,15 @@ export function PageNavigator({ } else if (currentPage && currentPage.includes(" ")) { completePrefix = currentPage.split(" ")[0] + " "; } - + const pageNoun = mode === "meta" ? mode : "page"; return ( { // Pages cannot start with ^, as documented in Page Name Rules if (key === "^" && text === "^") { - switch(mode) { + switch (mode) { case "page": onModeSwitch("meta"); break; @@ -159,7 +164,9 @@ export function PageNavigator({ if (mode !== "all") { // Filter out hidden pages - options = options.filter((page) => !(page.pageDecoration?.hide === true)); + options = options.filter((page) => + !(page.pageDecoration?.hide === true) + ); } return options; }}