1
0
This commit is contained in:
Zef Hemel 2024-01-25 14:51:40 +01:00
parent e508628826
commit 604bea3ee0
13 changed files with 129 additions and 9 deletions

View File

@ -4,7 +4,7 @@ import { System } from "../plugos/system.ts";
const indexVersionKey = ["$indexVersion"]; const indexVersionKey = ["$indexVersion"];
// Bump this one every time a full reinxex is needed // Bump this one every time a full reinxex is needed
const desiredIndexVersion = 3; const desiredIndexVersion = 4;
let indexOngoing = false; let indexOngoing = false;

View File

@ -7,6 +7,10 @@ Deno.test("Page utility functions", () => {
assertEquals(parsePageRef("[[foo]]"), { page: "foo" }); assertEquals(parsePageRef("[[foo]]"), { page: "foo" });
assertEquals(parsePageRef("foo@1"), { page: "foo", pos: 1 }); assertEquals(parsePageRef("foo@1"), { page: "foo", pos: 1 });
assertEquals(parsePageRef("foo$bar"), { page: "foo", anchor: "bar" }); assertEquals(parsePageRef("foo$bar"), { page: "foo", anchor: "bar" });
assertEquals(parsePageRef("foo#My header"), {
page: "foo",
header: "My header",
});
assertEquals(parsePageRef("foo$bar@1"), { assertEquals(parsePageRef("foo$bar@1"), {
page: "foo", page: "foo",
anchor: "bar", anchor: "bar",
@ -21,4 +25,5 @@ Deno.test("Page utility functions", () => {
assertEquals(encodePageRef({ page: "foo" }), "foo"); assertEquals(encodePageRef({ page: "foo" }), "foo");
assertEquals(encodePageRef({ page: "foo", pos: 10 }), "foo@10"); assertEquals(encodePageRef({ page: "foo", pos: 10 }), "foo@10");
assertEquals(encodePageRef({ page: "foo", anchor: "bar" }), "foo$bar"); assertEquals(encodePageRef({ page: "foo", anchor: "bar" }), "foo$bar");
assertEquals(encodePageRef({ page: "foo", header: "bar" }), "foo#bar");
}); });

View File

@ -15,11 +15,13 @@ export type PageRef = {
page: string; page: string;
pos?: number; pos?: number;
anchor?: string; anchor?: string;
header?: string;
}; };
const posRegex = /@(\d+)$/; const posRegex = /@(\d+)$/;
// Should be kept in sync with the regex in index.plug.yaml // Should be kept in sync with the regex in index.plug.yaml
const anchorRegex = /\$([a-zA-Z\.\-\/]+[\w\.\-\/]*)$/; const anchorRegex = /\$([a-zA-Z\.\-\/]+[\w\.\-\/]*)$/;
const headerRegex = /#([^#]*)$/;
export function parsePageRef(name: string): PageRef { export function parsePageRef(name: string): PageRef {
// Normalize the page name // Normalize the page name
@ -37,6 +39,11 @@ export function parsePageRef(name: string): PageRef {
pageRef.anchor = anchorMatch[1]; pageRef.anchor = anchorMatch[1];
pageRef.page = pageRef.page.replace(anchorRegex, ""); 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; return pageRef;
} }
@ -48,5 +55,8 @@ export function encodePageRef(pageRef: PageRef): string {
if (pageRef.anchor) { if (pageRef.anchor) {
name += `$${pageRef.anchor}`; name += `$${pageRef.anchor}`;
} }
if (pageRef.header) {
name += `#${pageRef.header}`;
}
return name; return name;
} }

View File

@ -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 { 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 { 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 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 * 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.2/mod.ts"; 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"; export * as YAML from "https://deno.land/std@0.184.0/yaml/mod.ts";

View File

@ -5,7 +5,7 @@ import { queryObjects } from "../index/plug_api.ts";
// Completion // Completion
export async function pageComplete(completeEvent: CompleteEvent) { export async function pageComplete(completeEvent: CompleteEvent) {
const match = /\[\[([^\]@$:\{}]*)$/.exec(completeEvent.linePrefix); const match = /\[\[([^\]@$#:\{}]*)$/.exec(completeEvent.linePrefix);
if (!match) { if (!match) {
return null; return null;
} }

View File

@ -51,7 +51,7 @@ export async function objectAttributeCompleter(
distinct: true, distinct: true,
select: [{ name: "name" }, { name: "attributeType" }, { name: "tag" }, { select: [{ name: "name" }, { name: "attributeType" }, { name: "tag" }, {
name: "readOnly", name: "readOnly",
}], }, { name: "tagName" }],
}, 5); }, 5);
return allAttributes.map((value) => { return allAttributes.map((value) => {
return { return {

View File

@ -70,6 +70,13 @@ export const builtins: Record<string, Record<string, string>> = {
alias: "!string", alias: "!string",
asTemplate: "!boolean", asTemplate: "!boolean",
}, },
header: {
ref: "!string",
name: "!string",
page: "!string",
level: "!number",
pos: "!number",
},
paragraph: { paragraph: {
text: "!string", text: "!string",
page: "!string", page: "!string",

59
plugs/index/header.ts Normal file
View 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",
})),
};
}

View File

@ -109,6 +109,16 @@ functions:
events: events:
- editor:complete - editor:complete
# Headers
indexHeaders:
path: header.ts:indexHeaders
events:
- page:index
headerComplete:
path: header.ts:headerComplete
events:
- editor:complete
# Data # Data
indexData: indexData:
path: data.ts:indexData path: data.ts:indexData

View File

@ -54,6 +54,11 @@ const taskPrefixRegex = /^\s*[\-\*]\s+\[([^\]]+)\]/;
const itemPrefixRegex = /^\s*[\-\*]\s+/; const itemPrefixRegex = /^\s*[\-\*]\s+/;
export async function tagComplete(completeEvent: CompleteEvent) { export async function tagComplete(completeEvent: CompleteEvent) {
const inLinkMatch = /\[\[([^\]]*)$/.exec(completeEvent.linePrefix);
if (inLinkMatch) {
return null;
}
const match = /#[^#\s]+$/.exec(completeEvent.linePrefix); const match = /#[^#\s]+$/.exec(completeEvent.linePrefix);
if (!match) { if (!match) {
return null; return null;

View File

@ -320,7 +320,8 @@ export class Client {
if ( if (
pageState.scrollTop !== undefined && pageState.scrollTop !== undefined &&
!(pageState.scrollTop === 0 && !(pageState.scrollTop === 0 &&
(pageState.pos !== undefined || pageState.anchor !== undefined)) (pageState.pos !== undefined || pageState.anchor !== undefined ||
pageState.header !== undefined))
) { ) {
setTimeout(() => { setTimeout(() => {
console.log("Kicking off scroll to", pageState.scrollTop); console.log("Kicking off scroll to", pageState.scrollTop);
@ -330,7 +331,10 @@ export class Client {
} }
// Was a particular cursor/selection set? // 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); console.log("Changing cursor position to", pageState.selection);
this.editorView.dispatch({ this.editorView.dispatch({
selection: pageState.selection, selection: pageState.selection,
@ -344,6 +348,7 @@ export class Client {
console.log("Navigating to anchor", pageState.anchor); console.log("Navigating to anchor", pageState.anchor);
const pageText = this.editorView.state.sliceDoc(); 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}`); pos = pageText.indexOf(`$${pageState.anchor}`);
if (pos === -1) { if (pos === -1) {
@ -355,6 +360,22 @@ export class Client {
adjustedPosition = true; 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) { if (pos !== undefined) {
// setTimeout(() => { // setTimeout(() => {
console.log("Doing this pos set to", pos); console.log("Doing this pos set to", pos);

View File

@ -162,7 +162,6 @@ export class MarkdownWidget extends WidgetType {
div.querySelectorAll("span.hashtag").forEach((el_) => { div.querySelectorAll("span.hashtag").forEach((el_) => {
const el = el_ as HTMLElement; const el = el_ as HTMLElement;
// Override default click behavior with a local navigate (faster) // Override default click behavior with a local navigate (faster)
console.log("Found hashtag", el.innerText);
el.addEventListener("click", (e) => { el.addEventListener("click", (e) => {
console.log("Hashtag clicked", el.innerText); console.log("Hashtag clicked", el.innerText);
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {

View File

@ -146,5 +146,9 @@ export function parsePageRefFromURI(): PageRef {
location.pathname.substring(1), location.pathname.substring(1),
)); ));
if (location.hash) {
pageRef.header = decodeURI(location.hash.substring(1));
}
return pageRef; return pageRef;
} }