diff --git a/common/languages.ts b/common/languages.ts index d2dca44..e8af865 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 bafe2ef..0e7f21e 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 f7da9a8..4c350d9 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 3d9d22d..e7df9fc 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 2340015..e8678ed 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 e7f2036..8301f44 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 d8185d9..731f040 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 194195f..0000000 --- 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 33a1345..d770732 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 f79cb61..509c229 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 84c2ff1..dccb863 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 60f78f1..21e679a 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 8e8a832..0000000 --- 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 b382077..0000000 --- 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 ecccd87..0000000 --- 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 2ad999d..0000000 --- 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 51bd2b9..0000000 --- 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 a0beea6..0000000 --- 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 600c5e6..0000000 --- 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 c3aa640..0000000 --- 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 260a1a6..aebbdc9 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 0254feb..78c6785 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 8a291ba..348da73 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 820893f..3ece21b 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 c3a09e8..ab9e647 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 cfe9205..cb418c8 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 ee85cf1..f6c52fb 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 4324241..0767ad5 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 74337a3..311523e 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 6626e40..40e010f 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 abab814..a3feebb 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 cef4e0b..15b641a 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 83db5a6..8d4dffe 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 efdf79b..94590fa 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 32b8bef..77b4f8f 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 8adec64..4e425e7 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 4ca2f6c..40bdb7a 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 a619cc8..0000000 --- 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 0c1a609..0a1ac2b 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 396d93c..edcfe38 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 afb08d5..0d7fa84 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 519a0b0..f815f17 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 5cc1683..f267cb0 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 d8271b9..cd44ceb 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 b7c52fd..54e780b 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 e86a59d..bf75a1c 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]]