Big refactors and fixes
* Query regen * Fix anchor completion * Dependency fixes * Changelog update
This commit is contained in:
@@ -8,4 +8,5 @@ export const builtinPlugNames = [
|
||||
"share",
|
||||
"tasks",
|
||||
"search",
|
||||
"federation",
|
||||
];
|
||||
|
||||
@@ -14,7 +14,6 @@ import { collab, editor, markdown } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
import { nanoid } from "https://esm.sh/nanoid@4.0.0";
|
||||
import { FileMeta } from "../../common/types.ts";
|
||||
import { base64EncodedDataUrl } from "../../plugos/asset_bundle/base64.ts";
|
||||
|
||||
const defaultServer = "wss://collab.silverbullet.md";
|
||||
|
||||
@@ -118,7 +117,7 @@ export function shareNoop() {
|
||||
|
||||
export function readFileCollab(
|
||||
name: string,
|
||||
): { data: string; meta: FileMeta } {
|
||||
): { data: Uint8Array; meta: FileMeta } {
|
||||
if (!name.endsWith(".md")) {
|
||||
throw new Error("Not found");
|
||||
}
|
||||
@@ -127,10 +126,7 @@ export function readFileCollab(
|
||||
|
||||
return {
|
||||
// encoding === "arraybuffer" is not an option, so either it's "utf8" or "dataurl"
|
||||
data: base64EncodedDataUrl(
|
||||
"text/markdown",
|
||||
new TextEncoder().encode(text),
|
||||
),
|
||||
data: new TextEncoder().encode(text),
|
||||
meta: {
|
||||
name,
|
||||
contentType: "text/markdown",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { collectNodesOfType } from "$sb/lib/tree.ts";
|
||||
import { editor, index } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { index } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import type { CompleteEvent, IndexTreeEvent } from "$sb/app_event.ts";
|
||||
import { removeQueries } from "$sb/lib/query.ts";
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function anchorComplete(completeEvent: CompleteEvent) {
|
||||
|
||||
let [pageRef, anchorRef] = match[1].split("@");
|
||||
if (!pageRef) {
|
||||
pageRef = await editor.getCurrentPage();
|
||||
pageRef = completeEvent.pageName;
|
||||
}
|
||||
const allAnchors = await index.queryPrefix(
|
||||
`a:${pageRef}:${anchorRef}`,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { traverseTree } from "../../plug-api/lib/tree.ts";
|
||||
import {
|
||||
editor,
|
||||
markdown,
|
||||
space,
|
||||
} from "../../plug-api/silverbullet-syscall/mod.ts";
|
||||
|
||||
export async function brokenLinksCommand() {
|
||||
const pageName = "BROKEN LINKS";
|
||||
await editor.flashNotification("Scanning your space...");
|
||||
const allPages = await space.listPages();
|
||||
const allPagesMap = new Map(allPages.map((p) => [p.name, true]));
|
||||
const brokenLinks: { page: string; link: string; pos: number }[] = [];
|
||||
for (const pageMeta of allPages) {
|
||||
const text = await space.readPage(pageMeta.name);
|
||||
const tree = await markdown.parseMarkdown(text);
|
||||
traverseTree(tree, (tree) => {
|
||||
if (tree.type === "WikiLinkPage") {
|
||||
// Add the prefix in the link text
|
||||
const [pageName] = tree.children![0].text!.split("@");
|
||||
if (pageName.startsWith("💭 ")) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
pageName && !pageName.startsWith("{{")
|
||||
) {
|
||||
if (!allPagesMap.has(pageName)) {
|
||||
brokenLinks.push({
|
||||
page: pageMeta.name,
|
||||
link: pageName,
|
||||
pos: tree.from!,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tree.type === "PageRef") {
|
||||
const pageName = tree.children![0].text!.slice(2, -2);
|
||||
if (pageName.startsWith("💭 ")) {
|
||||
return true;
|
||||
}
|
||||
if (!allPagesMap.has(pageName)) {
|
||||
brokenLinks.push({
|
||||
page: pageMeta.name,
|
||||
link: pageName,
|
||||
pos: tree.from!,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (tree.type === "DirectiveBody") {
|
||||
// Don't look inside directive bodies
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
const lines: string[] = [];
|
||||
for (const brokenLink of brokenLinks) {
|
||||
lines.push(
|
||||
`* [[${brokenLink.page}@${brokenLink.pos}]]: ${brokenLink.link}`,
|
||||
);
|
||||
}
|
||||
await space.writePage(pageName, lines.join("\n"));
|
||||
await editor.navigate(pageName);
|
||||
}
|
||||
+2
-6
@@ -1,13 +1,12 @@
|
||||
import { renderToText, replaceNodesMatching } from "$sb/lib/tree.ts";
|
||||
import type { FileMeta } from "../../common/types.ts";
|
||||
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
|
||||
import { base64EncodedDataUrl } from "../../plugos/asset_bundle/base64.ts";
|
||||
|
||||
export const cloudPrefix = "💭 ";
|
||||
|
||||
export async function readFileCloud(
|
||||
name: string,
|
||||
): Promise<{ data: string; meta: FileMeta } | undefined> {
|
||||
): Promise<{ data: Uint8Array; meta: FileMeta } | undefined> {
|
||||
const originalUrl = name.substring(
|
||||
cloudPrefix.length,
|
||||
name.length - ".md".length,
|
||||
@@ -38,10 +37,7 @@ export async function readFileCloud(
|
||||
`${cloudPrefix}${originalUrl.split("/")[0]}/`,
|
||||
);
|
||||
return {
|
||||
data: base64EncodedDataUrl(
|
||||
"text/markdown",
|
||||
new TextEncoder().encode(text),
|
||||
),
|
||||
data: new TextEncoder().encode(text),
|
||||
meta: {
|
||||
name,
|
||||
contentType: "text/markdown",
|
||||
|
||||
@@ -52,6 +52,13 @@ functions:
|
||||
command:
|
||||
name: "Page: Copy"
|
||||
|
||||
syncSpaceCommand:
|
||||
path: "./sync.ts:syncSpaceCommand"
|
||||
command:
|
||||
name: "Sync: Now"
|
||||
key: "Alt-Shift-s"
|
||||
mac: "Cmd-Shift-s"
|
||||
|
||||
# Attachments
|
||||
attachmentQueryProvider:
|
||||
path: ./attachment.ts:attachmentQueryProvider
|
||||
@@ -180,6 +187,15 @@ functions:
|
||||
description: Turn line into h4 header
|
||||
match: "^#*\\s*"
|
||||
replace: "#### "
|
||||
insertCodeBlock:
|
||||
redirect: insertTemplateText
|
||||
slashCommand:
|
||||
name: code
|
||||
description: Insert code block
|
||||
value: |
|
||||
```
|
||||
|^|
|
||||
```
|
||||
|
||||
newPage:
|
||||
path: ./page.ts:newPageCommand
|
||||
@@ -433,3 +449,8 @@ functions:
|
||||
name: "Editor: Vim: Load VIMRC"
|
||||
events:
|
||||
- editor:modeswitch
|
||||
|
||||
brokenLinksCommand:
|
||||
path: ./broken_links.ts:brokenLinksCommand
|
||||
command:
|
||||
name: "Broken Links: Show"
|
||||
|
||||
+10
-10
@@ -35,6 +35,7 @@ async function actionClickOrActionEnter(
|
||||
return;
|
||||
}
|
||||
}
|
||||
const currentPage = await editor.getCurrentPage();
|
||||
switch (mdTree.type) {
|
||||
case "WikiLink": {
|
||||
let pageLink = mdTree.children![1]!.children![0].text!;
|
||||
@@ -46,20 +47,20 @@ async function actionClickOrActionEnter(
|
||||
}
|
||||
}
|
||||
if (!pageLink) {
|
||||
pageLink = await editor.getCurrentPage();
|
||||
pageLink = currentPage;
|
||||
}
|
||||
await editor.navigate(pageLink, pos, false, inNewWindow);
|
||||
break;
|
||||
}
|
||||
case "PageRef": {
|
||||
const bracketedPageRef = mdTree.children![0].text!;
|
||||
await editor.navigate(
|
||||
// Slicing off the initial [[ and final ]]
|
||||
bracketedPageRef.substring(2, bracketedPageRef.length - 2),
|
||||
0,
|
||||
false,
|
||||
inNewWindow,
|
||||
|
||||
// Slicing off the initial [[ and final ]]
|
||||
const pageName = bracketedPageRef.substring(
|
||||
2,
|
||||
bracketedPageRef.length - 2,
|
||||
);
|
||||
await editor.navigate(pageName, 0, false, inNewWindow);
|
||||
break;
|
||||
}
|
||||
case "NakedURL":
|
||||
@@ -71,13 +72,12 @@ async function actionClickOrActionEnter(
|
||||
if (!urlNode) {
|
||||
return;
|
||||
}
|
||||
let url = urlNode.children![0].text!;
|
||||
const url = urlNode.children![0].text!;
|
||||
if (url.length <= 1) {
|
||||
return editor.flashNotification("Empty link, ignoring", "error");
|
||||
}
|
||||
if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) {
|
||||
url = decodeURIComponent(url);
|
||||
return editor.openUrl(`/.fs/${url}`);
|
||||
return editor.openUrl(`/.fs/${decodeURI(url)}`);
|
||||
} else {
|
||||
await editor.openUrl(url);
|
||||
}
|
||||
|
||||
+14
-11
@@ -20,9 +20,9 @@ import {
|
||||
renderToText,
|
||||
replaceNodesMatching,
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { applyQuery } from "$sb/lib/query.ts";
|
||||
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
|
||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||
import { invokeFunction } from "../../plug-api/silverbullet-syscall/system.ts";
|
||||
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
|
||||
|
||||
// Key space:
|
||||
// pl:toPage:pos => pageName
|
||||
@@ -32,6 +32,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
|
||||
const backLinks: { key: string; value: string }[] = [];
|
||||
// [[Style Links]]
|
||||
// console.log("Now indexing links for", name);
|
||||
removeQueries(tree);
|
||||
const pageMeta = await extractFrontmatter(tree);
|
||||
if (Object.keys(pageMeta).length > 0) {
|
||||
// console.log("Extracted page meta data", pageMeta);
|
||||
@@ -44,8 +45,6 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
|
||||
await index.set(name, "meta:", pageMeta);
|
||||
}
|
||||
|
||||
// throw new Error("Boom");
|
||||
|
||||
collectNodesMatching(tree, (n) => n.type === "WikiLinkPage").forEach((n) => {
|
||||
let toPage = n.children![0].text!;
|
||||
if (toPage.includes("@")) {
|
||||
@@ -106,7 +105,7 @@ export async function copyPage() {
|
||||
await space.getPageMeta(newName);
|
||||
// So when we get to this point, we error out
|
||||
throw new Error(
|
||||
`Page ${newName} already exists, cannot rename to existing page.`,
|
||||
`Page ${newName} already exists, cannot rename to existing page.`,
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (e.message === "Not found") {
|
||||
@@ -165,6 +164,7 @@ export async function renamePage(cmdDef: any) {
|
||||
console.log("All pages containing backlinks", pagesToUpdate);
|
||||
|
||||
const text = await editor.getText();
|
||||
|
||||
console.log("Writing new page to space");
|
||||
const newPageMeta = await space.writePage(newName, text);
|
||||
console.log("Navigating to new page");
|
||||
@@ -198,6 +198,7 @@ export async function renamePage(cmdDef: any) {
|
||||
}
|
||||
const mdTree = await markdown.parseMarkdown(text);
|
||||
addParentPointers(mdTree);
|
||||
// The links in the page are going to be relative pointers to the old name
|
||||
replaceNodesMatching(mdTree, (n): ParseTree | undefined | null => {
|
||||
if (n.type === "WikiLinkPage") {
|
||||
const pageName = n.children![0].text!;
|
||||
@@ -265,18 +266,20 @@ export async function reindexCommand() {
|
||||
|
||||
// Completion
|
||||
export async function pageComplete(completeEvent: CompleteEvent) {
|
||||
const match = /\[\[([^\]@:]*)$/.exec(completeEvent.linePrefix);
|
||||
const match = /\[\[([^\]@:\{}]*)$/.exec(completeEvent.linePrefix);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const allPages = await space.listPages();
|
||||
return {
|
||||
from: completeEvent.pos - match[1].length,
|
||||
options: allPages.map((pageMeta) => ({
|
||||
label: pageMeta.name,
|
||||
boost: pageMeta.lastModified,
|
||||
type: "page",
|
||||
})),
|
||||
options: allPages.map((pageMeta) => {
|
||||
return {
|
||||
label: pageMeta.name,
|
||||
boost: pageMeta.lastModified,
|
||||
type: "page",
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { editor } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
export async function syncSpaceCommand() {
|
||||
await editor.flashNotification("Syncing space...");
|
||||
await editor.syncSpace();
|
||||
await editor.flashNotification("Done.");
|
||||
}
|
||||
+39
-39
@@ -3,6 +3,10 @@ import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||
import { renderToText } from "$sb/lib/tree.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
import { readSettings } from "$sb/lib/settings_page.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
import { buildHandebarOptions } from "../directive/util.ts";
|
||||
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
export async function instantiateTemplateCommand() {
|
||||
const allPages = await space.listPages();
|
||||
@@ -36,10 +40,16 @@ export async function instantiateTemplateCommand() {
|
||||
"$disableDirectives",
|
||||
]);
|
||||
|
||||
const tempPageMeta: PageMeta = {
|
||||
name: "",
|
||||
lastModified: 0,
|
||||
perm: "rw",
|
||||
};
|
||||
|
||||
if (additionalPageMeta.$name) {
|
||||
additionalPageMeta.$name = replaceTemplateVars(
|
||||
additionalPageMeta.$name,
|
||||
"",
|
||||
tempPageMeta,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +60,7 @@ export async function instantiateTemplateCommand() {
|
||||
if (!pageName) {
|
||||
return;
|
||||
}
|
||||
tempPageMeta.name = pageName;
|
||||
|
||||
try {
|
||||
// Fails if doesn't exist
|
||||
@@ -67,7 +78,7 @@ export async function instantiateTemplateCommand() {
|
||||
// The preferred scenario, let's keep going
|
||||
}
|
||||
|
||||
const pageText = replaceTemplateVars(renderToText(parseTree), pageName);
|
||||
const pageText = replaceTemplateVars(renderToText(parseTree), tempPageMeta);
|
||||
await space.writePage(pageName, pageText);
|
||||
await editor.navigate(pageName);
|
||||
}
|
||||
@@ -79,6 +90,7 @@ export async function insertSnippet() {
|
||||
});
|
||||
const cursorPos = await editor.getCursor();
|
||||
const page = await editor.getCurrentPage();
|
||||
const pageMeta = await space.getPageMeta(page);
|
||||
const allSnippets = allPages
|
||||
.filter((pageMeta) => pageMeta.name.startsWith(snippetPrefix))
|
||||
.map((pageMeta) => ({
|
||||
@@ -97,10 +109,10 @@ export async function insertSnippet() {
|
||||
}
|
||||
|
||||
const text = await space.readPage(`${snippetPrefix}${selectedSnippet.name}`);
|
||||
let templateText = replaceTemplateVars(text, page);
|
||||
let templateText = replaceTemplateVars(text, pageMeta);
|
||||
const carretPos = templateText.indexOf("|^|");
|
||||
templateText = templateText.replace("|^|", "");
|
||||
templateText = replaceTemplateVars(templateText, page);
|
||||
templateText = replaceTemplateVars(templateText, pageMeta);
|
||||
await editor.insertAtCursor(templateText);
|
||||
if (carretPos !== -1) {
|
||||
await editor.moveCursor(cursorPos + carretPos);
|
||||
@@ -108,37 +120,9 @@ export async function insertSnippet() {
|
||||
}
|
||||
|
||||
// TODO: This should probably be replaced with handlebards somehow?
|
||||
export function replaceTemplateVars(s: string, pageName: string): string {
|
||||
return s.replaceAll(/\{\{([^\}]+)\}\}/g, (match, v) => {
|
||||
switch (v) {
|
||||
case "today":
|
||||
return niceDate(new Date());
|
||||
case "tomorrow": {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
return niceDate(tomorrow);
|
||||
}
|
||||
|
||||
case "yesterday": {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return niceDate(yesterday);
|
||||
}
|
||||
case "lastWeek": {
|
||||
const lastWeek = new Date();
|
||||
lastWeek.setDate(lastWeek.getDate() - 7);
|
||||
return niceDate(lastWeek);
|
||||
}
|
||||
case "nextWeek": {
|
||||
const nextWeek = new Date();
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
return niceDate(nextWeek);
|
||||
}
|
||||
case "page":
|
||||
return pageName;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
export function replaceTemplateVars(s: string, pageMeta: PageMeta): string {
|
||||
const template = Handlebars.compile(s, { noEscape: true });
|
||||
return template({}, buildHandebarOptions(pageMeta));
|
||||
}
|
||||
|
||||
export async function quickNoteCommand() {
|
||||
@@ -159,6 +143,7 @@ export async function dailyNoteCommand() {
|
||||
});
|
||||
const date = niceDate(new Date());
|
||||
const pageName = `${dailyNotePrefix}${date}`;
|
||||
let carretPos = 0;
|
||||
|
||||
try {
|
||||
await space.getPageMeta(pageName);
|
||||
@@ -167,15 +152,25 @@ export async function dailyNoteCommand() {
|
||||
let dailyNoteTemplateText = "";
|
||||
try {
|
||||
dailyNoteTemplateText = await space.readPage(dailyNoteTemplate);
|
||||
carretPos = dailyNoteTemplateText.indexOf("|^|");
|
||||
if (carretPos === -1) {
|
||||
carretPos = 0;
|
||||
}
|
||||
dailyNoteTemplateText = dailyNoteTemplateText.replace("|^|", "");
|
||||
} catch {
|
||||
console.warn(`No daily note template found at ${dailyNoteTemplate}`);
|
||||
}
|
||||
|
||||
await space.writePage(
|
||||
pageName,
|
||||
replaceTemplateVars(dailyNoteTemplateText, pageName),
|
||||
replaceTemplateVars(dailyNoteTemplateText, {
|
||||
name: pageName,
|
||||
lastModified: 0,
|
||||
perm: "rw",
|
||||
}),
|
||||
);
|
||||
}
|
||||
await editor.navigate(pageName);
|
||||
await editor.navigate(pageName, carretPos);
|
||||
}
|
||||
|
||||
function getWeekStartDate(monday = false) {
|
||||
@@ -210,7 +205,11 @@ export async function weeklyNoteCommand() {
|
||||
// Doesn't exist, let's create
|
||||
await space.writePage(
|
||||
pageName,
|
||||
replaceTemplateVars(weeklyNoteTemplateText, pageName),
|
||||
replaceTemplateVars(weeklyNoteTemplateText, {
|
||||
name: pageName,
|
||||
lastModified: 0,
|
||||
perm: "rw",
|
||||
}),
|
||||
);
|
||||
}
|
||||
await editor.navigate(pageName);
|
||||
@@ -222,10 +221,11 @@ export async function weeklyNoteCommand() {
|
||||
export async function insertTemplateText(cmdDef: any) {
|
||||
const cursorPos = await editor.getCursor();
|
||||
const page = await editor.getCurrentPage();
|
||||
const pageMeta = await space.getPageMeta(page);
|
||||
let templateText: string = cmdDef.value;
|
||||
const carretPos = templateText.indexOf("|^|");
|
||||
templateText = templateText.replace("|^|", "");
|
||||
templateText = replaceTemplateVars(templateText, page);
|
||||
templateText = replaceTemplateVars(templateText, pageMeta);
|
||||
await editor.insertAtCursor(templateText);
|
||||
if (carretPos !== -1) {
|
||||
await editor.moveCursor(cursorPos + carretPos);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { editor, markdown, sync } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { editor, markdown, space, sync } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import {
|
||||
removeParentPointers,
|
||||
renderToText,
|
||||
@@ -6,11 +6,12 @@ import {
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { renderDirectives } from "./directives.ts";
|
||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
|
||||
export async function updateDirectivesOnPageCommand(arg: any) {
|
||||
// If `arg` is a string, it's triggered automatically via an event, not explicitly via a command
|
||||
const explicitCall = typeof arg !== "string";
|
||||
const pageName = await editor.getCurrentPage();
|
||||
const pageMeta = await space.getPageMeta(await editor.getCurrentPage());
|
||||
const text = await editor.getText();
|
||||
const tree = await markdown.parseMarkdown(text);
|
||||
const metaData = await extractFrontmatter(tree, ["$disableDirectives"]);
|
||||
@@ -55,7 +56,7 @@ export async function updateDirectivesOnPageCommand(arg: any) {
|
||||
}
|
||||
const fullMatch = text.substring(tree.from!, tree.to!);
|
||||
try {
|
||||
const promise = renderDirectives(pageName, tree);
|
||||
const promise = renderDirectives(pageMeta, tree);
|
||||
replacements.push({
|
||||
textPromise: promise,
|
||||
fullMatch,
|
||||
@@ -117,7 +118,7 @@ export async function updateDirectivesOnPageCommand(arg: any) {
|
||||
|
||||
// Pure server driven implementation of directive updating
|
||||
export async function updateDirectives(
|
||||
pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
text: string,
|
||||
) {
|
||||
const tree = await markdown.parseMarkdown(text);
|
||||
@@ -134,7 +135,7 @@ export async function updateDirectives(
|
||||
const fullMatch = text.substring(tree.from!, tree.to!);
|
||||
try {
|
||||
const promise = renderDirectives(
|
||||
pageName,
|
||||
pageMeta,
|
||||
tree,
|
||||
);
|
||||
replacements.push({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
import { CompleteEvent } from "$sb/app_event.ts";
|
||||
import { buildHandebarOptions, handlebarHelpers } from "./util.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
|
||||
export async function queryComplete(completeEvent: CompleteEvent) {
|
||||
const match = /#query ([\w\-_]+)*$/.exec(completeEvent.linePrefix);
|
||||
@@ -18,3 +20,23 @@ export async function queryComplete(completeEvent: CompleteEvent) {
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function handlebarHelperComplete(completeEvent: CompleteEvent) {
|
||||
const match = /\{\{([\w@]*)$/.exec(completeEvent.linePrefix);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlebarOptions = buildHandebarOptions({ name: "" } as PageMeta);
|
||||
const allCompletions = Object.keys(handlebarOptions.helpers).concat(
|
||||
Object.keys(handlebarOptions.data).map((key) => `@${key}`),
|
||||
);
|
||||
|
||||
return {
|
||||
from: completeEvent.pos - match[1].length,
|
||||
options: allCompletions
|
||||
.map((name) => ({
|
||||
label: name,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ functions:
|
||||
path: ./complete.ts:queryComplete
|
||||
events:
|
||||
- editor:complete
|
||||
handlebarHelperComplete:
|
||||
path: ./complete.ts:handlebarHelperComplete
|
||||
events:
|
||||
- editor:complete
|
||||
|
||||
# Templates
|
||||
insertQuery:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ParseTree, renderToText } from "$sb/lib/tree.ts";
|
||||
import { sync } from "../../plug-api/silverbullet-syscall/mod.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
|
||||
import { evalDirectiveRenderer } from "./eval_directive.ts";
|
||||
import { queryDirectiveRenderer } from "./query_directive.ts";
|
||||
@@ -17,13 +18,13 @@ export const directiveRegex =
|
||||
* Looks for directives in the text dispatches them based on name
|
||||
*/
|
||||
export async function directiveDispatcher(
|
||||
pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
directiveTree: ParseTree,
|
||||
directiveRenderers: Record<
|
||||
string,
|
||||
(
|
||||
directive: string,
|
||||
pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
arg: string | ParseTree,
|
||||
) => Promise<string>
|
||||
>,
|
||||
@@ -34,6 +35,14 @@ export async function directiveDispatcher(
|
||||
const directiveStartText = renderToText(directiveStart).trim();
|
||||
const directiveEndText = renderToText(directiveEnd).trim();
|
||||
|
||||
if (!(await sync.hasInitialSyncCompleted())) {
|
||||
console.info(
|
||||
"Initial sync hasn't completed yet, not updating directives.",
|
||||
);
|
||||
// Render the query directive as-is
|
||||
return renderToText(directiveTree);
|
||||
}
|
||||
|
||||
if (directiveStart.children!.length === 1) {
|
||||
// Everything not #query
|
||||
const match = directiveStartRegex.exec(directiveStart.children![0].text!);
|
||||
@@ -44,7 +53,7 @@ export async function directiveDispatcher(
|
||||
let [_fullMatch, type, arg] = match;
|
||||
try {
|
||||
arg = arg.trim();
|
||||
const newBody = await directiveRenderers[type](type, pageName, arg);
|
||||
const newBody = await directiveRenderers[type](type, pageMeta, arg);
|
||||
const result =
|
||||
`${directiveStartText}\n${newBody.trim()}\n${directiveEndText}`;
|
||||
return result;
|
||||
@@ -53,17 +62,9 @@ export async function directiveDispatcher(
|
||||
}
|
||||
} else {
|
||||
// #query
|
||||
if (!(await sync.hasInitialSyncCompleted())) {
|
||||
console.info(
|
||||
"Initial sync hasn't completed yet, not updating query directives.",
|
||||
);
|
||||
// Render the query directive as-is
|
||||
return renderToText(directiveTree);
|
||||
}
|
||||
|
||||
const newBody = await directiveRenderers["query"](
|
||||
"query",
|
||||
pageName,
|
||||
pageMeta,
|
||||
directiveStart.children![1], // The query ParseTree
|
||||
);
|
||||
const result =
|
||||
@@ -73,10 +74,10 @@ export async function directiveDispatcher(
|
||||
}
|
||||
|
||||
export async function renderDirectives(
|
||||
pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
directiveTree: ParseTree,
|
||||
): Promise<string> {
|
||||
const replacementText = await directiveDispatcher(pageName, directiveTree, {
|
||||
const replacementText = await directiveDispatcher(pageMeta, directiveTree, {
|
||||
use: templateDirectiveRenderer,
|
||||
include: templateDirectiveRenderer,
|
||||
query: queryDirectiveRenderer,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { YAML } from "$sb/plugos-syscall/mod.ts";
|
||||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { jsonToMDTable, renderTemplate } from "./util.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
import { replaceTemplateVars } from "../core/template.ts";
|
||||
|
||||
// Enables plugName.functionName(arg1, arg2) syntax in JS expressions
|
||||
function translateJs(js: string): string {
|
||||
@@ -20,13 +22,13 @@ const expressionRegex = /(.+?)(\s+render\s+\[\[([^\]]+)\]\])?$/;
|
||||
// This is rather scary and fragile stuff, but it works.
|
||||
export async function evalDirectiveRenderer(
|
||||
_directive: string,
|
||||
_pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
expression: string | ParseTree,
|
||||
): Promise<string> {
|
||||
if (typeof expression !== "string") {
|
||||
throw new Error("Expected a string");
|
||||
}
|
||||
console.log("Got JS expression", expression);
|
||||
// console.log("Got JS expression", expression);
|
||||
const match = expressionRegex.exec(expression);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid eval directive: ${expression}`);
|
||||
@@ -44,11 +46,11 @@ export async function evalDirectiveRenderer(
|
||||
function invokeFunction(name, ...args) {
|
||||
return syscall("system.invokeFunction", "server", name, ...args);
|
||||
}
|
||||
return ${translateJs(expression)};
|
||||
return ${replaceTemplateVars(translateJs(expression), pageMeta)};
|
||||
})()`,
|
||||
);
|
||||
if (template) {
|
||||
return await renderTemplate(template, result);
|
||||
return await renderTemplate(pageMeta, template, result);
|
||||
}
|
||||
if (typeof result === "string") {
|
||||
return result;
|
||||
|
||||
@@ -5,17 +5,18 @@ import { renderTemplate } from "./util.ts";
|
||||
import { parseQuery } from "./parser.ts";
|
||||
import { jsonToMDTable } from "./util.ts";
|
||||
import { ParseTree } from "$sb/lib/tree.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
|
||||
export async function queryDirectiveRenderer(
|
||||
_directive: string,
|
||||
pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
query: string | ParseTree,
|
||||
): Promise<string> {
|
||||
if (typeof query === "string") {
|
||||
throw new Error("Argument must be a ParseTree");
|
||||
}
|
||||
const parsedQuery = parseQuery(
|
||||
JSON.parse(replaceTemplateVars(JSON.stringify(query), pageName)),
|
||||
JSON.parse(replaceTemplateVars(JSON.stringify(query), pageMeta)),
|
||||
);
|
||||
|
||||
const eventName = `query:${parsedQuery.table}`;
|
||||
@@ -24,7 +25,7 @@ export async function queryDirectiveRenderer(
|
||||
// Let's dispatch an event and see what happens
|
||||
const results = await events.dispatchEvent(
|
||||
eventName,
|
||||
{ query: parsedQuery, pageName: pageName },
|
||||
{ query: parsedQuery, pageName: pageMeta.name },
|
||||
30 * 1000,
|
||||
);
|
||||
if (results.length === 0) {
|
||||
@@ -34,6 +35,7 @@ export async function queryDirectiveRenderer(
|
||||
// console.log("Parsed query", parsedQuery);
|
||||
if (parsedQuery.render) {
|
||||
const rendered = await renderTemplate(
|
||||
pageMeta,
|
||||
parsedQuery.render,
|
||||
results[0],
|
||||
);
|
||||
|
||||
@@ -8,13 +8,14 @@ import { replaceTemplateVars } from "../core/template.ts";
|
||||
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
||||
import { directiveRegex } from "./directives.ts";
|
||||
import { updateDirectives } from "./command.ts";
|
||||
import { registerHandlebarsHelpers } from "./util.ts";
|
||||
import { buildHandebarOptions } from "./util.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
|
||||
const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/;
|
||||
|
||||
export async function templateDirectiveRenderer(
|
||||
directive: string,
|
||||
pageName: string,
|
||||
pageMeta: PageMeta,
|
||||
arg: string | ParseTree,
|
||||
): Promise<string> {
|
||||
if (typeof arg !== "string") {
|
||||
@@ -29,9 +30,13 @@ export async function templateDirectiveRenderer(
|
||||
let parsedArgs = {};
|
||||
if (args) {
|
||||
try {
|
||||
parsedArgs = JSON.parse(args);
|
||||
parsedArgs = JSON.parse(replaceTemplateVars(args, pageMeta));
|
||||
} catch {
|
||||
throw new Error(`Failed to parse template instantiation args: ${arg}`);
|
||||
throw new Error(
|
||||
`Failed to parse template instantiation arg: ${
|
||||
replaceTemplateVars(args, pageMeta)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
let templateText = "";
|
||||
@@ -51,18 +56,14 @@ export async function templateDirectiveRenderer(
|
||||
|
||||
// if it's a template injection (not a literal "include")
|
||||
if (directive === "use") {
|
||||
registerHandlebarsHelpers();
|
||||
const templateFn = Handlebars.compile(
|
||||
replaceTemplateVars(newBody, pageName),
|
||||
newBody,
|
||||
{ noEscape: true },
|
||||
);
|
||||
if (typeof parsedArgs !== "string") {
|
||||
(parsedArgs as any).page = pageName;
|
||||
}
|
||||
newBody = templateFn(parsedArgs);
|
||||
newBody = templateFn(parsedArgs, buildHandebarOptions(pageMeta));
|
||||
|
||||
// Recursively render directives
|
||||
newBody = await updateDirectives(pageName, newBody);
|
||||
newBody = await updateDirectives(pageMeta, newBody);
|
||||
}
|
||||
return newBody.trim();
|
||||
}
|
||||
|
||||
+42
-33
@@ -2,6 +2,7 @@ import Handlebars from "handlebars";
|
||||
|
||||
import { space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
import { PageMeta } from "../../web/types.ts";
|
||||
|
||||
const maxWidth = 70;
|
||||
|
||||
@@ -79,47 +80,55 @@ export function jsonToMDTable(
|
||||
}
|
||||
|
||||
export async function renderTemplate(
|
||||
pageMeta: PageMeta,
|
||||
renderTemplate: string,
|
||||
data: any[],
|
||||
): Promise<string> {
|
||||
registerHandlebarsHelpers();
|
||||
|
||||
// Handlebars.registerHelper("yaml", (v: any, prefix: string) => {
|
||||
// if (typeof prefix === "string") {
|
||||
// let yaml = (await YAML.stringify(v))
|
||||
// .split("\n")
|
||||
// .join("\n" + prefix)
|
||||
// .trim();
|
||||
// if (Array.isArray(v)) {
|
||||
// return "\n" + prefix + yaml;
|
||||
// } else {
|
||||
// return yaml;
|
||||
// }
|
||||
// } else {
|
||||
// return YAML.stringify(v).trim();
|
||||
// }
|
||||
// });
|
||||
let templateText = await space.readPage(renderTemplate);
|
||||
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
|
||||
const template = Handlebars.compile(templateText, { noEscape: true });
|
||||
return template(data);
|
||||
return template(data, buildHandebarOptions(pageMeta));
|
||||
}
|
||||
|
||||
export function registerHandlebarsHelpers() {
|
||||
Handlebars.registerHelper("json", (v: any) => JSON.stringify(v));
|
||||
Handlebars.registerHelper("niceDate", (ts: any) => niceDate(new Date(ts)));
|
||||
Handlebars.registerHelper("escapeRegexp", (ts: any) => {
|
||||
return ts.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
});
|
||||
Handlebars.registerHelper("prefixLines", (v: string, prefix: string) =>
|
||||
v
|
||||
.split("\n")
|
||||
.map((l) => prefix + l)
|
||||
.join("\n"));
|
||||
export function buildHandebarOptions(pageMeta: PageMeta) {
|
||||
return {
|
||||
helpers: handlebarHelpers(pageMeta.name),
|
||||
data: { page: pageMeta },
|
||||
};
|
||||
}
|
||||
|
||||
Handlebars.registerHelper(
|
||||
"substring",
|
||||
(s: string, from: number, to: number, elipsis = "") =>
|
||||
export function handlebarHelpers(pageName: string) {
|
||||
return {
|
||||
json: (v: any) => JSON.stringify(v),
|
||||
niceDate: (ts: any) => niceDate(new Date(ts)),
|
||||
escapeRegexp: (ts: any) => {
|
||||
return ts.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
},
|
||||
prefixLines: (v: string, prefix: string) =>
|
||||
v.split("\n").map((l) => prefix + l).join("\n"),
|
||||
substring: (s: string, from: number, to: number, elipsis = "") =>
|
||||
s.length > to - from ? s.substring(from, to) + elipsis : s,
|
||||
);
|
||||
|
||||
today: () => niceDate(new Date()),
|
||||
tomorrow: () => {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
return niceDate(tomorrow);
|
||||
},
|
||||
yesterday: () => {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return niceDate(yesterday);
|
||||
},
|
||||
lastWeek: () => {
|
||||
const lastWeek = new Date();
|
||||
lastWeek.setDate(lastWeek.getDate() - 7);
|
||||
return niceDate(lastWeek);
|
||||
},
|
||||
nextWeek: () => {
|
||||
const nextWeek = new Date();
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
return niceDate(nextWeek);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: federation
|
||||
requiredPermissions:
|
||||
- fetch
|
||||
functions:
|
||||
listFiles:
|
||||
path: ./federation.ts:listFiles
|
||||
pageNamespace:
|
||||
pattern: "!.+"
|
||||
operation: listFiles
|
||||
readFile:
|
||||
path: ./federation.ts:readFile
|
||||
pageNamespace:
|
||||
pattern: "!.+"
|
||||
operation: readFile
|
||||
writeFile:
|
||||
path: ./federation.ts:writeFile
|
||||
pageNamespace:
|
||||
pattern: "!.+"
|
||||
operation: writeFile
|
||||
deleteFile:
|
||||
path: ./federation.ts:deleteFile
|
||||
pageNamespace:
|
||||
pattern: "!.+"
|
||||
operation: deleteFile
|
||||
getFileMeta:
|
||||
path: ./federation.ts:getFileMeta
|
||||
pageNamespace:
|
||||
pattern: "!.+"
|
||||
operation: getFileMeta
|
||||
@@ -0,0 +1,162 @@
|
||||
import "$sb/lib/fetch.ts";
|
||||
import type { FileMeta } from "../../common/types.ts";
|
||||
import { readSetting } from "$sb/lib/settings_page.ts";
|
||||
|
||||
function resolveFederated(pageName: string): string {
|
||||
// URL without the prefix "!""
|
||||
let url = pageName.substring(1);
|
||||
const pieces = url.split("/");
|
||||
pieces.splice(1, 0, ".fs");
|
||||
url = pieces.join("/");
|
||||
if (!url.startsWith("127.0.0.1") && !url.startsWith("localhost")) {
|
||||
url = `https://${url}`;
|
||||
} else {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async function responseToFileMeta(
|
||||
r: Response,
|
||||
name: string,
|
||||
): Promise<FileMeta> {
|
||||
let perm = r.headers.get("X-Permission") as any || "ro";
|
||||
const federationConfigs = await readFederationConfigs();
|
||||
const federationConfig = federationConfigs.find((config) =>
|
||||
name.startsWith(config.uri)
|
||||
);
|
||||
if (federationConfig?.perm) {
|
||||
perm = federationConfig.perm;
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
size: r.headers.get("Content-length")
|
||||
? +r.headers.get("Content-length")!
|
||||
: 0,
|
||||
contentType: r.headers.get("Content-type")!,
|
||||
perm: perm,
|
||||
lastModified: +(r.headers.get("X-Last-Modified") || "0"),
|
||||
};
|
||||
}
|
||||
|
||||
type FederationConfig = {
|
||||
uri: string;
|
||||
perm?: "ro" | "rw";
|
||||
};
|
||||
let federationConfigs: FederationConfig[] = [];
|
||||
let lastFederationUrlFetch = 0;
|
||||
|
||||
async function readFederationConfigs() {
|
||||
// Update at most every 5 seconds
|
||||
if (Date.now() > lastFederationUrlFetch + 5000) {
|
||||
federationConfigs = await readSetting("federate", []);
|
||||
// Normalize URIs
|
||||
for (const config of federationConfigs) {
|
||||
if (!config.uri.startsWith("!")) {
|
||||
config.uri = `!${config.uri}`;
|
||||
}
|
||||
}
|
||||
lastFederationUrlFetch = Date.now();
|
||||
}
|
||||
return federationConfigs;
|
||||
}
|
||||
|
||||
export async function listFiles(): Promise<FileMeta[]> {
|
||||
let fileMetas: FileMeta[] = [];
|
||||
// Fetch them all in parallel
|
||||
await Promise.all((await readFederationConfigs()).map(async (config) => {
|
||||
// console.log("Fetching from federated", config);
|
||||
const uriParts = config.uri.split("/");
|
||||
const rootUri = uriParts[0];
|
||||
const prefix = uriParts.slice(1).join("/");
|
||||
const r = await nativeFetch(resolveFederated(rootUri));
|
||||
fileMetas = fileMetas.concat(
|
||||
(await r.json()).filter((meta: FileMeta) => meta.name.startsWith(prefix))
|
||||
.map((meta: FileMeta) => ({
|
||||
...meta,
|
||||
perm: config.perm || meta.perm,
|
||||
name: `${rootUri}/${meta.name}`,
|
||||
})),
|
||||
);
|
||||
}));
|
||||
// console.log("All of em: ", fileMetas);
|
||||
return fileMetas;
|
||||
}
|
||||
|
||||
export async function readFile(
|
||||
name: string,
|
||||
): Promise<{ data: Uint8Array; meta: FileMeta } | undefined> {
|
||||
const url = resolveFederated(name);
|
||||
const r = await nativeFetch(url);
|
||||
const fileMeta = await responseToFileMeta(r, name);
|
||||
console.log("Fetching", url);
|
||||
if (r.status === 404) {
|
||||
throw Error("Not found");
|
||||
}
|
||||
const data = await r.arrayBuffer();
|
||||
if (!r.ok) {
|
||||
return errorResult(name, `**Error**: Could not load`);
|
||||
}
|
||||
|
||||
return {
|
||||
data: new Uint8Array(data),
|
||||
meta: fileMeta,
|
||||
};
|
||||
}
|
||||
|
||||
function errorResult(
|
||||
name: string,
|
||||
error: string,
|
||||
): { data: Uint8Array; meta: FileMeta } {
|
||||
return {
|
||||
data: new TextEncoder().encode(error),
|
||||
meta: {
|
||||
name,
|
||||
contentType: "text/markdown",
|
||||
lastModified: 0,
|
||||
size: 0,
|
||||
perm: "ro",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeFile(
|
||||
name: string,
|
||||
data: Uint8Array,
|
||||
): Promise<FileMeta> {
|
||||
const url = resolveFederated(name);
|
||||
console.log("Writing federation file", url);
|
||||
|
||||
const r = await nativeFetch(url, {
|
||||
method: "PUT",
|
||||
body: data,
|
||||
});
|
||||
const fileMeta = await responseToFileMeta(r, name);
|
||||
if (!r.ok) {
|
||||
throw new Error("Could not write file");
|
||||
}
|
||||
|
||||
return fileMeta;
|
||||
}
|
||||
|
||||
export async function deleteFile(
|
||||
name: string,
|
||||
): Promise<void> {
|
||||
console.log("Deleting federation file", name);
|
||||
const url = resolveFederated(name);
|
||||
const r = await nativeFetch(url, { method: "DELETE" });
|
||||
if (!r.ok) {
|
||||
throw Error("Failed to delete file");
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFileMeta(name: string): Promise<FileMeta> {
|
||||
const url = resolveFederated(name);
|
||||
console.log("Fetching federation file meta", url);
|
||||
const r = await nativeFetch(url, { method: "OPTIONS" });
|
||||
const fileMeta = await responseToFileMeta(r, name);
|
||||
if (!r.ok) {
|
||||
throw new Error("Not found");
|
||||
}
|
||||
return fileMeta;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ type MarkdownRenderOptions = {
|
||||
annotationPositions?: true;
|
||||
attachmentUrlPrefix?: string;
|
||||
// When defined, use to inline images as data: urls
|
||||
inlineAttachments?: (url: string) => string;
|
||||
translateUrls?: (url: string) => string;
|
||||
};
|
||||
|
||||
function cleanTags(values: (Tag | null)[]): Tag[] {
|
||||
@@ -385,13 +385,17 @@ export function renderMarkdownToHtml(
|
||||
) {
|
||||
preprocess(t, options);
|
||||
const htmlTree = posPreservingRender(t, options);
|
||||
if (htmlTree && options.inlineAttachments) {
|
||||
if (htmlTree && options.translateUrls) {
|
||||
traverseTag(htmlTree, (t) => {
|
||||
if (typeof t === "string") {
|
||||
return;
|
||||
}
|
||||
if (t.name === "img") {
|
||||
t.attrs!.src = options.inlineAttachments!(t.attrs!.src!);
|
||||
t.attrs!.src = options.translateUrls!(t.attrs!.src!);
|
||||
}
|
||||
|
||||
if (t.name === "a") {
|
||||
t.attrs!.href = options.translateUrls!(t.attrs!.href!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { editor, space, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { editor, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { asset, store } from "$sb/plugos-syscall/mod.ts";
|
||||
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
|
||||
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
||||
@@ -7,6 +7,7 @@ export async function updateMarkdownPreview() {
|
||||
if (!(await store.get("enableMarkdownPreview"))) {
|
||||
return;
|
||||
}
|
||||
const pageName = await editor.getCurrentPage();
|
||||
const text = await editor.getText();
|
||||
const mdTree = await parseMarkdown(text);
|
||||
// const cleanMd = await cleanMarkdown(text);
|
||||
@@ -15,7 +16,7 @@ export async function updateMarkdownPreview() {
|
||||
const html = renderMarkdownToHtml(mdTree, {
|
||||
smartHardBreak: true,
|
||||
annotationPositions: true,
|
||||
inlineAttachments: (url) => {
|
||||
translateUrls: (url) => {
|
||||
if (!url.includes("://")) {
|
||||
return `/.fs/${url}`;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export async function searchCommand() {
|
||||
|
||||
export async function readFileSearch(
|
||||
name: string,
|
||||
): Promise<{ data: string; meta: FileMeta }> {
|
||||
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
||||
const phrase = name.substring(
|
||||
searchPrefix.length,
|
||||
name.length - ".md".length,
|
||||
@@ -105,11 +105,7 @@ export async function readFileSearch(
|
||||
`;
|
||||
|
||||
return {
|
||||
// encoding === "arraybuffer" is not an option, so either it's "utf8" or "dataurl"
|
||||
data: base64EncodedDataUrl(
|
||||
"text/markdown",
|
||||
new TextEncoder().encode(text),
|
||||
),
|
||||
data: new TextEncoder().encode(text),
|
||||
meta: {
|
||||
name,
|
||||
contentType: "text/markdown",
|
||||
|
||||
+13
-8
@@ -85,17 +85,21 @@ export async function indexTasks({ name, tree }: IndexTreeEvent) {
|
||||
}
|
||||
|
||||
export function taskToggle(event: ClickEvent) {
|
||||
return taskToggleAtPos(event.pos);
|
||||
return taskToggleAtPos(event.page, event.pos);
|
||||
}
|
||||
|
||||
export function previewTaskToggle(eventString: string) {
|
||||
export async function previewTaskToggle(eventString: string) {
|
||||
const [eventName, pos] = JSON.parse(eventString);
|
||||
if (eventName === "task") {
|
||||
return taskToggleAtPos(+pos);
|
||||
return taskToggleAtPos(await editor.getCurrentPage(), +pos);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
|
||||
async function toggleTaskMarker(
|
||||
_pageName: string,
|
||||
node: ParseTree,
|
||||
moveToPos: number,
|
||||
) {
|
||||
let changeTo = "[x]";
|
||||
if (node.children![0].text === "[x]" || node.children![0].text === "[X]") {
|
||||
changeTo = "[ ]";
|
||||
@@ -136,14 +140,14 @@ async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function taskToggleAtPos(pos: number) {
|
||||
export async function taskToggleAtPos(pageName: string, pos: number) {
|
||||
const text = await editor.getText();
|
||||
const mdTree = await markdown.parseMarkdown(text);
|
||||
addParentPointers(mdTree);
|
||||
|
||||
const node = nodeAtPos(mdTree, pos);
|
||||
if (node && node.type === "TaskMarker") {
|
||||
await toggleTaskMarker(node, pos);
|
||||
await toggleTaskMarker(pageName, node, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +160,7 @@ export async function taskToggleCommand() {
|
||||
const node = nodeAtPos(tree, pos);
|
||||
// We kwow node.type === Task (due to the task context)
|
||||
const taskMarker = findNodeOfType(node!, "TaskMarker");
|
||||
await toggleTaskMarker(taskMarker!, pos);
|
||||
await toggleTaskMarker(await editor.getCurrentPage(), taskMarker!, pos);
|
||||
}
|
||||
|
||||
export async function postponeCommand() {
|
||||
@@ -181,7 +185,7 @@ export async function postponeCommand() {
|
||||
return;
|
||||
}
|
||||
// Parse "naive" due date
|
||||
let [yyyy, mm, dd] = date.split("-").map(Number)
|
||||
let [yyyy, mm, dd] = date.split("-").map(Number);
|
||||
// Create new naive Date object.
|
||||
// `monthIndex` parameter is zero-based, so subtract 1 from parsed month.
|
||||
const d = new Date(yyyy, mm - 1, dd);
|
||||
@@ -214,6 +218,7 @@ export async function queryProvider({
|
||||
query,
|
||||
}: QueryProviderEvent): Promise<Task[]> {
|
||||
const allTasks: Task[] = [];
|
||||
|
||||
for (const { key, page, value } of await index.queryPrefix("task:")) {
|
||||
const pos = key.split(":")[1];
|
||||
allTasks.push({
|
||||
|
||||
Reference in New Issue
Block a user