import type { ClickEvent } from "@silverbulletmd/silverbullet/types"; import { syntaxTree } from "@codemirror/language"; import { Decoration } from "@codemirror/view"; import type { Client } from "../client.ts"; import { decoratorStateField, isCursorInRange, LinkWidget } from "./util.ts"; import { resolvePath } from "@silverbulletmd/silverbullet/lib/resolve"; import { encodePageRef, encodePageURI, parsePageRef, } from "@silverbulletmd/silverbullet/lib/page_ref"; /** * Plugin to hide path prefix when the cursor is not inside. */ export function cleanWikiLinkPlugin(client: Client) { return decoratorStateField((state) => { const widgets: any[] = []; // let parentRange: [number, number]; const allKnownFiles = client.clientSystem.allKnownFiles; syntaxTree(state).iterate({ enter: ({ type, from, to }) => { if (type.name !== "WikiLink") { return; } const text = state.sliceDoc(from, to); const match = /(!?\[\[)([^\]\|]+)(?:\|([^\]]+))?(\]\])/g.exec(text); if (!match) return; const [_fullMatch, firstMark, url, alias, lastMark] = match; if (firstMark.startsWith("!")) { // Is inline image return; } let fileExists = !client.fullSyncCompleted; const pageRef = parsePageRef(url); pageRef.page = resolvePath(client.currentPage, "/" + pageRef.page); const lowerCasePageName = pageRef.page.toLowerCase(); for (const fileName of allKnownFiles) { if ( fileName.toLowerCase().replace(/\.md$/, "") === lowerCasePageName ) { fileExists = true; break; } } if ( pageRef.page === "" || client.plugSpaceRemotePrimitives.isLikelyHandled(pageRef.page) ) { // Empty page name with local @anchor use or a link to a page that dynamically generated by a plug fileExists = true; } if (isCursorInRange(state, [from, to])) { // Only attach a CSS class, then get out if (!fileExists) { widgets.push( Decoration.mark({ class: "sb-wiki-link-page-missing", }).range( from + firstMark.length, to - lastMark.length, ), ); } return; } const pageMeta = client.ui.viewState.allPages.find((p) => p.name == url ); let cleanLinkText = url.includes("/") ? url.split("/").pop()! : url; if (cleanLinkText.startsWith("^")) { // Hide the ^ prefix cleanLinkText = cleanLinkText.slice(1); } const linkText = alias || ((pageMeta?.pageDecoration?.prefix ?? "") + cleanLinkText); let cssClass = fileExists ? "sb-wiki-link-page" : "sb-wiki-link-page-missing"; if (pageMeta?.pageDecoration?.cssClasses) { cssClass += " sb-decorated-object " + pageMeta.pageDecoration.cssClasses.join(" ").replaceAll( /[^a-zA-Z0-9-_ ]/g, "", ); } // And replace it with a widget widgets.push( Decoration.replace({ widget: new LinkWidget( { text: linkText, title: fileExists ? `Navigate to ${encodePageRef(pageRef)}` : `Create ${pageRef.page}`, href: `/${encodePageURI(encodePageRef(pageRef))}`, cssClass, from, callback: (e) => { if (e.altKey) { // Move cursor into the link client.editorView.dispatch({ selection: { anchor: from + firstMark.length }, }); client.focus(); return; } // Dispatch click event to navigate there without moving the cursor const clickEvent: ClickEvent = { page: client.currentPage, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey, pos: from, }; client.dispatchAppEvent("page:click", clickEvent).catch( console.error, ); }, }, ), }).range(from, to), ); }, }); return Decoration.set(widgets, true); }); }