pull/73/head
Zef Hemel 2022-08-30 10:44:20 +02:00
parent 0763342f5d
commit 799144a74e
10 changed files with 122 additions and 23 deletions

View File

@ -31,7 +31,7 @@ export function save(): Promise<void> {
export function navigate( export function navigate(
name: string, name: string,
pos?: number, pos?: string | number,
replaceState = false replaceState = false
): Promise<void> { ): Promise<void> {
return syscall("editor.navigate", name, pos, replaceState); return syscall("editor.navigate", name, pos, replaceState);

View File

@ -0,0 +1,50 @@
import { collectNodesOfType, traverseTree } from "@silverbulletmd/common/tree";
import {
batchSet,
queryPrefix,
} from "@silverbulletmd/plugos-silverbullet-syscall";
import {
getCurrentPage,
matchBefore,
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
import type { IndexTreeEvent } from "@silverbulletmd/web/app_event";
import { applyQuery, QueryProviderEvent } from "../query/engine";
import { removeQueries } from "../query/util";
// Key space
// a:pageName:anchorName => pos
export async function indexAnchors({ name: pageName, tree }: IndexTreeEvent) {
removeQueries(tree);
let anchors: { key: string; value: string }[] = [];
collectNodesOfType(tree, "NamedAnchor").forEach((n) => {
let aName = n.children![0].text!;
anchors.push({
key: `a:${pageName}:${aName}`,
value: "" + n.from,
});
});
console.log("Found", anchors.length, "anchors(s)");
await batchSet(pageName, anchors);
}
export async function anchorComplete() {
let prefix = await matchBefore("\\[\\[[^\\]@]*@[\\w\\.\\-\\/]*");
if (!prefix) {
return null;
}
const [pageRefPrefix, anchorRef] = prefix.text.split("@");
let pageRef = pageRefPrefix.substring(2);
if (!pageRef) {
pageRef = await getCurrentPage();
}
let allAnchors = await queryPrefix(`a:${pageRef}:@${anchorRef}`);
return {
from: prefix.from + pageRefPrefix.length + 1,
options: allAnchors.map((a) => ({
label: a.key.split("@")[1],
type: "anchor",
})),
};
}

View File

@ -15,6 +15,11 @@ syntax:
- "{" - "{"
regex: "\\{\\[[^\\]]+\\]\\}" regex: "\\{\\[[^\\]]+\\]\\}"
className: sb-command-link className: sb-command-link
NamedAnchor:
firstCharacters:
- "@"
regex: "@[a-zA-Z\\.\\-\\/]+[\\w\\.\\-\\/]*"
className: sb-named-anchor
functions: functions:
clearPageIndex: clearPageIndex:
path: "./page.ts:clearPageIndex" path: "./page.ts:clearPageIndex"
@ -111,6 +116,16 @@ functions:
events: events:
- query:tag - query:tag
# Anchors
indexAnchors:
path: "./anchor.ts:indexAnchors"
events:
- page:index
anchorComplete:
path: "./anchor.ts:anchorComplete"
events:
- page:complete
# Full text search # Full text search
searchIndex: searchIndex:
path: ./search.ts:index path: ./search.ts:index

View File

@ -1,6 +1,7 @@
import type { ClickEvent } from "@silverbulletmd/web/app_event"; import type { ClickEvent } from "@silverbulletmd/web/app_event";
import { import {
flashNotification, flashNotification,
getCurrentPage,
getCursor, getCursor,
getText, getText,
navigate as navigateTo, navigate as navigateTo,
@ -18,11 +19,17 @@ async function actionClickOrActionEnter(mdTree: ParseTree | null) {
switch (mdTree.type) { switch (mdTree.type) {
case "WikiLinkPage": case "WikiLinkPage":
let pageLink = mdTree.children![0].text!; let pageLink = mdTree.children![0].text!;
let pos = "0"; let pos;
if (pageLink.includes("@")) { if (pageLink.includes("@")) {
[pageLink, pos] = pageLink.split("@"); [pageLink, pos] = pageLink.split("@");
if (pos.match(/^\d+$/)) {
pos = +pos;
} }
await navigateTo(pageLink, +pos); }
if (!pageLink) {
pageLink = await getCurrentPage();
}
await navigateTo(pageLink, pos);
break; break;
case "URL": case "URL":
case "NakedURL": case "NakedURL":

View File

@ -206,7 +206,7 @@ export async function reindexCommand() {
// Completion // Completion
export async function pageComplete() { export async function pageComplete() {
let prefix = await matchBefore("\\[\\[[^\\]]*"); let prefix = await matchBefore("\\[\\[[^\\]@]*");
if (!prefix) { if (!prefix) {
return null; return null;
} }

View File

@ -203,8 +203,8 @@ export class Editor {
async init() { async init() {
this.focus(); this.focus();
this.pageNavigator.subscribe(async (pageName, pos) => { this.pageNavigator.subscribe(async (pageName, pos: number | string) => {
console.log("Now navigating to", pageName); console.log("Now navigating to", pageName, pos);
if (!this.editorView) { if (!this.editorView) {
return; return;
@ -212,8 +212,30 @@ export class Editor {
await this.loadPage(pageName); await this.loadPage(pageName);
if (pos) { if (pos) {
if (typeof pos === "string") {
console.log("Navigating to anchor", pos);
// We're going to look up the anchor through a direct page store query...
// TODO: This is a bit hacky, but it works for now
let posLookup = await this.system.syscallWithContext(
// Mock the "core" plug
{ plug: { name: "core" } as any },
"index.get",
[pageName, `a:${pageName}:@${pos}`]
);
if (!posLookup) {
return this.flashNotification(
`Could not find anchor @${pos}`,
"error"
);
} else {
pos = +posLookup;
}
}
this.editorView.dispatch({ this.editorView.dispatch({
selection: { anchor: pos }, selection: { anchor: pos },
scrollIntoView: true,
}); });
} }
}); });
@ -562,7 +584,7 @@ export class Editor {
this.editorView!.focus(); this.editorView!.focus();
} }
async navigate(name: string, pos?: number, replaceState = false) { async navigate(name: string, pos?: number | string, replaceState = false) {
if (!name) { if (!name) {
name = this.indexPage; name = this.indexPage;
} }

View File

@ -13,7 +13,7 @@ export class PathPageNavigator {
constructor(readonly indexPage: string, readonly root: string = "") {} constructor(readonly indexPage: string, readonly root: string = "") {}
async navigate(page: string, pos?: number, replaceState = false) { async navigate(page: string, pos?: number | string, replaceState = false) {
let encodedPage = encodePageUrl(page); let encodedPage = encodePageUrl(page);
if (page === this.indexPage) { if (page === this.indexPage) {
encodedPage = ""; encodedPage = "";
@ -43,7 +43,7 @@ export class PathPageNavigator {
} }
subscribe( subscribe(
pageLoadCallback: (pageName: string, pos: number) => Promise<void> pageLoadCallback: (pageName: string, pos: number | string) => Promise<void>
): void { ): void {
const cb = (event?: PopStateEvent) => { const cb = (event?: PopStateEvent) => {
const gotoPage = this.getCurrentPage(); const gotoPage = this.getCurrentPage();
@ -53,7 +53,7 @@ export class PathPageNavigator {
safeRun(async () => { safeRun(async () => {
await pageLoadCallback( await pageLoadCallback(
this.getCurrentPage(), this.getCurrentPage(),
event?.state && event.state.pos event?.state?.pos || this.getCurrentPos()
); );
if (this.navigationResolve) { if (this.navigationResolve) {
this.navigationResolve(); this.navigationResolve();
@ -64,17 +64,18 @@ export class PathPageNavigator {
cb(); cb();
} }
decodeURI(): [string, number] { decodeURI(): [string, number | string] {
let parts = decodeURI( let [page, pos] = decodeURI(
location.pathname.substring(this.root.length + 1) location.pathname.substring(this.root.length + 1)
).split("@"); ).split("@");
let page = if (pos) {
parts.length > 1 ? parts.slice(0, parts.length - 1).join("@") : parts[0];
let pos = parts.length > 1 ? parts[parts.length - 1] : "0";
if (pos.match(/^\d+$/)) { if (pos.match(/^\d+$/)) {
return [page, +pos]; return [page, +pos];
} else { } else {
return [`${page}@${pos}`, 0]; return [page, pos];
}
} else {
return [page, 0];
} }
} }
@ -82,7 +83,7 @@ export class PathPageNavigator {
return decodePageUrl(this.decodeURI()[0]) || this.indexPage; return decodePageUrl(this.decodeURI()[0]) || this.indexPage;
} }
getCurrentPos(): number { getCurrentPos(): number | string {
// console.log("Pos", this.decodeURI()[1]); // console.log("Pos", this.decodeURI()[1]);
return this.decodeURI()[1]; return this.decodeURI()[1];
} }

View File

@ -141,10 +141,13 @@
.sb-naked-url { .sb-naked-url {
color: #0330cb; color: #0330cb;
text-decoration: underline;
cursor: pointer; cursor: pointer;
} }
.sb-named-anchor {
color: #959595;
}
.sb-command-link { .sb-command-link {
background-color: #e3dfdf; background-color: #e3dfdf;
cursor: pointer; cursor: pointer;

View File

@ -46,7 +46,7 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
"editor.navigate": async ( "editor.navigate": async (
ctx, ctx,
name: string, name: string,
pos: number, pos: number | string,
replaceState = false replaceState = false
) => { ) => {
await editor.navigate(name, pos, replaceState); await editor.navigate(name, pos, replaceState);

View File

@ -1,10 +1,11 @@
An attempt at documenting of the changes/new features introduced in each (pre) release. An attempt at documenting of the changes/new features introduced in each (pre) release.
## 0.0.32 ## 0.0.32
* Inline image previews are now implemented, use the standard `![alt text](https://url.com/image.jpg)` notation and a preview of the image will appear automatically. Example: * **Inline image previews**: use the standard `![alt text](https://url.com/image.jpg)` notation and a preview of the image will appear automatically. Example:
![Inline image preview](https://user-images.githubusercontent.com/812886/186218876-6d8a4a71-af8b-4e9e-83eb-4ac89607a6b4.png) ![Inline image preview](https://user-images.githubusercontent.com/812886/186218876-6d8a4a71-af8b-4e9e-83eb-4ac89607a6b4.png)
* Dark mode! Toggle between the dark and light mode using a new button, top-right. * **Dark mode**. Toggle between the dark and light mode using a new button, top-right.
![Dark mode screenshot](https://user-images.githubusercontent.com/6335792/187000151-ba06ce55-ad27-494b-bfe9-6b19ef62145b.png) ![Dark mode screenshot](https://user-images.githubusercontent.com/6335792/187000151-ba06ce55-ad27-494b-bfe9-6b19ef62145b.png)
* **Named anchors** and references, create an anchor with the new @anchor notation (anywhere on a page), then reference it locally via [[@anchor]] or cross page via [[CHANGELOG@anchor]].
## 0.0.31 ## 0.0.31
* Update to the query language: the `render` clause now uses page reference syntax `[[page]]`. For example `render [[template/task]]` rather than `render "template/task"`. The old syntax still works, but is deprecated, completion for the old syntax has been removed. * Update to the query language: the `render` clause now uses page reference syntax `[[page]]`. For example `render [[template/task]]` rather than `render "template/task"`. The old syntax still works, but is deprecated, completion for the old syntax has been removed.