diff --git a/lib/web.ts b/lib/web.ts index 13a79800..8b85b272 100644 --- a/lib/web.ts +++ b/lib/web.ts @@ -33,3 +33,9 @@ export type ActionButton = { export type EmojiConfig = { aliases: string[][]; }; + +export type Decoration = { + tag: string; + prefix: string; +}; + diff --git a/plugs/editor/complete.ts b/plugs/editor/complete.ts index 2e63fadb..f2fa760d 100644 --- a/plugs/editor/complete.ts +++ b/plugs/editor/complete.ts @@ -7,9 +7,20 @@ import { import { listFilesCached } from "../federation/federation.ts"; import { queryObjects } from "../index/plug_api.ts"; import { folderName } from "$sb/lib/resolve.ts"; +import { readSetting } from "$sb/lib/settings_page.ts"; +import { editor } from "$sb/syscalls.ts" +import type { Decoration } from "$lib/web.ts"; + +let decorations: Decoration[] = []; // Completion export async function pageComplete(completeEvent: CompleteEvent) { + try { + await updateDecoratorConfig(); + } catch (err: any) { + await editor.flashNotification(err.message, "error"); + } + // Try to match [[wikilink]] let isWikilink = true; let match = /\[\[([^\]@$#:\{}]*)$/.exec(completeEvent.linePrefix); @@ -82,10 +93,17 @@ export async function pageComplete(completeEvent: CompleteEvent) { from: completeEvent.pos - match[1].length, options: allPages.map((pageMeta) => { const completions: any[] = []; + let namePrefix = ""; + const decor = decorations.find(d => pageMeta.tags?.some((t: any) => d.tag === t)); + if (decor) { + namePrefix = decor.prefix; + } if (isWikilink) { if (pageMeta.displayName) { + const decoratedName = namePrefix + pageMeta.displayName; completions.push({ label: `${pageMeta.displayName}`, + displayLabel: decoratedName, boost: new Date(pageMeta.lastModified).getTime(), apply: pageMeta.tag === "template" ? pageMeta.name @@ -96,8 +114,10 @@ export async function pageComplete(completeEvent: CompleteEvent) { } if (Array.isArray(pageMeta.aliases)) { for (const alias of pageMeta.aliases) { + const decoratedName = namePrefix + alias; completions.push({ label: `${alias}`, + displayLabel: decoratedName, boost: new Date(pageMeta.lastModified).getTime(), apply: pageMeta.tag === "template" ? pageMeta.name @@ -107,8 +127,10 @@ export async function pageComplete(completeEvent: CompleteEvent) { }); } } + const decoratedName = namePrefix + pageMeta.name; completions.push({ label: `${pageMeta.name}`, + displayLabel: decoratedName, boost: new Date(pageMeta.lastModified).getTime(), type: "page", }); @@ -135,6 +157,7 @@ export async function pageComplete(completeEvent: CompleteEvent) { }; } + function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta { const name = fileMeta.name.substring(0, fileMeta.name.length - 3); return { @@ -146,3 +169,17 @@ function fileMetaToPageMeta(fileMeta: FileMeta): PageMeta { lastModified: new Date(fileMeta.lastModified).toISOString(), } as PageMeta; } + +let lastConfigUpdate = 0; + +async function updateDecoratorConfig() { + // Update at most every 5 seconds + if (Date.now() < lastConfigUpdate + 5000) return; + lastConfigUpdate = Date.now(); + const decoratorConfig = await readSetting("decorations"); + if (!decoratorConfig) { + return; + } + + decorations = decoratorConfig; +} diff --git a/plugs/markdown/markdown_render.ts b/plugs/markdown/markdown_render.ts index 467beace..f3c1cde3 100644 --- a/plugs/markdown/markdown_render.ts +++ b/plugs/markdown/markdown_render.ts @@ -10,6 +10,7 @@ import { import { encodePageRef, parsePageRef } from "$sb/lib/page_ref.ts"; import { Fragment, renderHtml, Tag } from "./html_render.ts"; import { isLocalPath } from "$sb/lib/resolve.ts"; +import { PageMeta } from "$sb/types.ts"; export type MarkdownRenderOptions = { failOnUnknown?: true; @@ -554,20 +555,29 @@ function traverseTag( export function renderMarkdownToHtml( t: ParseTree, options: MarkdownRenderOptions = {}, + allPages: PageMeta[] = [], ) { preprocess(t); const htmlTree = posPreservingRender(t, options); - if (htmlTree && options.translateUrls) { + if (htmlTree) { traverseTag(htmlTree, (t) => { if (typeof t === "string") { return; } - if (t.name === "img") { + if (t.name === "img" && options.translateUrls) { t.attrs!.src = options.translateUrls!(t.attrs!.src!, "image"); } if (t.name === "a" && t.attrs!.href) { - t.attrs!.href = options.translateUrls!(t.attrs!.href, "link"); + if (options.translateUrls) { + t.attrs!.href = options.translateUrls!(t.attrs!.href, "link"); + } + if (t.attrs!["data-ref"]?.length) { + const pageMeta = allPages.find(p => t.attrs!["data-ref"]!.startsWith(p.name)); + if (pageMeta) { + t.body = [(pageMeta.pageDecorations?.prefix ?? "") + t.body] + } + } if (t.body.length === 0) { t.body = [t.attrs!.href]; } diff --git a/type/web.ts b/type/web.ts index 3b373330..616b8063 100644 --- a/type/web.ts +++ b/type/web.ts @@ -5,6 +5,7 @@ import { defaultSettings } from "$common/settings.ts"; import { ActionButton, EmojiConfig, + Decoration, FilterOption, Notification, PanelMode, @@ -24,6 +25,7 @@ export type BuiltinSettings = { // Format: compatible with docker ignore spaceIgnore?: string; emoji?: EmojiConfig; + decorations?: Decoration[]; }; export type PanelConfig = { diff --git a/web/cm_plugins/markdown_widget.ts b/web/cm_plugins/markdown_widget.ts index 6a92d4e2..d7f23d73 100644 --- a/web/cm_plugins/markdown_widget.ts +++ b/web/cm_plugins/markdown_widget.ts @@ -93,7 +93,7 @@ export class MarkdownWidget extends WidgetType { extendedMarkdownLanguage, trimmedMarkdown, ); - + const html = renderMarkdownToHtml(mdTree, { // Annotate every element with its position so we can use it to put // the cursor there when the user clicks on the table. @@ -109,7 +109,7 @@ export class MarkdownWidget extends WidgetType { return url; }, preserveAttributes: true, - }); + }, this.client.ui.viewState.allPages); if (cachedHtml === html) { // HTML still same as in cache, no need to re-render diff --git a/web/cm_plugins/wiki_link.ts b/web/cm_plugins/wiki_link.ts index 797e633c..a0e4d733 100644 --- a/web/cm_plugins/wiki_link.ts +++ b/web/cm_plugins/wiki_link.ts @@ -66,9 +66,9 @@ export function cleanWikiLinkPlugin(client: Client) { } return; } - + const pageMeta = client.ui.viewState.allPages.find(p => p.name == url); const linkText = alias || - (url.includes("/") ? url.split("/").pop()! : url); + (pageMeta?.pageDecorations.prefix ?? "") + (url.includes("/") ? url.split("/").pop()! : url); // And replace it with a widget widgets.push( diff --git a/web/components/page_navigator.tsx b/web/components/page_navigator.tsx index b289dd63..e9adca83 100644 --- a/web/components/page_navigator.tsx +++ b/web/components/page_navigator.tsx @@ -1,5 +1,5 @@ import { FilterList } from "./filter.tsx"; -import { FilterOption } from "$lib/web.ts"; +import { FilterOption, Decoration } from "$lib/web.ts"; import { CompletionContext, CompletionResult } from "@codemirror/autocomplete"; import { PageMeta } from "../../plug-api/types.ts"; import { isFederationPath } from "$sb/lib/resolve.ts"; @@ -65,6 +65,7 @@ export function PageNavigator({ } options.push({ ...pageMeta, + name: (pageMeta.pageDecorations?.prefix ?? "") + pageMeta.name, description, orderId: orderId, }); diff --git a/web/components/top_bar.tsx b/web/components/top_bar.tsx index 28c64c6c..e827c03b 100644 --- a/web/components/top_bar.tsx +++ b/web/components/top_bar.tsx @@ -30,6 +30,7 @@ export function TopBar({ lhs, onClick, rhs, + pageNamePrefix, }: { pageName?: string; unsavedChanges: boolean; @@ -46,6 +47,7 @@ export function TopBar({ actionButtons: ActionButton[]; lhs?: ComponentChildren; rhs?: ComponentChildren; + pageNamePrefix?: string; }) { return (