* Don't require auth for .ping * Finally introducing loggers * Remove file list caching from disk_space_primitives I don't think this is required anymore with the sync model * Simplification of space primitives and more tests * More reliable online status * Add .zed settings * Remove selfUpdate argument from `writeFile` no longer used - Add `sync` config schema and document sync options - Move sync config handling to client boot logic - Move BootConfig type to ui_types.ts - Remove sync env vars from server - Update documentation for sync and architecture * Improve sync logic and proxy routing for full sync state * Add BoxProxy for late-bound client and improve Lua boot - Add isMobile syscall for editor * Add concurrency to sync process and log indexing duration * Better service worker lifecycle management
155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
import { syntaxTree } from "@codemirror/language";
|
|
import { Decoration } from "@codemirror/view";
|
|
import type { Client } from "../client.ts";
|
|
import { decoratorStateField, isCursorInRange, LinkWidget } from "./util.ts";
|
|
import {
|
|
fileName,
|
|
isBuiltinPath,
|
|
} from "@silverbulletmd/silverbullet/lib/resolve";
|
|
import {
|
|
encodePageURI,
|
|
encodeRef,
|
|
getNameFromPath,
|
|
parseToRef,
|
|
} from "@silverbulletmd/silverbullet/lib/ref";
|
|
import type { ClickEvent } from "@silverbulletmd/silverbullet/type/client";
|
|
import { wikiLinkRegex } from "../markdown_parser/constants.ts";
|
|
|
|
/**
|
|
* 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];
|
|
syntaxTree(state).iterate({
|
|
enter: ({ type, from, to }) => {
|
|
if (type.name !== "WikiLink") {
|
|
return;
|
|
}
|
|
|
|
const text = state.sliceDoc(from, to);
|
|
|
|
wikiLinkRegex.lastIndex = 0;
|
|
const match = wikiLinkRegex.exec(text);
|
|
if (!match || !match.groups) {
|
|
return;
|
|
}
|
|
|
|
const { leadingTrivia, stringRef, alias, trailingTrivia } =
|
|
match.groups;
|
|
|
|
const ref = parseToRef(stringRef);
|
|
|
|
let linkStatus: "file-missing" | "default" | "invalid" = "default";
|
|
|
|
if (!ref) {
|
|
linkStatus = "invalid";
|
|
} else if (ref.path === "" || isBuiltinPath(ref.path)) {
|
|
linkStatus = "default";
|
|
} else if (
|
|
Array.from(client.clientSystem.allKnownFiles)
|
|
.some((file) => file === ref.path)
|
|
) {
|
|
linkStatus = "default";
|
|
} else if (client.clientSystem.knownFilesLoaded) {
|
|
linkStatus = "file-missing";
|
|
}
|
|
|
|
let css = {
|
|
"file-missing": "sb-wiki-link-missing",
|
|
"invalid": "sb-wiki-link-invalid",
|
|
"default": "",
|
|
}[linkStatus];
|
|
|
|
const renderingSyntax =
|
|
client.ui.viewState.uiOptions.markdownSyntaxRendering;
|
|
if (isCursorInRange(state, [from, to]) || renderingSyntax) {
|
|
// Only attach a CSS class, then get out
|
|
if (linkStatus !== "default") {
|
|
widgets.push(
|
|
Decoration.mark({
|
|
class: css,
|
|
}).range(from + leadingTrivia.length, to - trailingTrivia.length),
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const cleanedPath = ref ? getNameFromPath(ref.path) : stringRef;
|
|
const helpText = {
|
|
"default": `Navigate to ${cleanedPath}`,
|
|
"file-missing": `Create ${cleanedPath}`,
|
|
"invalid": `Cannot create invalid file ${cleanedPath}`,
|
|
}[linkStatus];
|
|
|
|
let linkText = alias || stringRef;
|
|
|
|
// The `&& ref` is only there to make typescript happy
|
|
if (linkStatus === "default" && ref) {
|
|
const meta = client.ui.viewState.allPages.find((p) =>
|
|
parseToRef(p.ref)?.path === ref.path
|
|
);
|
|
|
|
const renderedRef = structuredClone(ref);
|
|
|
|
// We don't want to render the meta
|
|
renderedRef.meta = false;
|
|
// We also don't want to rendered the prefix of the path
|
|
renderedRef.path = fileName(renderedRef.path);
|
|
|
|
const prefix = (ref.details?.type === "position" ||
|
|
ref.details?.type === "linecolumn")
|
|
? ""
|
|
: (meta?.pageDecoration?.prefix ?? "");
|
|
|
|
linkText = alias || (prefix + encodeRef(renderedRef));
|
|
|
|
if (meta?.pageDecoration?.cssClasses) {
|
|
css += " sb-decorated-object " +
|
|
meta.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: helpText,
|
|
href: ref ? encodePageURI(encodeRef(ref)) : undefined,
|
|
cssClass: "sb-wiki-link " + css,
|
|
from,
|
|
callback: (e) => {
|
|
if (e.altKey) {
|
|
// Move cursor into the link
|
|
client.editorView.dispatch({
|
|
selection: { anchor: from + leadingTrivia.length },
|
|
});
|
|
client.focus();
|
|
return;
|
|
}
|
|
// Dispatch click event to navigate there without moving the cursor
|
|
const clickEvent: ClickEvent = {
|
|
page: client.currentName(),
|
|
ctrlKey: e.ctrlKey,
|
|
metaKey: e.metaKey,
|
|
altKey: e.altKey,
|
|
pos: from,
|
|
};
|
|
client.dispatchClickEvent(clickEvent).catch(
|
|
console.error,
|
|
);
|
|
},
|
|
}),
|
|
}).range(from, to),
|
|
);
|
|
},
|
|
});
|
|
return Decoration.set(widgets, true);
|
|
});
|
|
}
|