Anchors
parent
0763342f5d
commit
799144a74e
|
@ -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);
|
||||||
|
|
|
@ -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: "\\{\\[[^\\]]+\\]\\}"
|
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
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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];
|
if (pos.match(/^\d+$/)) {
|
||||||
let pos = parts.length > 1 ? parts[parts.length - 1] : "0";
|
return [page, +pos];
|
||||||
if (pos.match(/^\d+$/)) {
|
} else {
|
||||||
return [page, +pos];
|
return [page, pos];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return [`${page}@${pos}`, 0];
|
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];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue