parent
e508628826
commit
604bea3ee0
@ -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;
|
||||||
|
|
||||||
|
@ -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");
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
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:
|
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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user