parent
e508628826
commit
604bea3ee0
@ -4,7 +4,7 @@ import { System } from "../plugos/system.ts";
|
||||
const indexVersionKey = ["$indexVersion"];
|
||||
|
||||
// Bump this one every time a full reinxex is needed
|
||||
const desiredIndexVersion = 3;
|
||||
const desiredIndexVersion = 4;
|
||||
|
||||
let indexOngoing = false;
|
||||
|
||||
|
@ -7,6 +7,10 @@ Deno.test("Page utility functions", () => {
|
||||
assertEquals(parsePageRef("[[foo]]"), { page: "foo" });
|
||||
assertEquals(parsePageRef("foo@1"), { page: "foo", pos: 1 });
|
||||
assertEquals(parsePageRef("foo$bar"), { page: "foo", anchor: "bar" });
|
||||
assertEquals(parsePageRef("foo#My header"), {
|
||||
page: "foo",
|
||||
header: "My header",
|
||||
});
|
||||
assertEquals(parsePageRef("foo$bar@1"), {
|
||||
page: "foo",
|
||||
anchor: "bar",
|
||||
@ -21,4 +25,5 @@ Deno.test("Page utility functions", () => {
|
||||
assertEquals(encodePageRef({ page: "foo" }), "foo");
|
||||
assertEquals(encodePageRef({ page: "foo", pos: 10 }), "foo@10");
|
||||
assertEquals(encodePageRef({ page: "foo", anchor: "bar" }), "foo$bar");
|
||||
assertEquals(encodePageRef({ page: "foo", header: "bar" }), "foo#bar");
|
||||
});
|
||||
|
@ -15,11 +15,13 @@ export type PageRef = {
|
||||
page: string;
|
||||
pos?: number;
|
||||
anchor?: string;
|
||||
header?: string;
|
||||
};
|
||||
|
||||
const posRegex = /@(\d+)$/;
|
||||
// Should be kept in sync with the regex in index.plug.yaml
|
||||
const anchorRegex = /\$([a-zA-Z\.\-\/]+[\w\.\-\/]*)$/;
|
||||
const headerRegex = /#([^#]*)$/;
|
||||
|
||||
export function parsePageRef(name: string): PageRef {
|
||||
// Normalize the page name
|
||||
@ -37,6 +39,11 @@ export function parsePageRef(name: string): PageRef {
|
||||
pageRef.anchor = anchorMatch[1];
|
||||
pageRef.page = pageRef.page.replace(anchorRegex, "");
|
||||
}
|
||||
const headerMatch = pageRef.page.match(headerRegex);
|
||||
if (headerMatch) {
|
||||
pageRef.header = headerMatch[1];
|
||||
pageRef.page = pageRef.page.replace(headerRegex, "");
|
||||
}
|
||||
return pageRef;
|
||||
}
|
||||
|
||||
@ -48,5 +55,8 @@ export function encodePageRef(pageRef: PageRef): string {
|
||||
if (pageRef.anchor) {
|
||||
name += `$${pageRef.anchor}`;
|
||||
}
|
||||
if (pageRef.header) {
|
||||
name += `#${pageRef.header}`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
@ -5,6 +5,6 @@ export { expandGlobSync } from "https://deno.land/std@0.165.0/fs/mod.ts";
|
||||
export { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts";
|
||||
export { default as cacheDir } from "https://deno.land/x/cache_dir@0.2.0/mod.ts";
|
||||
export * as flags from "https://deno.land/std@0.165.0/flags/mod.ts";
|
||||
export * as esbuild from "https://deno.land/x/esbuild@v0.19.2/mod.js";
|
||||
export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.2/mod.ts";
|
||||
export * as esbuild from "https://deno.land/x/esbuild@v0.19.12/mod.js";
|
||||
export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.5/mod.ts";
|
||||
export * as YAML from "https://deno.land/std@0.184.0/yaml/mod.ts";
|
||||
|
@ -5,7 +5,7 @@ import { queryObjects } from "../index/plug_api.ts";
|
||||
|
||||
// Completion
|
||||
export async function pageComplete(completeEvent: CompleteEvent) {
|
||||
const match = /\[\[([^\]@$:\{}]*)$/.exec(completeEvent.linePrefix);
|
||||
const match = /\[\[([^\]@$#:\{}]*)$/.exec(completeEvent.linePrefix);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ export async function objectAttributeCompleter(
|
||||
distinct: true,
|
||||
select: [{ name: "name" }, { name: "attributeType" }, { name: "tag" }, {
|
||||
name: "readOnly",
|
||||
}],
|
||||
}, { name: "tagName" }],
|
||||
}, 5);
|
||||
return allAttributes.map((value) => {
|
||||
return {
|
||||
|
@ -70,6 +70,13 @@ export const builtins: Record<string, Record<string, string>> = {
|
||||
alias: "!string",
|
||||
asTemplate: "!boolean",
|
||||
},
|
||||
header: {
|
||||
ref: "!string",
|
||||
name: "!string",
|
||||
page: "!string",
|
||||
level: "!number",
|
||||
pos: "!number",
|
||||
},
|
||||
paragraph: {
|
||||
text: "!string",
|
||||
page: "!string",
|
||||
|
59
plugs/index/header.ts
Normal file
59
plugs/index/header.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { collectNodesMatching } from "$sb/lib/tree.ts";
|
||||
import type { CompleteEvent, IndexTreeEvent } from "$sb/app_event.ts";
|
||||
import { ObjectValue } from "$sb/types.ts";
|
||||
import { indexObjects, queryObjects } from "./api.ts";
|
||||
import { parsePageRef } from "$sb/lib/page.ts";
|
||||
|
||||
type HeaderObject = ObjectValue<{
|
||||
name: string;
|
||||
page: string;
|
||||
level: number;
|
||||
pos: number;
|
||||
}>;
|
||||
|
||||
export async function indexHeaders({ name: pageName, tree }: IndexTreeEvent) {
|
||||
const headers: ObjectValue<HeaderObject>[] = [];
|
||||
|
||||
collectNodesMatching(tree, (t) => !!t.type?.startsWith("ATXHeading")).forEach(
|
||||
(n) => {
|
||||
const level = +n.type!.substring("ATXHeading".length);
|
||||
const name = n.children![1].text!.trim();
|
||||
headers.push({
|
||||
ref: `${pageName}#${name}@${n.from}`,
|
||||
tag: "header",
|
||||
level,
|
||||
name,
|
||||
page: pageName,
|
||||
pos: n.from!,
|
||||
});
|
||||
},
|
||||
);
|
||||
console.log("Found", headers.length, "headers(s)");
|
||||
await indexObjects(pageName, headers);
|
||||
}
|
||||
|
||||
export async function headerComplete(completeEvent: CompleteEvent) {
|
||||
const match = /\[\[([^\]$:#]*#.*)$/.exec(
|
||||
completeEvent.linePrefix,
|
||||
);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pageRef = parsePageRef(match[1]).page;
|
||||
const allHeaders = await queryObjects<HeaderObject>("header", {
|
||||
filter: ["=", ["attr", "page"], [
|
||||
"string",
|
||||
pageRef || completeEvent.pageName,
|
||||
]],
|
||||
}, 5);
|
||||
return {
|
||||
from: completeEvent.pos - match[1].length,
|
||||
options: allHeaders.map((a) => ({
|
||||
label: a.page === completeEvent.pageName
|
||||
? `#${a.name}`
|
||||
: a.ref.split("@")[0],
|
||||
type: "header",
|
||||
})),
|
||||
};
|
||||
}
|
@ -109,6 +109,16 @@ functions:
|
||||
events:
|
||||
- editor:complete
|
||||
|
||||
# Headers
|
||||
indexHeaders:
|
||||
path: header.ts:indexHeaders
|
||||
events:
|
||||
- page:index
|
||||
headerComplete:
|
||||
path: header.ts:headerComplete
|
||||
events:
|
||||
- editor:complete
|
||||
|
||||
# Data
|
||||
indexData:
|
||||
path: data.ts:indexData
|
||||
|
@ -54,6 +54,11 @@ const taskPrefixRegex = /^\s*[\-\*]\s+\[([^\]]+)\]/;
|
||||
const itemPrefixRegex = /^\s*[\-\*]\s+/;
|
||||
|
||||
export async function tagComplete(completeEvent: CompleteEvent) {
|
||||
const inLinkMatch = /\[\[([^\]]*)$/.exec(completeEvent.linePrefix);
|
||||
if (inLinkMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = /#[^#\s]+$/.exec(completeEvent.linePrefix);
|
||||
if (!match) {
|
||||
return null;
|
||||
|
@ -320,7 +320,8 @@ export class Client {
|
||||
if (
|
||||
pageState.scrollTop !== undefined &&
|
||||
!(pageState.scrollTop === 0 &&
|
||||
(pageState.pos !== undefined || pageState.anchor !== undefined))
|
||||
(pageState.pos !== undefined || pageState.anchor !== undefined ||
|
||||
pageState.header !== undefined))
|
||||
) {
|
||||
setTimeout(() => {
|
||||
console.log("Kicking off scroll to", pageState.scrollTop);
|
||||
@ -330,7 +331,10 @@ export class Client {
|
||||
}
|
||||
|
||||
// Was a particular cursor/selection set?
|
||||
if (pageState.selection?.anchor && !pageState.pos && !pageState.anchor) { // Only do this if we got a specific cursor position
|
||||
if (
|
||||
pageState.selection?.anchor && !pageState.pos && !pageState.anchor &&
|
||||
!pageState.header
|
||||
) { // Only do this if we got a specific cursor position
|
||||
console.log("Changing cursor position to", pageState.selection);
|
||||
this.editorView.dispatch({
|
||||
selection: pageState.selection,
|
||||
@ -344,6 +348,7 @@ export class Client {
|
||||
console.log("Navigating to anchor", pageState.anchor);
|
||||
const pageText = this.editorView.state.sliceDoc();
|
||||
|
||||
// This is somewhat of a simplistic way to find the anchor, but it works for now
|
||||
pos = pageText.indexOf(`$${pageState.anchor}`);
|
||||
|
||||
if (pos === -1) {
|
||||
@ -355,6 +360,22 @@ export class Client {
|
||||
|
||||
adjustedPosition = true;
|
||||
}
|
||||
if (pageState.header) {
|
||||
console.log("Navigating to header", pageState.header);
|
||||
const pageText = this.editorView.state.sliceDoc();
|
||||
|
||||
// This is somewhat of a simplistic way to find the header, but it works for now
|
||||
pos = pageText.indexOf(`# ${pageState.header}\n`) + 2;
|
||||
|
||||
if (pos === -1) {
|
||||
return this.flashNotification(
|
||||
`Could not find header "${pageState.header}"`,
|
||||
"error",
|
||||
);
|
||||
}
|
||||
|
||||
adjustedPosition = true;
|
||||
}
|
||||
if (pos !== undefined) {
|
||||
// setTimeout(() => {
|
||||
console.log("Doing this pos set to", pos);
|
||||
|
@ -162,7 +162,6 @@ export class MarkdownWidget extends WidgetType {
|
||||
div.querySelectorAll("span.hashtag").forEach((el_) => {
|
||||
const el = el_ as HTMLElement;
|
||||
// Override default click behavior with a local navigate (faster)
|
||||
console.log("Found hashtag", el.innerText);
|
||||
el.addEventListener("click", (e) => {
|
||||
console.log("Hashtag clicked", el.innerText);
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
|
@ -146,5 +146,9 @@ export function parsePageRefFromURI(): PageRef {
|
||||
location.pathname.substring(1),
|
||||
));
|
||||
|
||||
if (location.hash) {
|
||||
pageRef.header = decodeURI(location.hash.substring(1));
|
||||
}
|
||||
|
||||
return pageRef;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user