diff --git a/packages/common/customtags.ts b/packages/common/customtags.ts index 4befe11..75eb726 100644 --- a/packages/common/customtags.ts +++ b/packages/common/customtags.ts @@ -9,3 +9,4 @@ export const CommentTag = Tag.define(); export const CommentMarkerTag = Tag.define(); export const BulletList = Tag.define(); export const OrderedList = Tag.define(); +export const Highlight = Tag.define(); diff --git a/packages/common/parser.ts b/packages/common/parser.ts index ccc22fc..3af1209 100644 --- a/packages/common/parser.ts +++ b/packages/common/parser.ts @@ -53,6 +53,31 @@ const WikiLink: MarkdownConfig = { ], }; +const HighlightDelim = { resolve: "Highlight", mark: "HighlightMark" }; + +export const Strikethrough: MarkdownConfig = { + defineNodes: [ + { + name: "Highlight", + style: { "Highlight/...": ct.Highlight }, + }, + { + name: "HighlightMark", + style: t.processingInstruction, + }, + ], + parseInline: [ + { + name: "Highlight", + parse(cx, next, pos) { + if (next != 61 /* '=' */ || cx.char(pos + 1) != 61) return -1; + return cx.addDelimiter(HighlightDelim, pos, pos + 2, true, true); + }, + after: "Emphasis", + }, + ], +}; + class CommentParser implements LeafBlockParser { nextLine() { return false; @@ -88,6 +113,7 @@ export default function buildMarkdown(mdExtensions: MDExt[]): Language { WikiLink, TaskList, Comment, + Strikethrough, Table, ...mdExtensions.map(mdExtensionSyntaxConfig), // parseCode({ diff --git a/packages/plugos-syscall/event.ts b/packages/plugos-syscall/event.ts index 513bec5..592e628 100644 --- a/packages/plugos-syscall/event.ts +++ b/packages/plugos-syscall/event.ts @@ -23,3 +23,7 @@ export async function dispatch( .catch(reject); }); } + +export async function listEvents(): Promise { + return syscall("event.list"); +} diff --git a/packages/plugos/hooks/event.ts b/packages/plugos/hooks/event.ts index 2b65309..c362a8e 100644 --- a/packages/plugos/hooks/event.ts +++ b/packages/plugos/hooks/event.ts @@ -21,6 +21,30 @@ export class EventHook implements Hook { this.localListeners.get(eventName)!.push(callback); } + // Pull all events listened to + listEvents(): string[] { + if (!this.system) { + throw new Error("Event hook is not initialized"); + } + let eventNames = new Set(); + for (const plug of this.system.loadedPlugs.values()) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions + )) { + if (functionDef.events) { + for (let eventName of functionDef.events) { + eventNames.add(eventName); + } + } + } + } + for (let eventName of this.localListeners.keys()) { + eventNames.add(eventName); + } + + return [...eventNames]; + } + async dispatchEvent(eventName: string, data?: any): Promise { if (!this.system) { throw new Error("Event hook is not initialized"); diff --git a/packages/plugos/syscalls/event.ts b/packages/plugos/syscalls/event.ts index b109af9..5661df9 100644 --- a/packages/plugos/syscalls/event.ts +++ b/packages/plugos/syscalls/event.ts @@ -6,5 +6,8 @@ export function eventSyscalls(eventHook: EventHook): SysCallMapping { "event.dispatch": async (ctx, eventName: string, data: any) => { return eventHook.dispatchEvent(eventName, data); }, + "event.list": async () => { + return eventHook.listEvents(); + }, }; } diff --git a/packages/plugs/core/core.plug.yaml b/packages/plugs/core/core.plug.yaml index 35fe568..dcf6137 100644 --- a/packages/plugs/core/core.plug.yaml +++ b/packages/plugs/core/core.plug.yaml @@ -123,34 +123,63 @@ functions: # Template commands insertPageMeta: - path: "./page.ts:insertPageMeta" + path: "./template.ts:insertTemplateText" slashCommand: name: meta + value: | + ```meta + |^| + ``` + insertTask: + path: "./template.ts:insertTemplateText" + slashCommand: + name: task + value: "* [ ] |^|" + insertQuery: + path: "./template.ts:insertTemplateText" + slashCommand: + name: query + value: | + + + + insertTaskToday: + path: "./template.ts:insertTemplateText" + slashCommand: + name: task-today + value: "* [ ] |^| 📅 {{today}}" quickNoteCommand: path: ./template.ts:quickNoteCommand command: name: "Template: Quick Note" key: "Alt-Shift-n" - quickTaskCommand: - path: ./template.ts:quickTaskCommand - command: - name: "Template: Quick Task" - key: "Alt-Shift-t" + instantiateTemplateCommand: path: ./template.ts:instantiateTemplateCommand command: - name: "Template: Instantiate for Page" - insertToday: - path: "./dates.ts:insertToday" + name: "Template: Instantiate Page" + insertSnippet: + path: ./template.ts:insertSnippet + command: + name: "Template: Insert Snippet" + slashCommand: + name: snippet + description: Insert a snippet + insertTodayCommand: + path: "./template.ts:insertTemplateText" slashCommand: name: today - insertTomorrow: - path: "./dates.ts:insertTomorrow" + description: Insert today's date + value: "{{today}}" + insertTomorrowCommand: + path: "./template.ts:insertTemplateText" slashCommand: name: tomorrow + description: Insert tomorrow's date + value: "{{tomorrow}}" # Text editing commands - quoteSelection: + quoteSelectionCommand: path: ./text.ts:quoteSelection command: name: "Text: Quote Selection" @@ -165,17 +194,25 @@ functions: command: name: "Text: Number Listify Selection" bold: - path: ./text.ts:boldCommand + path: ./text.ts:wrapSelection command: name: "Text: Bold" key: "Ctrl-b" mac: "Cmd-b" + wrapper: "**" italic: - path: ./text.ts:italicCommand + path: ./text.ts:wrapSelection command: name: "Text: Italic" key: "Ctrl-i" mac: "Cmd-i" + wrapper: "_" + marker: + path: ./text.ts:wrapSelection + command: + name: "Text: Marker" + key: "Alt-m" + wrapper: "==" # Plug manager updatePlugsCommand: diff --git a/packages/plugs/core/dates.ts b/packages/plugs/core/dates.ts index afa8832..dea5090 100644 --- a/packages/plugs/core/dates.ts +++ b/packages/plugs/core/dates.ts @@ -1,15 +1,3 @@ -import { insertAtCursor } from "@silverbulletmd/plugos-silverbullet-syscall/editor"; - export function niceDate(d: Date): string { return d.toISOString().split("T")[0]; } - -export async function insertToday() { - await insertAtCursor(niceDate(new Date())); -} - -export async function insertTomorrow() { - let d = new Date(); - d.setDate(d.getDate() + 1); - await insertAtCursor(niceDate(d)); -} diff --git a/packages/plugs/core/page.ts b/packages/plugs/core/page.ts index f68f142..6b8df18 100644 --- a/packages/plugs/core/page.ts +++ b/packages/plugs/core/page.ts @@ -236,9 +236,3 @@ export async function parseIndexTextRepublish({ name, text }: IndexEvent) { tree: await parseMarkdown(text), }); } - -export async function insertPageMeta() { - let cursorPos = await getCursor(); - await insertAtCursor("```meta\n\n```"); - await moveCursor(cursorPos + 8); -} diff --git a/packages/plugs/core/template.ts b/packages/plugs/core/template.ts index 19ab0fc..cf1fe10 100644 --- a/packages/plugs/core/template.ts +++ b/packages/plugs/core/template.ts @@ -5,6 +5,9 @@ import { } from "@silverbulletmd/plugos-silverbullet-syscall/space"; import { filterBox, + getCurrentPage, + getCursor, + insertAtCursor, moveCursor, navigate, prompt, @@ -15,6 +18,7 @@ import { renderToText } from "@silverbulletmd/common/tree"; import { niceDate } from "./dates"; const pageTemplatePrefix = `template/page/`; +const snippetPrefix = `snippet/`; export async function instantiateTemplateCommand() { let allPages = await listPages(); @@ -48,12 +52,45 @@ export async function instantiateTemplateCommand() { await navigate(pageName); } +export async function insertSnippet() { + let allPages = await listPages(); + let cursorPos = await getCursor(); + let page = await getCurrentPage(); + let allSnippets = allPages.filter((pageMeta) => + pageMeta.name.startsWith(snippetPrefix) + ); + + let selectedSnippet = await filterBox( + "Snippet", + allSnippets, + `Select the snippet to insert (listing any page starting with ${snippetPrefix})` + ); + + if (!selectedSnippet) { + return; + } + let { text } = await readPage(selectedSnippet.name); + + let templateText = replaceTemplateVars(text, page); + let carretPos = templateText.indexOf("|^|"); + templateText = templateText.replace("|^|", ""); + templateText = replaceTemplateVars(templateText, page); + await insertAtCursor(templateText); + if (carretPos !== -1) { + await moveCursor(cursorPos + carretPos); + } +} + // TODO: This should probably be replaced with handlebards somehow? export function replaceTemplateVars(s: string, pageName: string): string { return s.replaceAll(/\{\{([^\}]+)\}\}/g, (match, v) => { switch (v) { case "today": return niceDate(new Date()); + case "tomorrow": + let tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + return niceDate(tomorrow); case "yesterday": let yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); @@ -62,6 +99,8 @@ export function replaceTemplateVars(s: string, pageName: string): string { let lastWeek = new Date(); lastWeek.setDate(lastWeek.getDate() - 7); return niceDate(lastWeek); + case "page": + return pageName; } return match; }); @@ -75,12 +114,15 @@ export async function quickNoteCommand() { await navigate(pageName); } -export async function quickTaskCommand() { - let isoDate = new Date().toISOString(); - let [date, time] = isoDate.split("T"); - time = time.split(".")[0]; - let pageName = `✅ ${date} ${time}`; - await writePage(pageName, "* [ ] "); - await navigate(pageName); - await moveCursor(6); +export async function insertTemplateText(cmdDef: any) { + let cursorPos = await getCursor(); + let page = await getCurrentPage(); + let templateText: string = cmdDef.value; + let carretPos = templateText.indexOf("|^|"); + templateText = templateText.replace("|^|", ""); + templateText = replaceTemplateVars(templateText, page); + await insertAtCursor(templateText); + if (carretPos !== -1) { + await moveCursor(cursorPos + carretPos); + } } diff --git a/packages/plugs/core/text.ts b/packages/plugs/core/text.ts index 73c2a55..3061cc3 100644 --- a/packages/plugs/core/text.ts +++ b/packages/plugs/core/text.ts @@ -56,12 +56,8 @@ export async function numberListifySelection() { await replaceRange(from, selection.to, text); } -export function boldCommand() { - return insertMarker("**"); -} - -export function italicCommand() { - return insertMarker("_"); +export function wrapSelection(cmdDef: any) { + return insertMarker(cmdDef.wrapper); } async function insertMarker(marker: string) { diff --git a/packages/plugs/query/command.ts b/packages/plugs/query/command.ts deleted file mode 100644 index 51b24e1..0000000 --- a/packages/plugs/query/command.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - getCursor, - insertAtCursor, - moveCursor, -} from "@silverbulletmd/plugos-silverbullet-syscall/editor"; - -export async function insertQuery() { - let cursorPos = await getCursor(); - await insertAtCursor(`\n\n`); - await moveCursor(cursorPos + 12); -} diff --git a/packages/plugs/query/complete.ts b/packages/plugs/query/complete.ts new file mode 100644 index 0000000..0c58b92 --- /dev/null +++ b/packages/plugs/query/complete.ts @@ -0,0 +1,32 @@ +import { listEvents } from "@plugos/plugos-syscall/event"; +import { matchBefore } from "@silverbulletmd/plugos-silverbullet-syscall/editor"; +import { listPages } from "@silverbulletmd/plugos-silverbullet-syscall/space"; + +export async function queryComplete() { + let prefix = await matchBefore("#query [\\w\\-_]*"); + + if (prefix) { + let allEvents = await listEvents(); + // console.log("All events", allEvents); + + return { + from: prefix.from + "#query ".length, + options: allEvents + .filter((eventName) => eventName.startsWith("query:")) + .map((source) => ({ + label: source.substring("query:".length), + })), + }; + } + + prefix = await matchBefore('render "[^"]*'); + if (prefix) { + let allPages = await listPages(); + return { + from: prefix.from + 'render "'.length, + options: allPages.map((pageMeta) => ({ + label: pageMeta.name, + })), + }; + } +} diff --git a/packages/plugs/query/materialized_queries.ts b/packages/plugs/query/materialized_queries.ts index fce944d..39b63ef 100644 --- a/packages/plugs/query/materialized_queries.ts +++ b/packages/plugs/query/materialized_queries.ts @@ -19,7 +19,6 @@ import { replaceAsync } from "../lib/util"; export async function updateMaterializedQueriesCommand() { const currentPage = await getCurrentPage(); await save(); - // await flashNotification("Updating materialized queries..."); if ( await invokeFunction( "server", @@ -67,8 +66,17 @@ async function updateTemplateInstantiations( export async function updateMaterializedQueriesOnPage( pageName: string ): Promise { - let { text } = await readPage(pageName); - + let text = ""; + try { + text = (await readPage(pageName)).text; + } catch { + console.warn( + "Could not read page", + pageName, + "perhaps it doesn't yet exist" + ); + return false; + } let newText = await updateTemplateInstantiations(text, pageName); newText = await replaceAsync( newText, diff --git a/packages/plugs/query/query.plug.yaml b/packages/plugs/query/query.plug.yaml index 256359b..f2d6252 100644 --- a/packages/plugs/query/query.plug.yaml +++ b/packages/plugs/query/query.plug.yaml @@ -17,7 +17,7 @@ functions: path: ./data.ts:queryProvider events: - query:data - insertQueryCommand: - path: ./command.ts:insertQuery - slashCommand: - name: query + queryComplete: + path: ./complete.ts:queryComplete + events: + - page:complete diff --git a/packages/web/editor.tsx b/packages/web/editor.tsx index fd15c26..b974546 100644 --- a/packages/web/editor.tsx +++ b/packages/web/editor.tsx @@ -59,6 +59,7 @@ import { FilterList } from "./components/filter"; import { FilterOption, PageMeta } from "@silverbulletmd/common/types"; import { syntaxTree } from "@codemirror/language"; import sandboxSyscalls from "@plugos/plugos/syscalls/sandbox"; +import { eventSyscalls } from "@plugos/plugos/syscalls/event"; // import globalModules from "../common/dist/global.plug.json"; class PageState { @@ -148,6 +149,7 @@ export class Editor { this.system.registerSyscalls( [], + eventSyscalls(this.eventHook), editorSyscalls(this), spaceSyscalls(this), indexerSyscalls(this.space), @@ -630,9 +632,15 @@ export class Editor { editor.focus(); if (cmd) { dispatch({ type: "command-run", command: cmd.command.name }); - cmd.run().catch((e) => { - console.error("Error running command", e.message); - }); + cmd + .run() + .catch((e) => { + console.error("Error running command", e.message); + }) + .then(() => { + // Always be focusing the editor after running a command + editor.focus(); + }); } }} commands={viewState.commands} diff --git a/packages/web/hooks/slash_command.ts b/packages/web/hooks/slash_command.ts index 2cc1a22..71a9e5a 100644 --- a/packages/web/hooks/slash_command.ts +++ b/packages/web/hooks/slash_command.ts @@ -8,9 +8,11 @@ import { import { slashCommandRegexp } from "../types"; import { safeRun } from "../../common/util"; import { Editor } from "../editor"; +import { syntaxTree } from "@codemirror/language"; export type SlashCommandDef = { name: string; + description?: string; }; export type AppSlashCommand = { @@ -43,7 +45,7 @@ export class SlashCommandHook implements Hook { this.slashCommands.set(cmd.name, { slashCommand: cmd, run: () => { - return plug.invoke(name, []); + return plug.invoke(name, [cmd]); }, }); } @@ -59,10 +61,16 @@ export class SlashCommandHook implements Hook { return null; } let options: Completion[] = []; + + // No slash commands in comment blocks (queries and such) + let currentNode = syntaxTree(ctx.state).resolveInner(ctx.pos); + if (currentNode.type.name === "CommentBlock") { + return null; + } for (let [name, def] of this.slashCommands.entries()) { options.push({ label: def.slashCommand.name, - detail: name, + detail: def.slashCommand.description, apply: () => { // Delete slash command part this.editor.editorView?.dispatch({ @@ -75,6 +83,7 @@ export class SlashCommandHook implements Hook { // Replace with whatever the completion is safeRun(async () => { await def.run(); + this.editor.focus(); }); }, }); diff --git a/packages/web/style.ts b/packages/web/style.ts index 828c418..348dab9 100644 --- a/packages/web/style.ts +++ b/packages/web/style.ts @@ -20,6 +20,7 @@ export default function highlightStyles(mdExtension: MDExt[]) { { tag: ct.CodeInfoTag, class: "code-info" }, { tag: ct.CommentTag, class: "comment" }, { tag: ct.CommentMarkerTag, class: "comment-marker" }, + { tag: ct.Highlight, class: "highlight" }, { tag: t.emphasis, class: "emphasis" }, { tag: t.strong, class: "strong" }, { tag: t.atom, class: "atom" }, diff --git a/packages/web/styles/editor.scss b/packages/web/styles/editor.scss index d344ede..4638aaa 100644 --- a/packages/web/styles/editor.scss +++ b/packages/web/styles/editor.scss @@ -81,6 +81,10 @@ background-color: rgba(72, 72, 72, 0.1); } + .highlight { + background-color: rgba(255, 255, 0, 0.5); + } + .line-fenced-code { background-color: rgba(72, 72, 72, 0.1);