diff --git a/common/languages.ts b/common/languages.ts index d2dca447..e8af865b 100644 --- a/common/languages.ts +++ b/common/languages.ts @@ -25,7 +25,7 @@ import { xmlLanguage, yamlLanguage, } from "./deps.ts"; -import { highlightingDirectiveParser } from "./markdown_parser/parser.ts"; +import { highlightingQueryParser } from "./markdown_parser/parser.ts"; export const builtinLanguages: Record = { "meta": StreamLanguage.define(yamlLanguage), @@ -79,7 +79,7 @@ export const builtinLanguages: Record = { "dart": StreamLanguage.define(dartLanguage), "query": LRLanguage.define({ name: "query", - parser: highlightingDirectiveParser, + parser: highlightingQueryParser, }), }; diff --git a/common/markdown_parser/customtags.ts b/common/markdown_parser/customtags.ts index bafe2efa..0e7f21ed 100644 --- a/common/markdown_parser/customtags.ts +++ b/common/markdown_parser/customtags.ts @@ -13,11 +13,6 @@ export const OrderedList = Tag.define(); export const Highlight = Tag.define(); export const HorizontalRuleTag = Tag.define(); -export const DirectiveTag = Tag.define(); -export const DirectiveStartTag = Tag.define(); -export const DirectiveEndTag = Tag.define(); -export const DirectiveProgramTag = Tag.define(); - export const AttributeTag = Tag.define(); export const AttributeNameTag = Tag.define(); export const AttributeValueTag = Tag.define(); diff --git a/common/markdown_parser/parser.test.ts b/common/markdown_parser/parser.test.ts index f7da9a89..4c350d9f 100644 --- a/common/markdown_parser/parser.test.ts +++ b/common/markdown_parser/parser.test.ts @@ -51,28 +51,6 @@ Deno.test("Test parser", () => { assertEquals(node, undefined); }); -const directiveSample = ` -Before - -Body line 1 - -Body line 2 - -End -`; - -const nestedDirectiveExample = ` -Before - -1 - -100 - -3 - -End -`; - const inlineAttributeSample = ` Hello there [a link](http://zef.plus) [age: 100] @@ -152,9 +130,9 @@ Deno.test("Test command link arguments", () => { const commands = collectNodesOfType(tree, "CommandLink"); assertEquals(commands.length, 2); - const args1 = findNodeOfType(commands[0], "CommandLinkArgs") + const args1 = findNodeOfType(commands[0], "CommandLinkArgs"); assertEquals(args1!.children![0].text, '"with", "args"'); - const args2 = findNodeOfType(commands[1], "CommandLinkArgs") + const args2 = findNodeOfType(commands[1], "CommandLinkArgs"); assertEquals(args2!.children![0].text, '"other", "args", 123'); -}); \ No newline at end of file +}); diff --git a/common/markdown_parser/parser.ts b/common/markdown_parser/parser.ts index 3d9d22d2..e7df9fc3 100644 --- a/common/markdown_parser/parser.ts +++ b/common/markdown_parser/parser.ts @@ -160,6 +160,21 @@ export const Highlight: MarkdownConfig = { ], }; +import { parser as queryParser } from "./parse-query.js"; + +export const highlightingQueryParser = queryParser.configure({ + props: [ + styleTags({ + "Name": t.variableName, + "String": t.string, + "Number": t.number, + "PageRef": ct.WikiLinkTag, + "where limit select render Order OrderKW and or as InKW each all": + t.keyword, + }), + ], +}); + export const attributeStartRegex = /^\[([\w\$]+)(::?\s*)/; export const Attribute: MarkdownConfig = { @@ -264,121 +279,8 @@ export const Comment: MarkdownConfig = { ], }; -// Directive parser - -const directiveStart = /^\s*\s*/; -const directiveEnd = /^\s*\s*/; - -import { parser as directiveParser } from "./parse-query.js"; -import { parser as expressionParser } from "./parse-expression.js"; import { Table } from "./table_parser.ts"; import { foldNodeProp } from "@codemirror/language"; -import { lezerToParseTree } from "./parse_tree.ts"; - -export const highlightingDirectiveParser = directiveParser.configure({ - props: [ - styleTags({ - "Name": t.variableName, - "String": t.string, - "Number": t.number, - "PageRef": ct.WikiLinkTag, - "where limit select render Order OrderKW and or as InKW each all": - t.keyword, - }), - ], -}); - -export const Directive: MarkdownConfig = { - defineNodes: [ - { name: "Directive", block: true, style: ct.DirectiveTag }, - { name: "DirectiveStart", style: ct.DirectiveStartTag, block: true }, - { name: "DirectiveEnd", style: ct.DirectiveEndTag }, - { name: "DirectiveBody", block: true }, - ], - parseBlock: [{ - name: "Directive", - parse: (cx, line: Line) => { - const match = directiveStart.exec(line.text); - if (!match) { - return false; - } - - // console.log("Parsing directive", line.text); - - const frontStart = cx.parsedPos; - const [fullMatch, directive, arg] = match; - const elts = []; - if (directive === "query") { - const queryParseTree = highlightingDirectiveParser.parse(arg); - elts.push(cx.elt( - "DirectiveStart", - cx.parsedPos, - cx.parsedPos + line.text.length + 1, - [cx.elt(queryParseTree, frontStart + fullMatch.indexOf(arg))], - )); - } else if (directive === "eval") { - const expressionParseTree = expressionParser.parse(arg); - elts.push(cx.elt( - "DirectiveStart", - cx.parsedPos, - cx.parsedPos + line.text.length + 1, - [cx.elt(expressionParseTree, frontStart + fullMatch.indexOf(arg))], - )); - } else { - elts.push(cx.elt( - "DirectiveStart", - cx.parsedPos, - cx.parsedPos + line.text.length + 1, - )); - } - - // console.log("Query parse tree", queryParseTree.topNode); - - cx.nextLine(); - const startPos = cx.parsedPos; - let endPos = startPos; - let text = ""; - let lastPos = cx.parsedPos; - let nesting = 0; - while (true) { - if (directiveEnd.exec(line.text) && nesting === 0) { - break; - } - text += line.text + "\n"; - endPos += line.text.length + 1; - if (directiveStart.exec(line.text)) { - nesting++; - } - if (directiveEnd.exec(line.text)) { - nesting--; - } - cx.nextLine(); - if (cx.parsedPos === lastPos) { - // End of file, no progress made, there may be a better way to do this but :shrug: - return false; - } - lastPos = cx.parsedPos; - } - const directiveBodyTree = cx.parser.parse(text); - - elts.push( - cx.elt("DirectiveBody", startPos, endPos, [ - cx.elt(directiveBodyTree, startPos), - ]), - ); - endPos = cx.parsedPos + line.text.length; - elts.push(cx.elt( - "DirectiveEnd", - cx.parsedPos, - cx.parsedPos + line.text.length, - )); - cx.nextLine(); - cx.addElement(cx.elt("Directive", frontStart, endPos, elts)); - return true; - }, - before: "HTMLBlock", - }], -}; // FrontMatter parser @@ -450,7 +352,6 @@ export default function buildMarkdown(mdExtensions: MDExt[]): Language { CommandLink, Attribute, FrontMatter, - Directive, TaskList, Comment, Highlight, diff --git a/common/spaces/sync.test.ts b/common/spaces/sync.test.ts index 23400157..e8678edf 100644 --- a/common/spaces/sync.test.ts +++ b/common/spaces/sync.test.ts @@ -1,4 +1,4 @@ -import { removeDirectiveBody, SpaceSync, SyncStatusItem } from "./sync.ts"; +import { SpaceSync, SyncStatusItem } from "./sync.ts"; import { DiskSpacePrimitives } from "./disk_space_primitives.ts"; import { assertEquals } from "../../test_deps.ts"; @@ -117,23 +117,11 @@ Deno.test("Test store", async () => { await primary.writeFile("index", stringToBytes("Hello 1")); await secondary.writeFile("index", stringToBytes("Hello 1")); - // And two more files with different bodies, but only within a query directive — shouldn't conflict - await primary.writeFile( - "index.md", - stringToBytes( - "Hello\n\nHello 1\n", - ), - ); - await secondary.writeFile( - "index.md", - stringToBytes("Hello\n\nHello 2\n"), - ); - await doSync(); await doSync(); // test + index + index.md + previous index.conflicting copy but nothing more - assertEquals((await primary.fetchFileList()).length, 4); + assertEquals((await primary.fetchFileList()).length, 3); console.log("Bringing a third device in the mix"); @@ -191,26 +179,6 @@ function sleep(ms = 10): Promise { }); } -Deno.test("Remove directive bodies", () => { - assertEquals( - removeDirectiveBody(` -This is a body -bla bla bla - -Hello - -This is a body - -`), - ` - -Hello - - -`, - ); -}); - function stringToBytes(s: string): Uint8Array { return new TextEncoder().encode(s); } diff --git a/common/spaces/sync.ts b/common/spaces/sync.ts index e7f2036e..8301f44a 100644 --- a/common/spaces/sync.ts +++ b/common/spaces/sync.ts @@ -1,6 +1,3 @@ -import { renderToText, replaceNodesMatching } from "../../plug-api/lib/tree.ts"; -import buildMarkdown from "../markdown_parser/parser.ts"; -import { parse } from "../markdown_parser/parse_tree.ts"; import { SpacePrimitives } from "./space_primitives.ts"; import { EventEmitter } from "../../plugos/event.ts"; import { FileMeta } from "$sb/types.ts"; @@ -305,56 +302,32 @@ export class SpaceSync extends EventEmitter { const pageData1 = await primary.readFile(name); const pageData2 = await secondary.readFile(name); - if (name.endsWith(".md")) { - console.log( - "[sync]", - "File is markdown, using smart conflict resolution", - ); - // Let's use a smartert check for markdown files, ignoring directive bodies - const pageText1 = removeDirectiveBody( - new TextDecoder().decode(pageData1.data), - ); - const pageText2 = removeDirectiveBody( - new TextDecoder().decode(pageData2.data), - ); - if (pageText1 === pageText2) { - console.log( - "[sync]", - "Files are the same (eliminating the directive bodies), no conflict", - ); + let byteWiseMatch = true; + const arrayBuffer1 = pageData1.data; + const arrayBuffer2 = pageData2.data; + if (arrayBuffer1.byteLength !== arrayBuffer2.byteLength) { + byteWiseMatch = false; + } + if (byteWiseMatch) { + // Byte-wise comparison + for (let i = 0; i < arrayBuffer1.byteLength; i++) { + if (arrayBuffer1[i] !== arrayBuffer2[i]) { + byteWiseMatch = false; + break; + } + } + // Byte wise they're still the same, so no confict + if (byteWiseMatch) { + console.log("[sync]", "Files are the same, no conflict"); + snapshot.set(name, [ pageData1.meta.lastModified, pageData2.meta.lastModified, ]); return 0; } - } else { - let byteWiseMatch = true; - const arrayBuffer1 = pageData1.data; - const arrayBuffer2 = pageData2.data; - if (arrayBuffer1.byteLength !== arrayBuffer2.byteLength) { - byteWiseMatch = false; - } - if (byteWiseMatch) { - // Byte-wise comparison - for (let i = 0; i < arrayBuffer1.byteLength; i++) { - if (arrayBuffer1[i] !== arrayBuffer2[i]) { - byteWiseMatch = false; - break; - } - } - // Byte wise they're still the same, so no confict - if (byteWiseMatch) { - console.log("[sync]", "Files are the same, no conflict"); - - snapshot.set(name, [ - pageData1.meta.lastModified, - pageData2.meta.lastModified, - ]); - return 0; - } - } } + let operations = 0; const revisionFileName = filePieces.length === 1 ? `${name}.conflicted.${pageData2.meta.lastModified}` @@ -403,18 +376,3 @@ export class SpaceSync extends EventEmitter { } } } - -const markdownLanguage = buildMarkdown([]); - -export function removeDirectiveBody(text: string): string { - // Parse - const tree = parse(markdownLanguage, text); - // Remove bodies - replaceNodesMatching(tree, (node) => { - if (node.type === "DirectiveBody") { - return null; - } - }); - // Turn back into text - return renderToText(tree); -} diff --git a/plug-api/lib/parser-query.test.ts b/plug-api/lib/parser-query.test.ts index d8185d95..731f0405 100644 --- a/plug-api/lib/parser-query.test.ts +++ b/plug-api/lib/parser-query.test.ts @@ -1,17 +1,16 @@ import { parse } from "../../common/markdown_parser/parse_tree.ts"; -import buildMarkdown from "../../common/markdown_parser/parser.ts"; -import { AST, findNodeOfType, parseTreeToAST } from "$sb/lib/tree.ts"; +import { AST, parseTreeToAST } from "$sb/lib/tree.ts"; import { assertEquals } from "../../test_deps.ts"; import { astToKvQuery } from "$sb/lib/parse-query.ts"; - -const lang = buildMarkdown([]); +import { languageFor } from "../../common/languages.ts"; function wrapQueryParse(query: string): AST | null { - const tree = parse(lang, `\n$\n`); - return parseTreeToAST(findNodeOfType(tree, "Query")!); + const tree = parse(languageFor("query")!, query); + // console.log("tree", tree); + return parseTreeToAST(tree.children![0]); } -Deno.test("Test directive parser", () => { +Deno.test("Test query parser", () => { // const query = ; // console.log("query", query); assertEquals( diff --git a/plug-api/lib/query.test.ts b/plug-api/lib/query.test.ts deleted file mode 100644 index 194195fa..00000000 --- a/plug-api/lib/query.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { renderToText } from "./tree.ts"; -import { assert, assertEquals } from "../../test_deps.ts"; -import { removeQueries } from "./query.ts"; -import { parseMarkdown } from "$sb/lib/test_utils.ts"; - -const queryRemovalTest = ` -# Heading -Before - -Bla bla remove me - -End -`; - -Deno.test("White out queries", () => { - const mdTree = parseMarkdown(queryRemovalTest); - removeQueries(mdTree); - const text = renderToText(mdTree); - // Same length? We should be good - assertEquals(text.length, queryRemovalTest.length); - assert(text.indexOf("remove me") === -1); - console.log("Whited out text", text); -}); diff --git a/plug-api/lib/query.ts b/plug-api/lib/query.ts index 33a13455..d7707328 100644 --- a/plug-api/lib/query.ts +++ b/plug-api/lib/query.ts @@ -1,12 +1,6 @@ import { ParseTree, renderToText, replaceNodesMatching } from "$sb/lib/tree.ts"; import { FunctionMap, KV, Query, QueryExpression } from "$sb/types.ts"; -export const queryRegex = - /()(.+?)()/gs; - -export const directiveStartRegex = //s; -export const directiveEndRegex = //s; - export function evalQueryExpression( val: QueryExpression, obj: any, @@ -246,17 +240,3 @@ export function applyQueryNoFilterKV( } return allItems; } - -export function removeQueries(pt: ParseTree) { - replaceNodesMatching(pt, (t) => { - if (t.type !== "Directive") { - return; - } - const renderedText = renderToText(t); - return { - from: t.from, - to: t.to, - text: new Array(renderedText.length + 1).join(" "), - }; - }); -} diff --git a/plug-api/lib/resolve.test.ts b/plug-api/lib/resolve.test.ts index f79cb615..509c2296 100644 --- a/plug-api/lib/resolve.test.ts +++ b/plug-api/lib/resolve.test.ts @@ -49,16 +49,13 @@ Deno.test("Test rewritePageRefs", () => { let tree = parseMarkdown(` This is a [[local link]] and [[local link|with alias]]. - - +\`\`\`query +page render [[template/page]] +\`\`\` - - - - - - - +\`\`\`template +page: "[[template/use-template]]" +\`\`\` `); rewritePageRefs(tree, "!silverbullet.md"); let rewrittenText = renderToText(tree); @@ -68,16 +65,13 @@ This is a [[local link]] and [[local link|with alias]]. ` This is a [[!silverbullet.md/local link]] and [[!silverbullet.md/local link|with alias]]. - - +\`\`\`query +page render [[!silverbullet.md/template/page]] +\`\`\` - - - - - - - +\`\`\`template +page: "[[!silverbullet.md/template/use-template]]" +\`\`\` `, ); diff --git a/plug-api/lib/resolve.ts b/plug-api/lib/resolve.ts index 84c2ff1e..dccb863e 100644 --- a/plug-api/lib/resolve.ts +++ b/plug-api/lib/resolve.ts @@ -49,29 +49,6 @@ export function isFederationPath(path: string) { export function rewritePageRefs(tree: ParseTree, containerPageName: string) { traverseTree(tree, (n): boolean => { - if (n.type === "DirectiveStart") { - const pageRef = findNodeOfType(n, "PageRef")!; - if (pageRef) { - const pageRefName = pageRef.children![0].text!.slice(2, -2); - pageRef.children![0].text = `[[${ - resolvePath(containerPageName, pageRefName) - }]]`; - } - const directiveText = n.children![0].text; - // #use or #import - if (directiveText) { - const match = /\[\[(.+)\]\]/.exec(directiveText); - if (match) { - const pageRefName = match[1]; - n.children![0].text = directiveText.replace( - match[0], - `[[${resolvePath(containerPageName, pageRefName)}]]`, - ); - } - } - - return true; - } if (n.type === "FencedCode") { const codeInfo = findNodeOfType(n, "CodeInfo"); if (!codeInfo) { diff --git a/plugs/builtin_plugs.ts b/plugs/builtin_plugs.ts index 60f78f12..21e679aa 100644 --- a/plugs/builtin_plugs.ts +++ b/plugs/builtin_plugs.ts @@ -5,7 +5,6 @@ export const builtinPlugNames = [ "sync", "template", "plug-manager", - "directive", "emoji", "query", "markdown", diff --git a/plugs/directive/command.ts b/plugs/directive/command.ts deleted file mode 100644 index 8e8a832e..00000000 --- a/plugs/directive/command.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { editor, markdown, mq, space, sync } from "$sb/syscalls.ts"; -import { - addParentPointers, - collectNodesOfType, - findParentMatching, - nodeAtPos, - ParseTree, - removeParentPointers, - renderToText, - traverseTree, -} from "$sb/lib/tree.ts"; -import { renderDirectives } from "./directives.ts"; -import { extractFrontmatter } from "$sb/lib/frontmatter.ts"; -import { isFederationPath } from "$sb/lib/resolve.ts"; -import { MQMessage, PageMeta } from "$sb/types.ts"; -import { sleep } from "$sb/lib/async.ts"; - -const directiveUpdateQueueName = "directiveUpdateQueue"; - -export async function updateDirectivesOnPageCommand() { - // If `arg` is a string, it's triggered automatically via an event, not explicitly via a command - const currentPage = await editor.getCurrentPage(); - let pageMeta: PageMeta | undefined; - try { - pageMeta = await space.getPageMeta(currentPage); - } catch { - console.info("Page not found, not updating directives"); - return; - } - const text = await editor.getText(); - const tree = await markdown.parseMarkdown(text); - const metaData = await extractFrontmatter(tree, { - removeKeys: ["$disableDirectives"], - }); - - if (isFederationPath(currentPage)) { - 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; - } - - if (!(await sync.hasInitialSyncCompleted())) { - console.info( - "Initial sync hasn't completed yet, not updating directives.", - ); - return; - } - - await editor.save(); - - const replacements = await findReplacements(tree, text, pageMeta); - - // 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. - 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, - ); - console.warn( - "Could not find directive in text, skipping", - replacement.fullMatch, - ); - continue; - } - const from = index, to = index + replacement.fullMatch.length; - const newText = await replacement.textPromise; - if (text.substring(from, to) === newText) { - // No change, skip - continue; - } - await editor.dispatch({ - changes: { - from, - to, - insert: newText, - }, - }); - } -} - -export async function updateDirectivesInSpaceCommand() { - await editor.flashNotification( - "Updating directives in entire space, this can take a while...", - ); - await updateDirectivesInSpace(); - - // And notify the user - await editor.flashNotification("Updating of all directives completed!"); -} - -export async function processUpdateQueue(messages: MQMessage[]) { - for (const message of messages) { - const pageName: string = message.body; - console.log("Updating directives in page", pageName); - await updateDirectivesForPage(pageName); - await mq.ack(directiveUpdateQueueName, message.id); - } -} - -async function findReplacements( - tree: ParseTree, - text: string, - pageMeta: PageMeta, -) { - // Collect all directives and their body replacements - const replacements: { fullMatch: string; textPromise: Promise }[] = - []; - - // Convenience array to wait for all promises to resolve - const allPromises: Promise[] = []; - - removeParentPointers(tree); - - traverseTree(tree, (tree) => { - if (tree.type !== "Directive") { - return false; - } - const fullMatch = text.substring(tree.from!, tree.to!); - try { - 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); - - return replacements; -} - -export async function updateDirectivesInSpace() { - 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); - } - - console.log("Done updating directives in space!"); -} - -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, { - removeKeys: ["$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); - if (newText !== currentText) { - console.info("Content of page changed, saving", pageName); - await space.writePage(pageName, newText); - } -} - -export async function updateDirectives( - pageMeta: PageMeta, - tree: ParseTree, - text: string, -) { - const replacements = await findReplacements(tree, text, pageMeta); - - // Iterate again and replace the bodies. - for (const replacement of replacements) { - text = text.replace( - replacement.fullMatch, - await replacement.textPromise, - ); - } - return text; -} - -export async function convertToLive() { - const text = await editor.getText(); - const pos = await editor.getCursor(); - const tree = await markdown.parseMarkdown(text); - addParentPointers(tree); - const currentNode = nodeAtPos(tree, pos); - const directive = findParentMatching( - currentNode!, - (node) => node.type === "Directive", - ); - if (!directive) { - await editor.flashNotification( - "No directive found at cursor position", - "error", - ); - return; - } - console.log("Got this directive", directive); - const startNode = directive.children![0]; - const startNodeText = renderToText(startNode); - if (startNodeText.includes("#query")) { - const queryText = renderToText(startNode.children![1]); - await editor.dispatch({ - changes: { - from: directive.from, - to: directive.to, - insert: "```query\n" + queryText + "\n```", - }, - }); - } else if ( - startNodeText.includes("#use") || startNodeText.includes("#include") - ) { - const pageRefMatch = /\[\[([^\]]+)\]\]\s*([^\-]+)?/.exec(startNodeText); - if (!pageRefMatch) { - await editor.flashNotification( - "No page reference found in directive", - "error", - ); - return; - } - const val = pageRefMatch[2]; - await editor.dispatch({ - changes: { - from: directive.from, - to: directive.to, - insert: '```template\npage: "[[' + pageRefMatch[1] + ']]"\n' + - (val ? `val: ${val}\n` : "") + "```", - }, - }); - } -} - -export async function convertSpaceToLive() { - if ( - !await editor.confirm( - "This will convert all directives in the space to live queries. Are you sure?", - ) - ) { - return; - } - const pages = await space.listPages(); - for (const page of pages) { - console.log("Now converting", page); - const text = await space.readPage(page.name); - const newText = await convertDirectivesOnPage(text); - if (text !== newText) { - console.log("Changes were made, writing", page.name); - await space.writePage(page.name, newText); - } - } - await editor.flashNotification("All done!"); -} - -export async function convertDirectivesOnPage(text: string) { - const tree = await markdown.parseMarkdown(text); - collectNodesOfType(tree, "Directive").forEach((directive) => { - const directiveText = renderToText(directive); - console.log("Got this directive", directiveText); - const startNode = directive.children![0]; - const startNodeText = renderToText(startNode); - if (startNodeText.includes("#query")) { - const queryText = renderToText(startNode.children![1]); - text = text.replace(directiveText, "```query\n" + queryText + "\n```"); - } else if ( - startNodeText.includes("#use") || startNodeText.includes("#include") - ) { - const pageRefMatch = /\[\[([^\]]+)\]\]\s*([^\-]+)?/.exec(startNodeText); - if (!pageRefMatch) { - return; - } - const val = pageRefMatch[2]; - text = text.replace( - directiveText, - '```template\npage: "[[' + pageRefMatch[1] + ']]"\n' + - (val ? `val: ${val}\n` : "") + "```", - ); - } - }); - // console.log("Converted page", text); - return text; -} diff --git a/plugs/directive/complete.ts b/plugs/directive/complete.ts deleted file mode 100644 index b382077a..00000000 --- a/plugs/directive/complete.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { events } from "$sb/syscalls.ts"; -import { CompleteEvent } from "$sb/app_event.ts"; -import { buildHandebarOptions } from "./util.ts"; -import type { - AttributeCompleteEvent, - AttributeCompletion, -} from "../index/attributes.ts"; -import { PageMeta } from "$sb/types.ts"; - -export async function queryComplete(completeEvent: CompleteEvent) { - const querySourceMatch = /#query\s+([\w\-_]*)$/.exec( - completeEvent.linePrefix, - ); - if (querySourceMatch) { - const allEvents = await events.listEvents(); - - const completionOptions = allEvents - .filter((eventName) => - eventName.startsWith("query:") && !eventName.includes("*") - ) - .map((source) => ({ - label: source.substring("query:".length), - })); - - const allObjectTypes: string[] = (await events.dispatchEvent("query_", {})) - .flat(); - - for (const type of allObjectTypes) { - completionOptions.push({ - label: type, - }); - } - - return { - from: completeEvent.pos - querySourceMatch[1].length, - options: completionOptions, - }; - } - - if (completeEvent.parentNodes.includes("DirectiveStart")) { - const querySourceMatch = /#query\s+([\w\-_\/]+)/.exec( - completeEvent.linePrefix, - ); - const whereMatch = - /(where|order\s+by|and|select(\s+[\w\s,]+)?)\s+([\w\-_]*)$/.exec( - completeEvent.linePrefix, - ); - if (querySourceMatch && whereMatch) { - const type = querySourceMatch[1]; - const attributePrefix = whereMatch[3]; - const completions = (await events.dispatchEvent( - `attribute:complete:${type}`, - { - source: type, - prefix: attributePrefix, - } as AttributeCompleteEvent, - )).flat() as AttributeCompletion[]; - return { - from: completeEvent.pos - attributePrefix.length, - options: attributeCompletionsToCMCompletion(completions), - }; - } - } - return null; -} - -export function attributeCompletionsToCMCompletion( - completions: AttributeCompletion[], -) { - return completions.map( - (completion) => ({ - label: completion.name, - detail: `${completion.attributeType} (${completion.source})`, - type: "attribute", - }), - ); -} diff --git a/plugs/directive/directive.plug.yaml b/plugs/directive/directive.plug.yaml deleted file mode 100644 index ecccd874..00000000 --- a/plugs/directive/directive.plug.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: directive -requiredPermissions: - - fetch -functions: - updateDirectivesOnPageCommand: - path: ./command.ts:updateDirectivesOnPageCommand - command: - name: "Directives: Update" - events: - - editor:pageLoaded - updateDirectivesInSpace: - path: ./command.ts:updateDirectivesInSpace - updateDirectivesInSpaceCommand: - path: ./command.ts:updateDirectivesInSpaceCommand - command: - name: "Directives: Update Entire Space" - processUpdateQueue: - path: ./command.ts:processUpdateQueue - mqSubscriptions: - - queue: directiveUpdateQueue - batchSize: 3 - queryComplete: - path: ./complete.ts:queryComplete - events: - - editor:complete - # Conversion - convertToLiveQuery: - path: command.ts:convertToLive - command: - name: "Directive: Convert to Live Query/Template" - - convertSpaceToLive: - path: command.ts:convertSpaceToLive - command: - name: "Directive: Convert Entire Space to Live/Templates" diff --git a/plugs/directive/directives.ts b/plugs/directive/directives.ts deleted file mode 100644 index 2ad999d4..00000000 --- a/plugs/directive/directives.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - addParentPointers, - findParentMatching, - ParseTree, - renderToText, -} from "$sb/lib/tree.ts"; -import { PageMeta } from "$sb/types.ts"; -import { editor, markdown } from "$sb/syscalls.ts"; - -import { evalDirectiveRenderer } from "./eval_directive.ts"; -import { queryDirectiveRenderer } from "./query_directive.ts"; -import { - cleanTemplateInstantiations, - templateDirectiveRenderer, -} from "./template_directive.ts"; - -/** An error that occurs while a directive is being rendered. - * Mostly annotates the underlying error with page metadata. - */ -export class RenderDirectiveError extends Error { - pageMeta: PageMeta; - directive: string; - cause: Error; - - constructor(pageMeta: PageMeta, directive: string, cause: Error) { - super(`In directive "${directive}" from "${pageMeta.name}": ${cause}`, { - cause: cause, - }); - - this.pageMeta = pageMeta; - this.directive = directive; - this.cause = cause; - } -} - -export const directiveStartRegex = - //i; - -export const directiveRegex = - /()(.+?)()/gs; -/** - * Looks for directives in the text dispatches them based on name - */ -export async function directiveDispatcher( - pageMeta: PageMeta, - directiveTree: ParseTree, - directiveRenderers: Record< - string, - ( - directive: string, - pageMeta: PageMeta, - arg: string | ParseTree, - ) => Promise - >, -): Promise { - const directiveStart = directiveTree.children![0]; // - const directiveEnd = directiveTree.children![2]; // - - const directiveStartText = renderToText(directiveStart).trim(); - const directiveEndText = renderToText(directiveEnd).trim(); - - const firstPart = directiveStart.children![0].text!; - if (firstPart?.includes("#query")) { - // #query - const newBody = await directiveRenderers["query"]( - "query", - pageMeta, - directiveStart.children![1].children![0], // The query ParseTree - ); - const result = - `${directiveStartText}\n${newBody.trim()}\n${directiveEndText}`; - return result; - } else if (firstPart?.includes("#eval")) { - console.log("Eval stuff", directiveStart.children![1].children![0]); - const newBody = await directiveRenderers["eval"]( - "eval", - pageMeta, - directiveStart.children![1].children![0], - ); - const result = - `${directiveStartText}\n${newBody.trim()}\n${directiveEndText}`; - return result; - } else { - // Everything not #query and #eval - const match = directiveStartRegex.exec(directiveStart.children![0].text!); - if (!match) { - throw Error("No match"); - } - - let [_fullMatch, type, arg] = match; - try { - arg = arg.trim(); - const newBody = await directiveRenderers[type](type, pageMeta, arg); - const result = - `${directiveStartText}\n${newBody.trim()}\n${directiveEndText}`; - return result; - } catch (e: any) { - return `${directiveStartText}\n**ERROR:** ${e.message}\n${directiveEndText}`; - } - } -} - -export async function renderDirectives( - pageMeta: PageMeta, - directiveTree: ParseTree, -): Promise { - try { - const replacementText = await directiveDispatcher(pageMeta, directiveTree, { - use: templateDirectiveRenderer, - include: templateDirectiveRenderer, - query: queryDirectiveRenderer, - eval: evalDirectiveRenderer, - }); - return cleanTemplateInstantiations(replacementText); - } catch (e) { - throw new RenderDirectiveError( - pageMeta, - renderToText(directiveTree.children![0].children![1]).trim(), - e, - ); - } -} diff --git a/plugs/directive/eval_directive.ts b/plugs/directive/eval_directive.ts deleted file mode 100644 index 51bd2b92..00000000 --- a/plugs/directive/eval_directive.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ParseTree, parseTreeToAST } from "$sb/lib/tree.ts"; -import { replaceTemplateVars } from "../template/template.ts"; -import { PageMeta } from "$sb/types.ts"; -import { expressionToKvQueryExpression } from "$sb/lib/parse-query.ts"; -import { evalQueryExpression } from "$sb/lib/query.ts"; -import { builtinFunctions } from "$sb/lib/builtin_query_functions.ts"; - -// This is rather scary and fragile stuff, but it works. -export async function evalDirectiveRenderer( - _directive: string, - pageMeta: PageMeta, - expression: string | ParseTree, -): Promise { - try { - const result = evalQueryExpression( - expressionToKvQueryExpression(parseTreeToAST( - JSON.parse( - await replaceTemplateVars(JSON.stringify(expression), pageMeta), - ), - )), - {}, - builtinFunctions, - ); - - return Promise.resolve("" + result); - } catch (e: any) { - return Promise.resolve(`**ERROR:** ${e.message}`); - } -} diff --git a/plugs/directive/query_directive.ts b/plugs/directive/query_directive.ts deleted file mode 100644 index a0beea60..00000000 --- a/plugs/directive/query_directive.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { events } from "$sb/syscalls.ts"; - -import { replaceTemplateVars } from "../template/template.ts"; -import { renderQueryTemplate } from "./util.ts"; -import { jsonToMDTable } from "./util.ts"; -import { ParseTree, parseTreeToAST } from "$sb/lib/tree.ts"; -import { astToKvQuery } from "$sb/lib/parse-query.ts"; -import { PageMeta, Query } from "$sb/types.ts"; - -export async function queryDirectiveRenderer( - _directive: string, - pageMeta: PageMeta, - query: string | ParseTree, -): Promise { - if (typeof query === "string") { - throw new Error("Argument must be a ParseTree"); - } - const parsedQuery: Query = astToKvQuery( - parseTreeToAST( - JSON.parse(await replaceTemplateVars(JSON.stringify(query), pageMeta)), - ), - ); - // console.log("QUERY", parsedQuery); - - const eventName = `query:${parsedQuery.querySource}`; - - // console.log("Parsed query", parsedQuery); - // Let's dispatch an event and see what happens - const results = await events.dispatchEvent( - eventName, - { query: parsedQuery, pageName: pageMeta.name }, - 30 * 1000, - ); - if (results.length === 0) { - // This means there was no handler for the event which means it's unsupported - return `**Error:** Unsupported query source '${parsedQuery.querySource}'`; - } else { - // console.log("Parsed query", parsedQuery); - const allResults = results.flat(); - if (parsedQuery.render) { - const rendered = await renderQueryTemplate( - pageMeta, - parsedQuery.render, - allResults, - parsedQuery.renderAll!, - ); - return rendered.trim(); - } else { - if (allResults.length === 0) { - return "No results"; - } else { - return jsonToMDTable(allResults); - } - } - } -} diff --git a/plugs/directive/template_directive.ts b/plugs/directive/template_directive.ts deleted file mode 100644 index 600c5e6e..00000000 --- a/plugs/directive/template_directive.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { queryRegex } from "$sb/lib/query.ts"; -import { ParseTree, renderToText } from "$sb/lib/tree.ts"; -import { handlebars, markdown, space } from "$sb/syscalls.ts"; - -import { replaceTemplateVars } from "../template/template.ts"; -import { extractFrontmatter } from "$sb/lib/frontmatter.ts"; -import { directiveRegex } from "./directives.ts"; -import { updateDirectives } from "./command.ts"; -import { resolvePath, rewritePageRefs } from "$sb/lib/resolve.ts"; -import { PageMeta } from "$sb/types.ts"; -import { renderTemplate } from "../template/plug_api.ts"; - -const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/; - -export async function templateDirectiveRenderer( - directive: string, - pageMeta: PageMeta, - arg: string | ParseTree, -): Promise { - if (typeof arg !== "string") { - throw new Error("Template directives must be a string"); - } - const match = arg.match(templateRegex); - if (!match) { - throw new Error(`Invalid template directive: ${arg}`); - } - let templatePath = match[1]; - const args = match[2]; - let parsedArgs = {}; - if (args) { - try { - parsedArgs = JSON.parse(await replaceTemplateVars(args, pageMeta)); - } catch { - throw new Error( - `Failed to parse template instantiation arg: ${ - replaceTemplateVars(args, pageMeta) - }`, - ); - } - } - let templateText = ""; - if ( - templatePath.startsWith("http://") || templatePath.startsWith("https://") - ) { - try { - const req = await fetch(templatePath); - templateText = await req.text(); - } catch (e: any) { - templateText = `ERROR: ${e.message}`; - } - } else { - templatePath = resolvePath(pageMeta.name, templatePath); - templateText = await space.readPage(templatePath); - } - const tree = await markdown.parseMarkdown(templateText); - await extractFrontmatter(tree, { removeFrontmatterSection: true }); // Remove entire frontmatter section, if any - - // Resolve paths in the template - rewritePageRefs(tree, templatePath); - - let newBody = renderToText(tree); - - // console.log("Rewritten template:", newBody); - - // if it's a template injection (not a literal "include") - if (directive === "use") { - newBody = (await renderTemplate(newBody, pageMeta, parsedArgs)).text; - - // Recursively render directives - const tree = await markdown.parseMarkdown(newBody); - newBody = await updateDirectives(pageMeta, tree, newBody); - } - return newBody.trim(); -} - -export function cleanTemplateInstantiations(text: string) { - return text.replaceAll(directiveRegex, ( - _fullMatch, - startInst, - type, - _args, - body, - endInst, - ): string => { - if (type === "use") { - body = body.replaceAll( - queryRegex, - ( - _fullMatch: string, - _startQuery: string, - _query: string, - body: string, - ) => { - return body.trim(); - }, - ); - } - return `${startInst}${body}${endInst}`; - }); -} diff --git a/plugs/directive/util.ts b/plugs/directive/util.ts deleted file mode 100644 index c3aa6402..00000000 --- a/plugs/directive/util.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { handlebars, space } from "$sb/syscalls.ts"; -import { handlebarHelpers } from "../../common/syscalls/handlebar_helpers.ts"; -import { PageMeta } from "$sb/types.ts"; -import { cleanTemplate } from "../template/plug_api.ts"; - -export function defaultJsonTransformer(_k: string, v: any) { - if (v === undefined) { - return ""; - } - if (typeof v === "string") { - return v.replaceAll("\n", " ").replaceAll("|", "\\|"); - } - return "" + v; -} - -// Nicely format an array of JSON objects as a Markdown table -export function jsonToMDTable( - jsonArray: any[], - valueTransformer: (k: string, v: any) => string = defaultJsonTransformer, -): string { - const headers = new Set(); - for (const entry of jsonArray) { - for (const k of Object.keys(entry)) { - headers.add(k); - } - } - - const headerList = [...headers]; - const lines = []; - lines.push( - "|" + - headerList - .map( - (headerName) => headerName, - ) - .join("|") + - "|", - ); - lines.push( - "|" + - headerList - .map(() => "--") - .join("|") + - "|", - ); - for (const val of jsonArray) { - const el = []; - for (const prop of headerList) { - const s = valueTransformer(prop, val[prop]); - el.push(s); - } - lines.push("|" + el.join("|") + "|"); - } - return lines.join("\n"); -} - -export async function renderQueryTemplate( - pageMeta: PageMeta, - templatePage: string, - data: any[], - renderAll: boolean, -): Promise { - let templateText = await space.readPage(templatePage); - templateText = await cleanTemplate(templateText); - - if (!renderAll) { - templateText = `{{#each .}}\n${templateText}\n{{/each}}`; - } - return handlebars.renderTemplate(templateText, data, { page: pageMeta }); -} - -export function buildHandebarOptions(pageMeta: PageMeta) { - return { - helpers: handlebarHelpers(), - data: { page: pageMeta }, - }; -} diff --git a/plugs/editor/broken_links.ts b/plugs/editor/broken_links.ts index 260a1a6d..aebbdc99 100644 --- a/plugs/editor/broken_links.ts +++ b/plugs/editor/broken_links.ts @@ -43,11 +43,6 @@ export async function brokenLinksCommand() { } } - if (tree.type === "DirectiveBody") { - // Don't look inside directive bodies - return true; - } - return false; }); } diff --git a/plugs/index/anchor.ts b/plugs/index/anchor.ts index 0254feb3..78c6785d 100644 --- a/plugs/index/anchor.ts +++ b/plugs/index/anchor.ts @@ -1,6 +1,5 @@ import { collectNodesOfType } from "$sb/lib/tree.ts"; import type { CompleteEvent, IndexTreeEvent } from "$sb/app_event.ts"; -import { removeQueries } from "$sb/lib/query.ts"; import { ObjectValue, QueryExpression } from "$sb/types.ts"; import { indexObjects, queryObjects } from "./api.ts"; @@ -11,7 +10,6 @@ type AnchorObject = ObjectValue<{ }>; export async function indexAnchors({ name: pageName, tree }: IndexTreeEvent) { - removeQueries(tree); const anchors: ObjectValue[] = []; collectNodesOfType(tree, "NamedAnchor").forEach((n) => { diff --git a/plugs/index/builtins.ts b/plugs/index/builtins.ts index 8a291ba4..348da731 100644 --- a/plugs/index/builtins.ts +++ b/plugs/index/builtins.ts @@ -62,7 +62,6 @@ export const builtins: Record> = { page: "!string", pos: "!number", alias: "!string", - inDirective: "!boolean", asTemplate: "!boolean", }, paragraph: { diff --git a/plugs/index/command.ts b/plugs/index/command.ts index 820893f6..3ece21ba 100644 --- a/plugs/index/command.ts +++ b/plugs/index/command.ts @@ -14,6 +14,10 @@ export async function reindexSpace() { console.log("Clearing page index..."); // Executed this way to not have to embed the search plug code here await system.invokeFunction("index.clearIndex"); + + // Load builtins + await system.invokeFunction("index.loadBuiltinsIntoIndex"); + const pages = await space.listPages(); // Queue all page names to be indexed diff --git a/plugs/index/data.ts b/plugs/index/data.ts index c3a09e8c..ab9e647f 100644 --- a/plugs/index/data.ts +++ b/plugs/index/data.ts @@ -1,7 +1,6 @@ import type { IndexTreeEvent } from "$sb/app_event.ts"; import { YAML } from "$sb/syscalls.ts"; import { collectNodesOfType, findNodeOfType } from "$sb/lib/tree.ts"; -import { removeQueries } from "$sb/lib/query.ts"; import { ObjectValue } from "$sb/types.ts"; import { indexObjects } from "./api.ts"; import { TagObject } from "./tags.ts"; @@ -16,8 +15,6 @@ type DataObject = ObjectValue< export async function indexData({ name, tree }: IndexTreeEvent) { const dataObjects: ObjectValue[] = []; - removeQueries(tree); - await Promise.all( collectNodesOfType(tree, "FencedCode").map(async (t) => { const codeInfoNode = findNodeOfType(t, "CodeInfo"); diff --git a/plugs/index/item.ts b/plugs/index/item.ts index cfe92051..cb418c89 100644 --- a/plugs/index/item.ts +++ b/plugs/index/item.ts @@ -1,7 +1,6 @@ import type { IndexTreeEvent } from "$sb/app_event.ts"; import { collectNodesOfType, ParseTree, renderToText } from "$sb/lib/tree.ts"; -import { removeQueries } from "$sb/lib/query.ts"; import { extractAttributes } from "$sb/lib/attribute.ts"; import { rewritePageRefs } from "$sb/lib/resolve.ts"; import { ObjectValue } from "$sb/types.ts"; @@ -17,7 +16,6 @@ export type ItemObject = ObjectValue< export async function indexItems({ name, tree }: IndexTreeEvent) { const items: ObjectValue[] = []; - removeQueries(tree); // console.log("Indexing items", name); diff --git a/plugs/index/linked_mentions.ts b/plugs/index/linked_mentions.ts index ee85cf17..f6c52fbb 100644 --- a/plugs/index/linked_mentions.ts +++ b/plugs/index/linked_mentions.ts @@ -19,11 +19,11 @@ export async function renderMentions(): Promise { const page = await editor.getCurrentPage(); const linksResult = await queryObjects("link", { - // Query all links that point to this page, excluding those that are inside directives and self pointers. - filter: ["and", ["!=", ["attr", "page"], ["string", page]], ["and", ["=", [ + // Query all links that point to this page + filter: ["and", ["!=", ["attr", "page"], ["string", page]], ["=", [ "attr", "toPage", - ], ["string", page]], ["=", ["attr", "inDirective"], ["boolean", false]]]], + ], ["string", page]]], }); if (linksResult.length === 0) { // Don't show the panel if there are no links here. diff --git a/plugs/index/page_links.ts b/plugs/index/page_links.ts index 43242419..0767ad51 100644 --- a/plugs/index/page_links.ts +++ b/plugs/index/page_links.ts @@ -16,7 +16,6 @@ export type LinkObject = { pos: number; snippet: string; alias?: string; - inDirective: boolean; asTemplate: boolean; }; @@ -51,55 +50,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) { const pageText = renderToText(tree); - let directiveDepth = 0; traverseTree(tree, (n): boolean => { - if (n.type === "DirectiveStart") { - directiveDepth++; - const pageRef = findNodeOfType(n, "PageRef")!; - if (pageRef) { - const pageRefName = resolvePath( - name, - pageRef.children![0].text!.slice(2, -2), - ); - const pos = pageRef.from! + 2; - links.push({ - ref: `${name}@${pos}`, - tags: ["link"], - toPage: pageRefName, - pos: pos, - snippet: extractSnippet(pageText, pos), - page: name, - asTemplate: true, - inDirective: false, - }); - } - const directiveText = n.children![0].text; - // #use or #import - if (directiveText) { - const match = /\[\[(.+)\]\]/.exec(directiveText); - if (match) { - const pageRefName = resolvePath(name, match[1]); - const pos = n.from! + match.index! + 2; - links.push({ - ref: `${name}@${pos}`, - tags: ["link"], - toPage: pageRefName, - page: name, - snippet: extractSnippet(pageText, pos), - pos: pos, - asTemplate: true, - inDirective: false, - }); - } - } - - return true; - } - if (n.type === "DirectiveEnd") { - directiveDepth--; - return true; - } - if (n.type === "WikiLink") { const wikiLinkPage = findNodeOfType(n, "WikiLinkPage")!; const wikiLinkAlias = findNodeOfType(n, "WikiLinkAlias"); @@ -113,12 +64,8 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) { snippet: extractSnippet(pageText, pos), pos, page: name, - inDirective: false, asTemplate: false, }; - if (directiveDepth > 0) { - link.inDirective = true; - } if (wikiLinkAlias) { link.alias = wikiLinkAlias.children![0].text!; } @@ -151,7 +98,6 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) { snippet: extractSnippet(pageText, pos), pos: pos, asTemplate: true, - inDirective: false, }); } } diff --git a/plugs/index/tags.ts b/plugs/index/tags.ts index 74337a36..311523e6 100644 --- a/plugs/index/tags.ts +++ b/plugs/index/tags.ts @@ -1,5 +1,4 @@ import type { CompleteEvent, IndexTreeEvent } from "$sb/app_event.ts"; -import { removeQueries } from "$sb/lib/query.ts"; import { extractFrontmatter } from "$sb/lib/frontmatter.ts"; import { indexObjects, queryObjects } from "./api.ts"; import { @@ -16,7 +15,6 @@ export type TagObject = ObjectValue<{ }>; export async function indexTags({ name, tree }: IndexTreeEvent) { - removeQueries(tree); const tags = new Set(); // name:parent addParentPointers(tree); const pageTags: string[] = (await extractFrontmatter(tree)).tags; diff --git a/plugs/markdown/markdown_render.ts b/plugs/markdown/markdown_render.ts index 6626e400..40e010f1 100644 --- a/plugs/markdown/markdown_render.ts +++ b/plugs/markdown/markdown_render.ts @@ -391,10 +391,6 @@ function render( body: cleanTags(mapRender(newChildren)), }; } - case "Directive": { - const body = findNodeOfType(t, "DirectiveBody")!; - return posPreservingRender(body.children![0], options); - } case "Attribute": if (options.preserveAttributes) { return { diff --git a/plugs/query/query.plug.yaml b/plugs/query/query.plug.yaml index abab8149..a3feebba 100644 --- a/plugs/query/query.plug.yaml +++ b/plugs/query/query.plug.yaml @@ -45,15 +45,6 @@ functions: ```query |^| ``` - insertInclude: - redirect: template.insertTemplateText - slashCommand: - name: include - description: Include another page - value: | - - - insertUseTemplate: redirect: template.insertTemplateText slashCommand: diff --git a/plugs/query/query.ts b/plugs/query/query.ts index cef4e0b5..15b641a8 100644 --- a/plugs/query/query.ts +++ b/plugs/query/query.ts @@ -1,4 +1,4 @@ -import type { LintEvent, WidgetContent } from "$sb/app_event.ts"; +import type { LintEvent } from "$sb/app_event.ts"; import { events, language, space } from "$sb/syscalls.ts"; import { findNodeOfType, @@ -6,10 +6,10 @@ import { traverseTreeAsync, } from "$sb/lib/tree.ts"; import { astToKvQuery } from "$sb/lib/parse-query.ts"; -import { jsonToMDTable, renderQueryTemplate } from "../directive/util.ts"; import { loadPageObject, replaceTemplateVars } from "../template/template.ts"; import { cleanPageRef, resolvePath } from "$sb/lib/resolve.ts"; import { CodeWidgetContent, LintDiagnostic } from "$sb/types.ts"; +import { jsonToMDTable, renderQueryTemplate } from "../template/util.ts"; export async function widget( bodyText: string, diff --git a/plugs/tasks/task.ts b/plugs/tasks/task.ts index 83db5a63..8d4dffe9 100644 --- a/plugs/tasks/task.ts +++ b/plugs/tasks/task.ts @@ -13,7 +13,6 @@ import { replaceNodesMatching, traverseTreeAsync, } from "$sb/lib/tree.ts"; -import { removeQueries } from "$sb/lib/query.ts"; import { niceDate } from "$sb/lib/dates.ts"; import { extractAttributes } from "$sb/lib/attribute.ts"; import { rewritePageRefs } from "$sb/lib/resolve.ts"; @@ -47,7 +46,6 @@ const incompleteStates = [" "]; export async function indexTasks({ name, tree }: IndexTreeEvent) { const tasks: ObjectValue[] = []; const taskStates = new Map(); - removeQueries(tree); addParentPointers(tree); // const allAttributes: AttributeObject[] = []; // const allTags = new Set(); diff --git a/plugs/template/complete.ts b/plugs/template/complete.ts index efdf79bb..94590fa5 100644 --- a/plugs/template/complete.ts +++ b/plugs/template/complete.ts @@ -1,7 +1,6 @@ import { CompleteEvent, SlashCompletion } from "$sb/app_event.ts"; import { PageMeta } from "$sb/types.ts"; import { editor, events, markdown, space } from "$sb/syscalls.ts"; -import { buildHandebarOptions } from "../directive/util.ts"; import type { AttributeCompleteEvent, AttributeCompletion, @@ -11,6 +10,7 @@ import { TemplateObject } from "./types.ts"; import { loadPageObject } from "./template.ts"; import { renderTemplate } from "./api.ts"; import { prepareFrontmatterDispatch } from "$sb/lib/frontmatter.ts"; +import { buildHandebarOptions } from "./util.ts"; export async function templateVariableComplete(completeEvent: CompleteEvent) { const match = /\{\{([\w@]*)$/.exec(completeEvent.linePrefix); diff --git a/plugs/template/util.ts b/plugs/template/util.ts index 32b8befc..77b4f8f9 100644 --- a/plugs/template/util.ts +++ b/plugs/template/util.ts @@ -1,4 +1,8 @@ import { determineTags } from "$sb/lib/cheap_yaml.ts"; +import { handlebarHelpers } from "../../common/syscalls/handlebar_helpers.ts"; +import { PageMeta } from "$sb/types.ts"; +import { handlebars, space } from "$sb/syscalls.ts"; +import { cleanTemplate } from "./plug_api.ts"; const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/; @@ -24,3 +28,76 @@ export function isTemplate(pageText: string): boolean { } return false; } + +export function buildHandebarOptions(pageMeta: PageMeta) { + return { + helpers: handlebarHelpers(), + data: { page: pageMeta }, + }; +} + +export function defaultJsonTransformer(_k: string, v: any) { + if (v === undefined) { + return ""; + } + if (typeof v === "string") { + return v.replaceAll("\n", " ").replaceAll("|", "\\|"); + } + return "" + v; +} + +// Nicely format an array of JSON objects as a Markdown table +export function jsonToMDTable( + jsonArray: any[], + valueTransformer: (k: string, v: any) => string = defaultJsonTransformer, +): string { + const headers = new Set(); + for (const entry of jsonArray) { + for (const k of Object.keys(entry)) { + headers.add(k); + } + } + + const headerList = [...headers]; + const lines = []; + lines.push( + "|" + + headerList + .map( + (headerName) => headerName, + ) + .join("|") + + "|", + ); + lines.push( + "|" + + headerList + .map(() => "--") + .join("|") + + "|", + ); + for (const val of jsonArray) { + const el = []; + for (const prop of headerList) { + const s = valueTransformer(prop, val[prop]); + el.push(s); + } + lines.push("|" + el.join("|") + "|"); + } + return lines.join("\n"); +} + +export async function renderQueryTemplate( + pageMeta: PageMeta, + templatePage: string, + data: any[], + renderAll: boolean, +): Promise { + let templateText = await space.readPage(templatePage); + templateText = await cleanTemplate(templateText); + + if (!renderAll) { + templateText = `{{#each .}}\n${templateText}\n{{/each}}`; + } + return handlebars.renderTemplate(templateText, data, { page: pageMeta }); +} diff --git a/scripts/build_website.sh b/scripts/build_website.sh index 8adec640..4e425e7d 100755 --- a/scripts/build_website.sh +++ b/scripts/build_website.sh @@ -29,7 +29,7 @@ cp -r dist_plug_bundle/_plug/* website_build/_plug/ #echo "And additional ones" curl https://raw.githubusercontent.com/silverbulletmd/silverbullet-mermaid/main/mermaid.plug.js > website_build/_plug/mermaid.plug.js echo "But remove some plugs" -rm -rf website_build/_plug/{plugmd,directive}.plug.js +rm -rf website_build/_plug/{plugmd}.plug.js # Generate random modified date, and replace in _headers too diff --git a/web/cm_plugins/clean.ts b/web/cm_plugins/clean.ts index 4ca2f6ca..40bdb7a8 100644 --- a/web/cm_plugins/clean.ts +++ b/web/cm_plugins/clean.ts @@ -3,7 +3,6 @@ import type { Extension } from "../deps.ts"; import type { Client } from "../client.ts"; import { blockquotePlugin } from "./block_quote.ts"; import { admonitionPlugin } from "./admonition.ts"; -import { directivePlugin } from "./directive.ts"; import { hideHeaderMarkPlugin, hideMarksPlugin } from "./hide_mark.ts"; import { cleanBlockPlugin } from "./block.ts"; import { linkPlugin } from "./link.ts"; @@ -17,7 +16,6 @@ import { fencedCodePlugin } from "./fenced_code.ts"; export function cleanModePlugins(editor: Client) { return [ linkPlugin(editor), - directivePlugin(), blockquotePlugin(), admonitionPlugin(editor), hideMarksPlugin(), diff --git a/web/cm_plugins/directive.ts b/web/cm_plugins/directive.ts deleted file mode 100644 index a619cc84..00000000 --- a/web/cm_plugins/directive.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - directiveEndRegex, - directiveStartRegex, -} from "../../plug-api/lib/query.ts"; -import { Decoration, EditorState, syntaxTree } from "../deps.ts"; -import { decoratorStateField, HtmlWidget, isCursorInRange } from "./util.ts"; - -// Does a few things: hides the directives when the cursor is not placed inside -// Adds a class to the start and end of the directive when the cursor is placed inside -export function directivePlugin() { - return decoratorStateField((state: EditorState) => { - const widgets: any[] = []; - - syntaxTree(state).iterate({ - enter: ({ type, from, to, node }) => { - const parent = node.parent; - - if (!parent) { - return; - } - - const cursorInRange = isCursorInRange(state, [parent.from, parent.to]); - - if (type.name === "DirectiveStart") { - if (cursorInRange) { - // Cursor inside this directive - widgets.push( - Decoration.line({ class: "sb-directive-start" }).range(from), - ); - } else { - const text = state.sliceDoc(from, to); - const match = directiveStartRegex.exec(text); - if (!match) { - console.error("Something went wrong with this directive"); - return; - } - const [_fullMatch, directiveName] = match; - widgets.push( - Decoration.widget({ - widget: new HtmlWidget( - `#${directiveName}`, - "sb-directive-placeholder", - ), - }).range(from), - ); - widgets.push( - Decoration.line({ - class: "sb-directive-start sb-directive-start-outside", - attributes: { - spellcheck: "false", - }, - }).range( - from, - ), - ); - } - return true; - } - - if (type.name === "DirectiveEnd") { - // Cursor outside this directive - if (cursorInRange) { - widgets.push( - Decoration.line({ class: "sb-directive-end" }).range(from), - ); - } else { - const text = state.sliceDoc(from, to); - const match = directiveEndRegex.exec(text); - if (!match) { - console.error("Something went wrong with this directive"); - return; - } - const [_fullMatch, directiveName] = match; - widgets.push( - Decoration.widget({ - widget: new HtmlWidget( - `/${directiveName}`, - "sb-directive-placeholder", - ), - }).range(from), - ); - widgets.push( - Decoration.line({ - class: "sb-directive-end sb-directive-end-outside", - }).range( - from, - ), - ); - } - return true; - } - - if (type.name === "DirectiveBody") { - const lines = state.sliceDoc(from, to).split("\n"); - let pos = from; - for (const line of lines) { - if (pos !== to) { - widgets.push( - Decoration.line({ - class: "sb-directive-body", - }).range(pos), - ); - } - pos += line.length + 1; - } - return true; - } - }, - }); - - return Decoration.set(widgets, true); - }); -} diff --git a/web/cm_plugins/smart_quotes.ts b/web/cm_plugins/smart_quotes.ts index 0c1a6094..0a1ac2bb 100644 --- a/web/cm_plugins/smart_quotes.ts +++ b/web/cm_plugins/smart_quotes.ts @@ -6,9 +6,8 @@ const straightQuoteContexts = [ "FencedCode", "InlineCode", "FrontMatterCode", - "DirectiveStart", "Attribute", - "CommandLink" + "CommandLink", ]; // TODO: Add support for selection (put quotes around or create blockquote block?) diff --git a/web/style.ts b/web/style.ts index 396d93cd..edcfe38a 100644 --- a/web/style.ts +++ b/web/style.ts @@ -48,7 +48,6 @@ export default function highlightStyles(mdExtension: MDExt[]) { { tag: t.invalid, class: "sb-invalid" }, { tag: t.processingInstruction, class: "sb-meta" }, { tag: t.punctuation, class: "sb-punctuation" }, - { tag: ct.DirectiveTag, class: "sb-directive" }, { tag: ct.HorizontalRuleTag, class: "sb-hr" }, ...mdExtension.map((mdExt) => { return { tag: mdExt.tag, ...mdExt.styles, class: mdExt.className }; diff --git a/web/styles/colors.scss b/web/styles/colors.scss index afb08d57..0d7fa84d 100644 --- a/web/styles/colors.scss +++ b/web/styles/colors.scss @@ -317,27 +317,6 @@ color: var(--editor-frontmatter-marker-color); } - // Directives - - .sb-directive-body { - border-left: 1px solid var(--editor-directive-border-color); - border-right: 1px solid var(--editor-directive-border-color); - } - - .cm-line.sb-directive-start, - .cm-line.sb-directive-end { - color: var(--editor-directive-color); - border-color: var(--editor-directive-border-color); - background-color: var(--editor-directive-background-color); - } - - .sb-directive-start-outside, - .sb-directive-end-outside { - &>span.sb-directive-placeholder { - color: var(--editor-directive-info-color); - } - } - .sb-emphasis { font-style: italic; } diff --git a/web/styles/editor.scss b/web/styles/editor.scss index 519a0b03..f815f17e 100644 --- a/web/styles/editor.scss +++ b/web/styles/editor.scss @@ -316,47 +316,6 @@ font-size: 91%; } - .sb-directive-start { - border-top-left-radius: 10px; - border-top-right-radius: 10px; - border-style: solid; - border-width: 1px 1px 0 1px; - padding: 3px; - } - - .sb-directive-end { - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - border-style: solid; - border-width: 0 1px 1px 1px; - padding: 3px; - } - - .sb-directive-start .sb-comment, - .sb-directive-end .sb-comment { - position: relative; - left: -12px; - } - - .sb-directive-start-outside, - .sb-directive-end-outside { - color: transparent; - pointer-events: none; - height: 1.3em; - - .sb-directive-placeholder { - padding-right: 7px; - float: right; - font-size: 80%; - } - - &>span, - &.sb-directive-start, - &.sb-directive-end { - color: transparent; - } - } - .sb-line-frontmatter-outside, .sb-line-code-outside { .sb-meta { @@ -458,7 +417,7 @@ border-top-left-radius: 5px; margin: 0 0 5px 0; padding: 10px !important; - background-color: var(--editor-directive-background-color); + background-color: var(--editor-widget-background-color); font-size: 1.2em; } @@ -474,7 +433,7 @@ .sb-markdown-top-widget:has(*), .sb-markdown-bottom-widget:has(*) { overflow-y: auto; - border: 1px solid var(--editor-directive-background-color); + border: 1px solid var(--editor-widget-background-color); border-radius: 5px; white-space: normal; position: relative; @@ -542,7 +501,7 @@ right: 0; top: 0; display: none; - background: var(--editor-directive-background-color); + background: var(--editor-widget-background-color); padding-inline: 3px; padding: 4px 0; // border-radius: 0 5px; @@ -573,7 +532,7 @@ max-width: 100%; padding: 0; margin: 0 0 -2ch 0; - border: 1px solid var(--editor-directive-background-color); + border: 1px solid var(--editor-widget-background-color); border-radius: 5px; } } @@ -608,15 +567,4 @@ div:not(.cm-focused).cm-fat-cursor { outline: none !important; -} - - -// @media only screen and (max-width: 600px) { -// #sb-editor { - -// .sb-directive-start-outside, -// .sb-directive-end-outside { -// height: 22px; -// } -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/web/styles/main.scss b/web/styles/main.scss index 5cc16835..f267cb0a 100644 --- a/web/styles/main.scss +++ b/web/styles/main.scss @@ -150,14 +150,14 @@ body { .sb-bottom-iframe { width: 100%; margin-top: 10px; - border: 1px solid var(--editor-directive-background-color); + border: 1px solid var(--editor-widget-background-color); border-radius: 5px; } .sb-top-iframe { width: 100%; margin-top: 10px; - border: 1px solid var(--editor-directive-background-color); + border: 1px solid var(--editor-widget-background-color); border-radius: 5px; } diff --git a/web/styles/theme.scss b/web/styles/theme.scss index d8271b9d..cd44cebe 100644 --- a/web/styles/theme.scss +++ b/web/styles/theme.scss @@ -101,10 +101,7 @@ html { --editor-frontmatter-background-color: rgba(255, 246, 189, 0.5); --editor-frontmatter-color: var(--subtle-color); --editor-frontmatter-marker-color: #89000080; - --editor-directive-background-color: rgb(238, 238, 238); - --editor-directive-border-color: #0000000f; - --editor-directive-color: #5b5b5b; - --editor-directive-info-color: var(--subtle-color); + --editor-widget-background-color: rgb(238, 238, 238); --editor-task-marker-color: var(--subtle-color); --editor-task-state-color: var(--subtle-color); @@ -219,9 +216,6 @@ html[data-theme="dark"] { --editor-frontmatter-background-color: rgb(41, 40, 35, 0.5); --editor-frontmatter-color: var(--subtle-color); --editor-frontmatter-marker-color: #fff; - --editor-directive-background-color: rgba(72, 72, 72, 0.5); - --editor-directive-border-color: #0000000f; - --editor-directive-color: #5b5b5b; - --editor-directive-info-color: var(--subtle-color); + --editor-widget-background-color: rgba(72, 72, 72, 0.5); --editor-task-marker-color: var(--subtle-color); } \ No newline at end of file diff --git a/website/Objects.md b/website/Objects.md index b7c52fdb..54e780bd 100644 --- a/website/Objects.md +++ b/website/Objects.md @@ -128,7 +128,7 @@ _Note_: this is the data source used for the {[Mentions: Toggle]} feature as wel Here is a query that shows all links that appear in this particular page: ```query -link where page = "{{@page.name}}" and inDirective = false +link where page = "{{@page.name}}" ``` ## anchor diff --git a/website/Plugs/Tasks.md b/website/Plugs/Tasks.md index e86a59d7..bf75a1c4 100644 --- a/website/Plugs/Tasks.md +++ b/website/Plugs/Tasks.md @@ -44,7 +44,7 @@ task where page = "{{@page.name}}" ## Rendering There is a [[!silverbullet.md/template/tasks/task]] template you can use to render tasks nicely rather than using the default table (as demonstrated above). When you use this template, you can even cycle through the states of the task by click on its state _inside_ the rendered query, and it will update the state of the _original_ task automatically (although not yet in reverse) — this works across pages. -Try it (by clicking on the checkbox inside of the directive): +Try it (by clicking on the checkbox inside of the query): ```query task where page = "{{@page.name}}" and name = "Remote toggle me" render [[template/task]]