From 7f63c52588fd8b00127d4b46dbb6c118057e2954 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Sat, 18 Oct 2025 21:35:00 -0600 Subject: [PATCH 1/3] feat: enhance command palette integration for SmartBlocks - Added support for a new <%CMD%> tag to opt specific workflows into the Roam command palette. - Updated documentation to reflect the new command palette opt-in feature and usage instructions. - Implemented functionality to refresh command palette workflows after changes to the <%CMD%> tag. - Refactored command palette command management in the code for better clarity and performance. --- docs/050-command-reference.md | 8 ++ docs/060-alternative-methods.md | 6 +- src/index.ts | 147 +++++++++++++++++++++----------- src/utils/core.ts | 20 +++-- 4 files changed, 127 insertions(+), 54 deletions(-) diff --git a/docs/050-command-reference.md b/docs/050-command-reference.md index 7ee2429..dde97ff 100644 --- a/docs/050-command-reference.md +++ b/docs/050-command-reference.md @@ -1357,6 +1357,14 @@ Add any of these commands to the workflow name to modify the workflow's entire b - `#SmartBlock NameOfSmartBlock <%HIDE%>` +## CMD + +**Purpose:** Opt the workflow into appearing in Roam's command palette when the Command Palette setting requires it + +**Usage** + +- `#SmartBlock NameOfSmartBlock <%CMD%>` + ## NOCURSOR **Purpose:** After a SmartBlock has finished running, Roam should be left in a non-edit state, meaning no block currently has editing focus diff --git a/docs/060-alternative-methods.md b/docs/060-alternative-methods.md index d833ce3..0cabfa9 100644 --- a/docs/060-alternative-methods.md +++ b/docs/060-alternative-methods.md @@ -156,8 +156,12 @@ The input field in the configuration page is a special input field. It will reac # Command Palette -You can surface all custom SmartBlock workflows to be triggered from the Roam Command Palette. To do so, head to the SmartBlocks Settings within RoamDepot, toggle on the command palette setting. +You can surface all custom SmartBlock workflows to be triggered from the Roam Command Palette. To do so, head to the SmartBlocks Settings within RoamDepot and toggle on the command palette setting. If you only want specific workflows to show up, enable the "Command Palette Opt-In" setting and add `<%CMD%>` to the workflow title you wish to expose. ![](media/command-palette.png) Each workflow will start with a prefix `Trigger SmartBlock:` with the name of the workflow at the end of the command label. + +## Refreshing Command Palette Workflows + +After adding or removing the `<%CMD%>` tag from workflow titles (when Command Palette Opt-In is enabled), you may need to refresh the list of available workflows in the command palette. To do this, open the Roam Command Palette and search for "Refresh SmartBlocks Command Palette". Running this command will update the command palette with your current workflows and display a confirmation message. diff --git a/src/index.ts b/src/index.ts index 00dfc37..c2a8ef0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -115,57 +115,76 @@ export default runExtension(async ({ extensionAPI }) => { color: #4b5563; }`); - const toggleCommandPalette = (flag: boolean) => { - const workflows = getCleanCustomWorkflows(getVisibleCustomWorkflows()); - if (flag) { - workflows.forEach((wf) => { - window.roamAlphaAPI.ui.commandPalette.addCommand({ - label: `Trigger SmartBlock: ${wf.name}`, - callback: () => { - const targetUid = - window.roamAlphaAPI.ui.getFocusedBlock()?.["block-uid"]; - // Because the command palette does a blur event on close, - // we want a slight delay so that we could keep focus - window.setTimeout(() => { - if (targetUid) { - sbBomb({ - srcUid: wf.uid, - target: { - uid: targetUid, - isParent: false, - start: getTextByBlockUid(targetUid).length, - }, - mutableCursor: true, - }); - } else { - window.roamAlphaAPI.ui.mainWindow - .getOpenPageOrBlockUid() - .then((uid) => - sbBomb({ - srcUid: wf.uid, - target: { - uid: - uid || - window.roamAlphaAPI.util.dateToPageUid(new Date()), - isParent: true, - }, - mutableCursor: true, - }) - ); - } - }, 500); - }, - }); + let commandPaletteEnabled = false; + let commandPaletteOptIn = !!extensionAPI.settings.get( + "command-palette-opt-in" + ); + + const removeCommandPaletteCommands = () => { + getCleanCustomWorkflows(getVisibleCustomWorkflows()).forEach((wf) => { + window.roamAlphaAPI.ui.commandPalette.removeCommand({ + label: `Trigger SmartBlock: ${wf.name}`, }); - } else { - workflows.forEach((wf) => { - window.roamAlphaAPI.ui.commandPalette.removeCommand({ - label: `Trigger SmartBlock: ${wf.name}`, - }); + }); + }; + + const addCommandPaletteCommands = () => { + const eligibleWorkflows = getVisibleCustomWorkflows().filter((wf) => + commandPaletteOptIn ? wf.commandPaletteEligible : true + ); + getCleanCustomWorkflows(eligibleWorkflows).forEach((wf) => { + window.roamAlphaAPI.ui.commandPalette.addCommand({ + label: `Trigger SmartBlock: ${wf.name}`, + callback: () => { + const targetUid = + window.roamAlphaAPI.ui.getFocusedBlock()?.["block-uid"]; + // Because the command palette does a blur event on close, + // we want a slight delay so that we could keep focus + window.setTimeout(() => { + if (targetUid) { + sbBomb({ + srcUid: wf.uid, + target: { + uid: targetUid, + isParent: false, + start: getTextByBlockUid(targetUid).length, + }, + mutableCursor: true, + }); + } else { + window.roamAlphaAPI.ui.mainWindow + .getOpenPageOrBlockUid() + .then((uid) => + sbBomb({ + srcUid: wf.uid, + target: { + uid: + uid || + window.roamAlphaAPI.util.dateToPageUid(new Date()), + isParent: true, + }, + mutableCursor: true, + }) + ); + } + }, 500); + }, }); + }); + }; + + const syncCommandPaletteCommands = () => { + removeCommandPaletteCommands(); + if (commandPaletteEnabled) { + addCommandPaletteCommands(); } }; + const toggleCommandPalette = (flag: boolean) => { + commandPaletteEnabled = flag; + syncCommandPaletteCommands(); + }; + let trigger = "jj"; let triggerRegex = /$^/; const refreshTrigger = (value: string) => { @@ -195,6 +214,19 @@ export default runExtension(async ({ extensionAPI }) => { toggleCommandPalette((e.target as HTMLInputElement).checked), }, }, + { + id: "command-palette-opt-in", + name: "Command Palette Opt-In", + description: + "If enabled, workflows must include <%CMD%> in their title to appear in the command palette", + action: { + type: "switch", + onChange: (e) => { + commandPaletteOptIn = (e.target as HTMLInputElement).checked; + syncCommandPaletteCommands(); + }, + }, + }, { action: { type: "input", @@ -258,7 +290,8 @@ export default runExtension(async ({ extensionAPI }) => { }, ], }); - toggleCommandPalette(!!extensionAPI.settings.get("command-palette")); + commandPaletteEnabled = !!extensionAPI.settings.get("command-palette"); + syncCommandPaletteCommands(); refreshTrigger(extensionAPI.settings.get("trigger") as string); const customCommands: { text: string; help: string }[] = []; @@ -514,6 +547,21 @@ export default runExtension(async ({ extensionAPI }) => { }, }); + const REFRESH_SMARTBLOCKS_COMMAND_LABEL = + "Refresh SmartBlocks Command Palette"; + window.roamAlphaAPI.ui.commandPalette.addCommand({ + label: REFRESH_SMARTBLOCKS_COMMAND_LABEL, + callback: () => { + syncCommandPaletteCommands(); + renderToast({ + id: "smartblocks-command-palette-refresh", + intent: Intent.SUCCESS, + content: "Command palette workflows refreshed", + timeout: 2000, + }); + }, + }); + const logoObserver = createHTMLObserver({ className: "rm-page-ref--tag", tag: "SPAN", @@ -891,7 +939,10 @@ export default runExtension(async ({ extensionAPI }) => { { type: "input", listener: documentInputListener, el: document }, { type: "keydown", listener: globalHotkeyListener, el: document }, ], - commands: [RUN_MULTIPLE_SMARTBLOCKS_COMMAND_LABEL], + commands: [ + RUN_MULTIPLE_SMARTBLOCKS_COMMAND_LABEL, + REFRESH_SMARTBLOCKS_COMMAND_LABEL, + ], unload: () => { unloads.forEach((u) => u()); window.clearTimeout(getDailyConfig()["next-run-timeout"]); diff --git a/src/utils/core.ts b/src/utils/core.ts index 4f40846..bf2f282 100644 --- a/src/utils/core.ts +++ b/src/utils/core.ts @@ -293,6 +293,9 @@ const predefinedChildrenByUid = Object.fromEntries( ); export const HIDE_REGEX = /<%HIDE%>/i; +export const COMMAND_PALETTE_REGEX = /<%CMD%>/i; +const HIDE_REGEX_GLOBAL = /<%HIDE%>/gi; +const COMMAND_PALETTE_REGEX_GLOBAL = /<%CMD%>/gi; const customWorkflowsCache: { current?: { uid: string; name: string }[]; @@ -338,15 +341,22 @@ export const getCustomWorkflows = () => { export const getVisibleCustomWorkflows = () => getCustomWorkflows() .filter(({ name }) => !HIDE_REGEX.test(name)) - .map(({ name, uid }) => ({ - uid, - name: name.replace(HIDE_REGEX, ""), - })); + .map(({ name, uid }) => { + const commandPaletteEligible = COMMAND_PALETTE_REGEX.test(name); + return { + uid, + name: name + .replace(HIDE_REGEX_GLOBAL, "") + .replace(COMMAND_PALETTE_REGEX_GLOBAL, "") + .trim(), + commandPaletteEligible, + }; + }); export const getCleanCustomWorkflows = (workflows = getCustomWorkflows()) => workflows.map(({ name, uid }) => ({ uid, - name: name.replace(/<%[A-Z]+%>/, "").trim(), + name: name.replace(/<%[A-Z]+%>/g, "").trim(), })); const getFormatter = From f3cff3a8f5493314c2cc79a5e169c2058c19b0fe Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Sat, 18 Oct 2025 21:51:16 -0600 Subject: [PATCH 2/3] Updated the command options to include the new CMD modifier for enhanced user experience. --- src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/index.ts b/src/index.ts index c2a8ef0..b2f0c3a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -430,6 +430,11 @@ export default runExtension(async ({ extensionAPI }) => { id: "HIDE", help: "Workflow modifier that hides this workflow from the standard SmartBlock menu execution", }, + { + text: "CMD", + id: "CMD", + help: "Workflow modifier that opts this workflow into appearing in the command palette when Command Palette Opt-In is enabled", + }, ...customCommands.map(({ text, help }) => ({ text, id: text, From 0831732913f8bc6b7e49300780b517410f42fdf8 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Sat, 18 Oct 2025 21:51:28 -0600 Subject: [PATCH 3/3] 1.12.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 780a96d..ab83257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "smartblocks", - "version": "1.11.0", + "version": "1.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "smartblocks", - "version": "1.11.0", + "version": "1.12.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index dcb5305..228c2a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smartblocks", - "version": "1.11.0", + "version": "1.12.0", "description": "Create custom and programmable templates from within Roam!", "main": "./build/main.js", "scripts": {