silverbullet/web/components/page_navigator.tsx

176 lines
5.7 KiB
TypeScript
Raw Normal View History

import { FilterList } from "./filter.tsx";
2024-07-14 17:29:43 +08:00
import { FilterOption } from "$lib/web.ts";
import { CompletionContext, CompletionResult } from "@codemirror/autocomplete";
2024-02-29 22:23:05 +08:00
import { PageMeta } from "../../plug-api/types.ts";
import { tagRegex as mdTagRegex } from "$common/markdown_parser/parser.ts";
2024-02-29 22:23:05 +08:00
const tagRegex = new RegExp(mdTagRegex.source, "g");
2023-12-22 02:49:25 +08:00
export function PageNavigator({
allPages,
onNavigate,
onModeSwitch,
completer,
vimMode,
mode,
darkMode,
currentPage,
}: {
allPages: PageMeta[];
vimMode: boolean;
darkMode: boolean;
mode: "page" | "meta" | "all";
onNavigate: (page: string | undefined) => void;
onModeSwitch: (mode: "page" | "meta" | "all") => void;
completer: (context: CompletionContext) => Promise<CompletionResult | null>;
currentPage?: string;
}) {
const options: FilterOption[] = [];
for (const pageMeta of allPages) {
// Sanitize the page name
if (!pageMeta.name) {
pageMeta.name = pageMeta.ref;
}
// Order by last modified date in descending order
let orderId = -new Date(pageMeta.lastModified).getTime();
2022-04-10 17:04:07 +08:00
// Unless it was opened in this session
if (pageMeta.lastOpened) {
orderId = -pageMeta.lastOpened;
}
// Or it's the currently open page
if (currentPage && currentPage === pageMeta.name) {
// ... then we put it all the way to the end
orderId = Infinity;
}
2024-01-24 21:52:07 +08:00
if (mode === "page") {
// Special behavior for regular pages
let description: string | undefined;
let aliases: string[] = [];
if (pageMeta.displayName) {
aliases.push(pageMeta.displayName);
}
if (Array.isArray(pageMeta.aliases)) {
aliases = aliases.concat(pageMeta.aliases);
}
if (aliases.length > 0) {
description = "(a.k.a. " + aliases.join(", ") + ") ";
}
if (pageMeta.tags) {
description = (description || "") +
pageMeta.tags.map((tag) => `#${tag}`).join(" ");
}
options.push({
...pageMeta,
2024-07-14 17:29:43 +08:00
name: (pageMeta.pageDecoration?.prefix ?? "") + pageMeta.name,
2024-01-24 21:52:07 +08:00
description,
orderId: orderId,
});
} else if (mode === "meta") {
// Special behavior for #template and #meta pages
2024-01-24 21:52:07 +08:00
options.push({
...pageMeta,
// Use the displayName or last bit of the path as the name
name: pageMeta.displayName || pageMeta.name.split("/").pop()!,
// And use the full path as the description
description: pageMeta.name,
hint: pageMeta.tags![0],
2024-01-24 21:52:07 +08:00
orderId: orderId,
});
} else {
// In mode "all" just show the full path and all tags
let description: string | undefined;
if (pageMeta.tags) {
description = (description || "") +
pageMeta.tags.map((tag) => `#${tag}`).join(" ");
}
options.push({
...pageMeta,
name: pageMeta.name,
description,
orderId: orderId,
});
2023-12-22 01:37:50 +08:00
}
}
2023-11-10 17:57:57 +08:00
let completePrefix = currentPage + "/";
2022-04-01 21:02:35 +08:00
if (currentPage && currentPage.includes("/")) {
const pieces = currentPage.split("/");
completePrefix = pieces.slice(0, pieces.length - 1).join("/") + "/";
2022-07-04 17:38:16 +08:00
} else if (currentPage && currentPage.includes(" ")) {
completePrefix = currentPage.split(" ")[0] + " ";
2022-04-01 21:02:35 +08:00
}
const pageNoun = mode === "meta" ? mode : "page";
return (
<FilterList
placeholder={mode === "page" ? "Page" : (mode === "meta" ? "#template or #meta page" : "Any page, also hidden")}
label="Open"
options={options}
vimMode={vimMode}
darkMode={darkMode}
completer={completer}
2023-12-22 02:49:25 +08:00
phrasePreprocessor={(phrase) => {
phrase = phrase.replaceAll(tagRegex, "").trim();
return phrase;
}}
onKeyPress={(key, text) => {
// Pages cannot start with ^, as documented in Page Name Rules
if (key === "^" && text === "^") {
switch(mode) {
case "page":
onModeSwitch("meta");
break;
case "meta":
onModeSwitch("all");
break;
case "all":
onModeSwitch("page");
break;
}
}
}}
2023-12-22 02:49:25 +08:00
preFilter={(options, phrase) => {
if (mode === "page") {
const allTags = phrase.match(tagRegex);
if (allTags) {
// Search phrase contains hash tags, let's pre-filter the results based on this
const filterTags = allTags.map((t) => t.slice(1));
options = options.filter((pageMeta) => {
if (!pageMeta.tags) {
return false;
}
return filterTags.every((tag) =>
pageMeta.tags.find((itemTag: string) => itemTag.startsWith(tag))
);
});
}
// Remove pages that are tagged as templates or meta
2023-12-22 02:49:25 +08:00
options = options.filter((pageMeta) => {
return !pageMeta.tags?.includes("template") &&
!pageMeta.tags?.includes("meta");
2023-12-22 02:49:25 +08:00
});
} else if (mode === "meta") {
// Filter on pages tagged with "template" or "meta"
options = options.filter((pageMeta) => {
return pageMeta.tags?.includes("template") ||
pageMeta.tags?.includes("meta");
});
2023-12-22 02:49:25 +08:00
}
if (mode !== "all") {
// Filter out hidden pages
options = options.filter((page) => !(page.pageDecoration?.hide === true));
}
return options;
2023-12-22 02:49:25 +08:00
}}
allowNew={true}
helpText={`Press <code>Enter</code> to open the selected ${pageNoun}, or <code>Shift-Enter</code> to create a new ${pageNoun} with this exact name.`}
newHint={`Create ${pageNoun}`}
2022-04-01 21:02:35 +08:00
completePrefix={completePrefix}
onSelect={(opt) => {
2024-01-24 21:56:02 +08:00
onNavigate(opt?.ref || opt?.name);
}}
/>
);
}