More instant page navigator
This commit is contained in:
parent
8a181145ad
commit
3350c7f076
@ -97,11 +97,6 @@ export async function extractFrontmatter(
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.name) {
|
|
||||||
data.displayName = data.name;
|
|
||||||
delete data.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,31 +46,31 @@ export async function pageComplete(completeEvent: CompleteEvent) {
|
|||||||
const completions: any[] = [];
|
const completions: any[] = [];
|
||||||
if (pageMeta.displayName) {
|
if (pageMeta.displayName) {
|
||||||
completions.push({
|
completions.push({
|
||||||
label: pageMeta.displayName,
|
label: `${pageMeta.displayName}`,
|
||||||
boost: pageMeta.lastModified,
|
boost: new Date(pageMeta.lastModified).getTime(),
|
||||||
apply: isInTemplateContext
|
apply: isInTemplateContext
|
||||||
? pageMeta.name
|
? pageMeta.name
|
||||||
: `${pageMeta.name}|${pageMeta.displayName}`,
|
: `${pageMeta.name}|${pageMeta.displayName}`,
|
||||||
detail: "alias",
|
detail: `displayName: ${pageMeta.name}`,
|
||||||
type: "page",
|
type: "page",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (Array.isArray(pageMeta.aliases)) {
|
if (Array.isArray(pageMeta.aliases)) {
|
||||||
for (const alias of pageMeta.aliases) {
|
for (const alias of pageMeta.aliases) {
|
||||||
completions.push({
|
completions.push({
|
||||||
label: alias,
|
label: `${alias}`,
|
||||||
boost: pageMeta.lastModified,
|
boost: new Date(pageMeta.lastModified).getTime(),
|
||||||
apply: isInTemplateContext
|
apply: isInTemplateContext
|
||||||
? pageMeta.name
|
? pageMeta.name
|
||||||
: `${pageMeta.name}|${alias}`,
|
: `${pageMeta.name}|${alias}`,
|
||||||
detail: "alias",
|
detail: `alias: ${pageMeta.name}`,
|
||||||
type: "page",
|
type: "page",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
completions.push({
|
completions.push({
|
||||||
label: pageMeta.name,
|
label: `${pageMeta.name}`,
|
||||||
boost: pageMeta.lastModified,
|
boost: new Date(pageMeta.lastModified).getTime(),
|
||||||
type: "page",
|
type: "page",
|
||||||
});
|
});
|
||||||
return completions;
|
return completions;
|
||||||
|
@ -137,7 +137,7 @@ export class ServerSystem {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.listInterval = setInterval(() => {
|
this.listInterval = setInterval(() => {
|
||||||
space.updatePageListCache().catch(console.error);
|
space.updatePageList().catch(console.error);
|
||||||
}, fileListInterval);
|
}, fileListInterval);
|
||||||
|
|
||||||
eventHook.addLocalListener("file:changed", (path, localChange) => {
|
eventHook.addLocalListener("file:changed", (path, localChange) => {
|
||||||
|
@ -107,6 +107,9 @@ export class Client {
|
|||||||
spaceDataStore!: DataStore;
|
spaceDataStore!: DataStore;
|
||||||
mq!: DataStoreMQ;
|
mq!: DataStoreMQ;
|
||||||
|
|
||||||
|
// Used by the "wiki link" highlighter to check if a page exists
|
||||||
|
public allKnownPages = new Set<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private parent: Element,
|
private parent: Element,
|
||||||
public syncMode = false,
|
public syncMode = false,
|
||||||
@ -226,6 +229,8 @@ export class Client {
|
|||||||
console.error("Interval sync error", e);
|
console.error("Interval sync error", e);
|
||||||
}
|
}
|
||||||
}, pageSyncInterval);
|
}, pageSyncInterval);
|
||||||
|
|
||||||
|
this.updatePageListCache().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initSync() {
|
private initSync() {
|
||||||
@ -235,7 +240,7 @@ export class Client {
|
|||||||
// console.log("Operations", operations);
|
// console.log("Operations", operations);
|
||||||
if (operations > 0) {
|
if (operations > 0) {
|
||||||
// Update the page list
|
// Update the page list
|
||||||
await this.space.updatePageListCache();
|
await this.space.updatePageList();
|
||||||
}
|
}
|
||||||
if (operations !== undefined) {
|
if (operations !== undefined) {
|
||||||
// "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages
|
// "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages
|
||||||
@ -499,6 +504,28 @@ export class Client {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Caching a list of known pages for the wiki_link highlighter (that checks if a page exists)
|
||||||
|
this.eventHook.addLocalListener("page:saved", (pageName: string) => {
|
||||||
|
// Make sure this page is in the list of known pages
|
||||||
|
this.allKnownPages.add(pageName);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventHook.addLocalListener("page:deleted", (pageName: string) => {
|
||||||
|
this.allKnownPages.delete(pageName);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventHook.addLocalListener(
|
||||||
|
"file:listed",
|
||||||
|
(allFiles: FileMeta[]) => {
|
||||||
|
// Update list of known pages
|
||||||
|
this.allKnownPages = new Set(
|
||||||
|
allFiles.filter((f) => f.name.endsWith(".md")).map((f) =>
|
||||||
|
f.name.slice(0, -3)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.space.watch();
|
this.space.watch();
|
||||||
|
|
||||||
return localSpacePrimitives;
|
return localSpacePrimitives;
|
||||||
@ -582,11 +609,19 @@ export class Client {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async startPageNavigate() {
|
startPageNavigate() {
|
||||||
// Fetch all pages from the index
|
|
||||||
const pages = await this.system.queryObjects<PageMeta>("page", {});
|
|
||||||
// Then show the page navigator
|
// Then show the page navigator
|
||||||
this.ui.viewDispatch({ type: "start-navigate", pages });
|
this.ui.viewDispatch({ type: "start-navigate" });
|
||||||
|
this.updatePageListCache().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePageListCache() {
|
||||||
|
console.log("Updating page list cache");
|
||||||
|
const allPages = await this.system.queryObjects<PageMeta>("page", {});
|
||||||
|
this.ui.viewDispatch({
|
||||||
|
type: "update-page-list",
|
||||||
|
allPages,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private progressTimeout?: number;
|
private progressTimeout?: number;
|
||||||
@ -744,6 +779,7 @@ export class Client {
|
|||||||
actualResult = result;
|
actualResult = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// console.log("Compeltion result", actualResult);
|
||||||
return actualResult;
|
return actualResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import { resolvePath } from "$sb/lib/resolve.ts";
|
|||||||
/**
|
/**
|
||||||
* Plugin to hide path prefix when the cursor is not inside.
|
* Plugin to hide path prefix when the cursor is not inside.
|
||||||
*/
|
*/
|
||||||
export function cleanWikiLinkPlugin(editor: Client) {
|
export function cleanWikiLinkPlugin(client: Client) {
|
||||||
return decoratorStateField((state) => {
|
return decoratorStateField((state) => {
|
||||||
const widgets: any[] = [];
|
const widgets: any[] = [];
|
||||||
// let parentRange: [number, number];
|
// let parentRange: [number, number];
|
||||||
@ -28,21 +28,20 @@ export function cleanWikiLinkPlugin(editor: Client) {
|
|||||||
if (!match) return;
|
if (!match) return;
|
||||||
const [_fullMatch, page, pipePart, alias] = match;
|
const [_fullMatch, page, pipePart, alias] = match;
|
||||||
|
|
||||||
const allPages = editor.space.listPages();
|
let pageExists = !client.fullSyncCompleted;
|
||||||
let pageExists = !editor.fullSyncCompleted;
|
|
||||||
let cleanPage = page;
|
let cleanPage = page;
|
||||||
cleanPage = page.split(/[@$]/)[0];
|
cleanPage = page.split(/[@$]/)[0];
|
||||||
cleanPage = resolvePath(editor.currentPage!, cleanPage);
|
cleanPage = resolvePath(client.currentPage!, cleanPage);
|
||||||
const lowerCasePageName = cleanPage.toLowerCase();
|
const lowerCasePageName = cleanPage.toLowerCase();
|
||||||
for (const pageMeta of allPages) {
|
for (const pageName of client.allKnownPages) {
|
||||||
if (pageMeta.name.toLowerCase() === lowerCasePageName) {
|
if (pageName.toLowerCase() === lowerCasePageName) {
|
||||||
pageExists = true;
|
pageExists = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
cleanPage === "" ||
|
cleanPage === "" ||
|
||||||
editor.plugSpaceRemotePrimitives.isLikelyHandled(cleanPage)
|
client.plugSpaceRemotePrimitives.isLikelyHandled(cleanPage)
|
||||||
) {
|
) {
|
||||||
// Empty page name with local @anchor use or a link to a page that dynamically generated by a plug
|
// Empty page name with local @anchor use or a link to a page that dynamically generated by a plug
|
||||||
pageExists = true;
|
pageExists = true;
|
||||||
@ -90,19 +89,19 @@ export function cleanWikiLinkPlugin(editor: Client) {
|
|||||||
callback: (e) => {
|
callback: (e) => {
|
||||||
if (e.altKey) {
|
if (e.altKey) {
|
||||||
// Move cursor into the link
|
// Move cursor into the link
|
||||||
return editor.editorView.dispatch({
|
return client.editorView.dispatch({
|
||||||
selection: { anchor: from + 2 },
|
selection: { anchor: from + 2 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Dispatch click event to navigate there without moving the cursor
|
// Dispatch click event to navigate there without moving the cursor
|
||||||
const clickEvent: ClickEvent = {
|
const clickEvent: ClickEvent = {
|
||||||
page: editor.currentPage!,
|
page: client.currentPage!,
|
||||||
ctrlKey: e.ctrlKey,
|
ctrlKey: e.ctrlKey,
|
||||||
metaKey: e.metaKey,
|
metaKey: e.metaKey,
|
||||||
altKey: e.altKey,
|
altKey: e.altKey,
|
||||||
pos: from,
|
pos: from,
|
||||||
};
|
};
|
||||||
editor.dispatchAppEvent("page:click", clickEvent).catch(
|
client.dispatchAppEvent("page:click", clickEvent).catch(
|
||||||
console.error,
|
console.error,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -57,7 +57,7 @@ export function createEditorState(
|
|||||||
// Keyboard shortcuts from SETTINGS take precedense
|
// Keyboard shortcuts from SETTINGS take precedense
|
||||||
if (client.settings?.keyboardShortcuts) {
|
if (client.settings?.keyboardShortcuts) {
|
||||||
for (const shortcut of client.settings.keyboardShortcuts) {
|
for (const shortcut of client.settings.keyboardShortcuts) {
|
||||||
console.info("Configuring keyboard shortcut", shortcut);
|
// console.info("Configuring keyboard shortcut", shortcut);
|
||||||
commandKeyBindings.push({
|
commandKeyBindings.push({
|
||||||
key: shortcut.key,
|
key: shortcut.key,
|
||||||
mac: shortcut.mac,
|
mac: shortcut.mac,
|
||||||
@ -215,7 +215,7 @@ export function createEditorState(
|
|||||||
key: "Ctrl-k",
|
key: "Ctrl-k",
|
||||||
mac: "Cmd-k",
|
mac: "Cmd-k",
|
||||||
run: (): boolean => {
|
run: (): boolean => {
|
||||||
client.startPageNavigate().catch(console.error);
|
client.startPageNavigate();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,7 @@ export class MainUI {
|
|||||||
if (ev.touches.length === 2) {
|
if (ev.touches.length === 2) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
client.startPageNavigate().catch(console.error);
|
client.startPageNavigate();
|
||||||
}
|
}
|
||||||
// Launch the command palette using a three-finger tap
|
// Launch the command palette using a three-finger tap
|
||||||
if (ev.touches.length === 3) {
|
if (ev.touches.length === 3) {
|
||||||
@ -251,7 +251,7 @@ export class MainUI {
|
|||||||
icon: BookIcon,
|
icon: BookIcon,
|
||||||
description: `Open page (${isMacLike() ? "Cmd-k" : "Ctrl-k"})`,
|
description: `Open page (${isMacLike() ? "Cmd-k" : "Ctrl-k"})`,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
client.startPageNavigate().catch(console.error);
|
client.startPageNavigate();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -45,20 +45,26 @@ export default function reducer(
|
|||||||
...state,
|
...state,
|
||||||
syncFailures: action.syncSuccess ? 0 : state.syncFailures + 1,
|
syncFailures: action.syncSuccess ? 0 : state.syncFailures + 1,
|
||||||
};
|
};
|
||||||
case "start-navigate": {
|
case "update-page-list": {
|
||||||
// Let's move over any "lastOpened" times to the "allPages" list
|
// Let's move over any "lastOpened" times to the "allPages" list
|
||||||
const oldPageMeta = new Map(
|
const oldPageMeta = new Map(
|
||||||
[...state.allPages].map((pm) => [pm.name, pm]),
|
[...state.allPages].map((pm) => [pm.name, pm]),
|
||||||
);
|
);
|
||||||
for (const pageMeta of action.pages) {
|
for (const pageMeta of action.allPages) {
|
||||||
const oldPageMetaItem = oldPageMeta.get(pageMeta.name);
|
const oldPageMetaItem = oldPageMeta.get(pageMeta.name);
|
||||||
if (oldPageMetaItem && oldPageMetaItem.lastOpened) {
|
if (oldPageMetaItem && oldPageMetaItem.lastOpened) {
|
||||||
pageMeta.lastOpened = oldPageMetaItem.lastOpened;
|
pageMeta.lastOpened = oldPageMetaItem.lastOpened;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
allPages: action.allPages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "start-navigate": {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
allPages: action.pages,
|
|
||||||
showPageNavigator: true,
|
showPageNavigator: true,
|
||||||
showCommandPalette: false,
|
showCommandPalette: false,
|
||||||
showFilterBox: false,
|
showFilterBox: false,
|
||||||
|
26
web/space.ts
26
web/space.ts
@ -14,9 +14,6 @@ export class Space {
|
|||||||
imageHeightCache = new LimitedMap<number>(100); // url -> height
|
imageHeightCache = new LimitedMap<number>(100); // url -> height
|
||||||
widgetHeightCache = new LimitedMap<number>(100); // bodytext -> height
|
widgetHeightCache = new LimitedMap<number>(100); // bodytext -> height
|
||||||
|
|
||||||
// Note: this is "clean" PageMeta, it doesn't contain custom attributes (it's fetched from the store)
|
|
||||||
private cachedPageList: PageMeta[] = [];
|
|
||||||
|
|
||||||
debouncedImageCacheFlush = throttle(() => {
|
debouncedImageCacheFlush = throttle(() => {
|
||||||
this.ds.set(["cache", "imageHeight"], this.imageHeightCache).catch(
|
this.ds.set(["cache", "imageHeight"], this.imageHeightCache).catch(
|
||||||
console.error,
|
console.error,
|
||||||
@ -72,25 +69,18 @@ export class Space {
|
|||||||
this.widgetHeightCache = new LimitedMap(100, widgetCache);
|
this.widgetHeightCache = new LimitedMap(100, widgetCache);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// eventHook.addLocalListener("file:listed", (files: FileMeta[]) => {
|
|
||||||
// // console.log("Files listed", files);
|
|
||||||
// this.cachedPageList = files.filter(this.isListedPage).map(
|
|
||||||
// fileMetaToPageMeta,
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
eventHook.addLocalListener("page:deleted", (pageName: string) => {
|
eventHook.addLocalListener("page:deleted", (pageName: string) => {
|
||||||
if (this.watchedPages.has(pageName)) {
|
if (this.watchedPages.has(pageName)) {
|
||||||
// Stop watching deleted pages already
|
// Stop watching deleted pages already
|
||||||
this.watchedPages.delete(pageName);
|
this.watchedPages.delete(pageName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.updatePageListCache().catch(console.error);
|
this.updatePageList().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updatePageListCache() {
|
public async updatePageList() {
|
||||||
console.log("Updating page list cache");
|
// The only reason to do this is to trigger events
|
||||||
// This will trigger appropriate events automatically
|
await this.fetchPageList();
|
||||||
this.cachedPageList = await this.fetchPageList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePage(name: string): Promise<void> {
|
async deletePage(name: string): Promise<void> {
|
||||||
@ -104,10 +94,6 @@ export class Space {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
listPages(): PageMeta[] {
|
|
||||||
return this.cachedPageList;
|
|
||||||
}
|
|
||||||
|
|
||||||
async listPlugs(): Promise<FileMeta[]> {
|
async listPlugs(): Promise<FileMeta[]> {
|
||||||
const files = await this.deduplicatedFileList();
|
const files = await this.deduplicatedFileList();
|
||||||
return files
|
return files
|
||||||
@ -139,10 +125,6 @@ export class Space {
|
|||||||
selfUpdate,
|
selfUpdate,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!this.cachedPageList.find((page) => page.name === pageMeta.name)) {
|
|
||||||
// New page, let's cache it
|
|
||||||
this.cachedPageList.push(pageMeta);
|
|
||||||
}
|
|
||||||
// Note: we don't do very elaborate cache invalidation work here, quite quickly the cache will be flushed anyway
|
// Note: we don't do very elaborate cache invalidation work here, quite quickly the cache will be flushed anyway
|
||||||
return pageMeta;
|
return pageMeta;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -382,7 +382,7 @@ export class NoSyncSyncService implements ISyncService {
|
|||||||
start() {
|
start() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
// Trigger a page upload for change events
|
// Trigger a page upload for change events
|
||||||
this.space.updatePageListCache().catch(console.error);
|
this.space.updatePageList().catch(console.error);
|
||||||
}, spaceSyncInterval);
|
}, spaceSyncInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,8 @@ export type Action =
|
|||||||
| { type: "page-changed" }
|
| { type: "page-changed" }
|
||||||
| { type: "page-saved" }
|
| { type: "page-saved" }
|
||||||
| { type: "sync-change"; syncSuccess: boolean }
|
| { type: "sync-change"; syncSuccess: boolean }
|
||||||
| { type: "start-navigate"; pages: PageMeta[] }
|
| { type: "update-page-list"; allPages: PageMeta[] }
|
||||||
|
| { type: "start-navigate" }
|
||||||
| { type: "stop-navigate" }
|
| { type: "stop-navigate" }
|
||||||
| {
|
| {
|
||||||
type: "update-commands";
|
type: "update-commands";
|
||||||
|
Loading…
Reference in New Issue
Block a user