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(
name: string,
pos?: number,
pos?: string | number,
replaceState = false
): Promise<void> {
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: "\\{\\[[^\\]]+\\]\\}"
className: sb-command-link
NamedAnchor:
firstCharacters:
- "@"
regex: "@[a-zA-Z\\.\\-\\/]+[\\w\\.\\-\\/]*"
className: sb-named-anchor
functions:
clearPageIndex:
path: "./page.ts:clearPageIndex"
@ -111,6 +116,16 @@ functions:
events:
- query:tag
# Anchors
indexAnchors:
path: "./anchor.ts:indexAnchors"
events:
- page:index
anchorComplete:
path: "./anchor.ts:anchorComplete"
events:
- page:complete
# Full text search
searchIndex:
path: ./search.ts:index

View File

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

View File

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

View File

@ -203,8 +203,8 @@ export class Editor {
async init() {
this.focus();
this.pageNavigator.subscribe(async (pageName, pos) => {
console.log("Now navigating to", pageName);
this.pageNavigator.subscribe(async (pageName, pos: number | string) => {
console.log("Now navigating to", pageName, pos);
if (!this.editorView) {
return;
@ -212,8 +212,30 @@ export class Editor {
await this.loadPage(pageName);
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({
selection: { anchor: pos },
scrollIntoView: true,
});
}
});
@ -562,7 +584,7 @@ export class Editor {
this.editorView!.focus();
}
async navigate(name: string, pos?: number, replaceState = false) {
async navigate(name: string, pos?: number | string, replaceState = false) {
if (!name) {
name = this.indexPage;
}

View File

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

View File

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

View File

@ -46,7 +46,7 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
"editor.navigate": async (
ctx,
name: string,
pos: number,
pos: number | string,
replaceState = false
) => {
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.
## 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)
* 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)
* **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
* 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.