1
0
silverbullet/plugs/directive/command.ts

204 lines
6.1 KiB
TypeScript
Raw Normal View History

2023-07-30 06:56:44 +00:00
import { editor, markdown, space, sync } from "$sb/silverbullet-syscall/mod.ts";
import {
2023-08-04 16:56:55 +00:00
ParseTree,
removeParentPointers,
renderToText,
traverseTree,
} from "$sb/lib/tree.ts";
import { renderDirectives } from "./directives.ts";
2022-11-24 11:04:00 +00:00
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { PageMeta } from "../../web/types.ts";
2023-07-30 06:56:44 +00:00
import { isFederationPath } from "$sb/lib/resolve.ts";
import { mq } from "$sb/plugos-syscall/mod.ts";
2023-08-11 18:37:13 +00:00
import { Message } from "$sb/types.ts";
import { sleep } from "../../common/async_util.ts";
const directiveUpdateQueueName = "directiveUpdateQueue";
2023-07-14 11:44:30 +00:00
export async function updateDirectivesOnPageCommand() {
2022-11-24 15:08:51 +00:00
// If `arg` is a string, it's triggered automatically via an event, not explicitly via a command
2023-07-30 06:56:44 +00:00
const currentPage = await editor.getCurrentPage();
const pageMeta = await space.getPageMeta(currentPage);
2022-11-19 14:22:43 +00:00
const text = await editor.getText();
const tree = await markdown.parseMarkdown(text);
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
2023-07-30 06:56:44 +00:00
if (isFederationPath(currentPage)) {
console.info("Current page is a federation page, not updating directives.");
2023-07-30 09:30:01 +00:00
return;
2023-07-30 06:56:44 +00:00
}
2022-11-19 14:22:43 +00:00
if (metaData.$disableDirectives) {
2023-07-30 06:56:44 +00:00
console.info("Directives disabled in page meta, not updating them.");
return;
}
if (!(await sync.hasInitialSyncCompleted())) {
console.info(
"Initial sync hasn't completed yet, not updating directives.",
);
2022-11-19 14:22:43 +00:00
return;
}
2022-11-19 14:22:43 +00:00
2023-07-28 11:54:31 +00:00
await editor.save();
2023-08-04 16:56:55 +00:00
const replacements = await findReplacements(tree, text, pageMeta);
2022-11-19 14:22:43 +00:00
// Iterate again and replace the bodies. Iterating again (not using previous positions)
// because text may have changed in the mean time (directive processing may take some time)
// Hypothetically in the mean time directives in text may have been changed/swapped, in which
// case this will break. This would be a rare edge case, however.
2022-11-23 09:05:01 +00:00
for (const replacement of replacements) {
// Fetch the text every time, because dispatch() will have been made changes
const text = await editor.getText();
// Determine the current position
const index = text.indexOf(replacement.fullMatch);
// This may happen if the query itself, or the user is editing inside the directive block (WHY!?)
if (index === -1) {
console.warn(
"Text I got",
text,
);
2022-11-23 09:05:01 +00:00
console.warn(
"Could not find directive in text, skipping",
replacement.fullMatch,
);
continue;
}
2022-11-24 15:08:51 +00:00
const from = index, to = index + replacement.fullMatch.length;
const newText = await replacement.textPromise;
if (text.substring(from, to) === newText) {
2022-11-24 15:08:51 +00:00
// No change, skip
continue;
}
2022-11-23 09:05:01 +00:00
await editor.dispatch({
changes: {
2022-11-24 15:08:51 +00:00
from,
to,
insert: newText,
2022-11-23 09:05:01 +00:00
},
});
}
}
2023-08-04 19:25:14 +00:00
export async function updateDirectivesInSpaceCommand() {
await editor.flashNotification(
"Updating directives in entire space, this can take a while...",
);
2023-08-11 18:37:13 +00:00
await updateDirectivesInSpace();
2023-08-11 18:37:13 +00:00
// And notify the user
await editor.flashNotification("Updating of all directives completed!");
}
export async function processUpdateQueue(messages: Message[]) {
for (const message of messages) {
const pageName: string = message.body;
console.log("Updating directives in page", pageName);
await updateDirectivesForPage(pageName);
2023-08-11 18:37:13 +00:00
await mq.ack(directiveUpdateQueueName, message.id);
}
2023-08-04 19:25:14 +00:00
}
2023-08-04 16:56:55 +00:00
async function findReplacements(
tree: ParseTree,
text: string,
2023-08-04 16:56:55 +00:00
pageMeta: PageMeta,
) {
// Collect all directives and their body replacements
const replacements: { fullMatch: string; textPromise: Promise<string> }[] =
[];
2023-08-04 16:56:55 +00:00
// Convenience array to wait for all promises to resolve
const allPromises: Promise<string>[] = [];
2023-08-04 16:56:55 +00:00
removeParentPointers(tree);
traverseTree(tree, (tree) => {
if (tree.type !== "Directive") {
return false;
}
const fullMatch = text.substring(tree.from!, tree.to!);
try {
2023-08-04 16:56:55 +00:00
const promise = renderDirectives(pageMeta, tree);
replacements.push({
textPromise: promise,
fullMatch,
});
allPromises.push(promise);
} catch (e: any) {
replacements.push({
fullMatch,
textPromise: Promise.resolve(
`${renderToText(tree.children![0])}\n**ERROR:** ${e.message}\n${
renderToText(tree.children![tree.children!.length - 1])
}`,
),
});
}
return true;
});
// Wait for all to have processed
await Promise.all(allPromises);
2023-08-04 16:56:55 +00:00
return replacements;
}
export async function updateDirectivesInSpace() {
2023-08-11 18:37:13 +00:00
const pages = await space.listPages();
await mq.batchSend(directiveUpdateQueueName, pages.map((page) => page.name));
// Now let's wait for the processing to finish
let queueStats = await mq.getQueueStats(directiveUpdateQueueName);
while (queueStats.queued > 0 || queueStats.processing > 0) {
sleep(1000);
queueStats = await mq.getQueueStats(directiveUpdateQueueName);
2023-08-04 16:56:55 +00:00
}
2023-08-11 18:37:13 +00:00
console.log("Done updating directives in space!");
2023-08-04 16:56:55 +00:00
}
async function updateDirectivesForPage(
pageName: string,
) {
const pageMeta = await space.getPageMeta(pageName);
const currentText = await space.readPage(pageName);
const tree = await markdown.parseMarkdown(currentText);
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
if (isFederationPath(pageName)) {
console.info("Current page is a federation page, not updating directives.");
return;
}
if (metaData.$disableDirectives) {
console.info("Directives disabled in page meta, not updating them.");
return;
}
const newText = await updateDirectives(pageMeta, tree, currentText);
2023-08-04 16:56:55 +00:00
if (newText !== currentText) {
2023-08-11 18:37:13 +00:00
console.info("Content of page changed, saving", pageName);
2023-08-04 16:56:55 +00:00
await space.writePage(pageName, newText);
}
}
export async function updateDirectives(
pageMeta: PageMeta,
tree: ParseTree,
2023-08-04 16:56:55 +00:00
text: string,
) {
const replacements = await findReplacements(tree, text, pageMeta);
// Iterate again and replace the bodies.
for (const replacement of replacements) {
2023-08-04 16:56:55 +00:00
text = text.replace(
replacement.fullMatch,
await replacement.textPromise,
);
}
return text;
}