Anchors
parent
0763342f5d
commit
799144a74e
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
})),
|
||||
};
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue