silverbullet/plugs/index/header.ts

91 lines
2.3 KiB
TypeScript
Raw Permalink Normal View History

2024-03-02 20:54:31 +08:00
import {
collectNodesMatching,
collectNodesOfType,
renderToText,
} from "@silverbulletmd/silverbullet/lib/tree";
import type {
CompleteEvent,
IndexTreeEvent,
ObjectValue,
} from "../../plug-api/types.ts";
2024-01-25 21:51:40 +08:00
import { indexObjects, queryObjects } from "./api.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import { extractAttributes } from "@silverbulletmd/silverbullet/lib/attribute";
import { extractHashtag } from "../../plug-api/lib/tags.ts";
2024-01-25 21:51:40 +08:00
2024-03-02 20:54:31 +08:00
type HeaderObject = ObjectValue<
{
name: string;
page: string;
level: number;
pos: number;
} & Record<string, any>
>;
2024-01-25 21:51:40 +08:00
export async function indexHeaders({ name: pageName, tree }: IndexTreeEvent) {
const headers: ObjectValue<HeaderObject>[] = [];
2024-03-02 20:54:31 +08:00
for (
const n of collectNodesMatching(
tree,
(t) => !!t.type?.startsWith("ATXHeading"),
)
) {
const level = +n.type!.substring("ATXHeading".length);
const tags = new Set<string>();
collectNodesOfType(n, "Hashtag").forEach((h) => {
// Push tag to the list, removing the initial #
tags.add(extractHashtag(h.children![0].text!));
2024-03-02 20:54:31 +08:00
h.children = [];
});
// Extract attributes and remove from tree
const extractedAttributes = await extractAttributes(
["header", ...tags],
n,
);
const name = n.children!.slice(1).map(renderToText).join("").trim();
headers.push({
ref: `${pageName}#${name}@${n.from}`,
tag: "header",
tags: [...tags],
level,
name,
page: pageName,
pos: n.from!,
...extractedAttributes,
});
}
// console.log("Found", headers, "headers(s)");
2024-01-25 21:51:40 +08:00
await indexObjects(pageName, headers);
}
export async function headerComplete(completeEvent: CompleteEvent) {
2024-05-28 02:33:41 +08:00
const match = /(?:\[\[|\[.*?\]\()([^\]$:#]*#[^\]\)]*)$/.exec(
2024-01-25 21:51:40 +08:00
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",
})),
};
}