From 61b675e3ff9810e83de799714698f2094954fbc2 Mon Sep 17 00:00:00 2001 From: salmonumbrella <182032677+salmonumbrella@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:29:06 -0800 Subject: [PATCH 1/2] fix: support SmartBlock button resolution inside Roam tables (#118) --- src/index.ts | 83 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/src/index.ts b/src/index.ts index fd50bac..6e59b5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,6 +38,7 @@ import XRegExp from "xregexp"; import { PullBlock } from "roamjs-components/types"; import getParentUidByBlockUid from "roamjs-components/queries/getParentUidByBlockUid"; import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid"; +import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; import extractRef from "roamjs-components/util/extractRef"; import getDailyConfig from "./utils/getDailyConfig"; import saveDailyConfig from "./utils/saveDailyConfig"; @@ -597,6 +598,54 @@ export default runExtension(async ({ extensionAPI }) => { }, }); + const ROAM_TABLE_REGEX = /\{\{\[\[table\]\]\}\}/i; + type TableNode = { + uid: string; + children?: TableNode[]; + }; + + const getRoamTableUidRows = (nodes: TableNode[] = []): string[][] => + nodes.flatMap((node) => { + if (!node?.uid) return []; + const children = (node.children || []).filter( + (c): c is TableNode => !!c?.uid + ); + if (!children.length) return [[node.uid]]; + const childRows = getRoamTableUidRows(children); + return childRows.length + ? childRows.map((row) => [node.uid, ...row]) + : [[node.uid]]; + }); + + const resolveRoamTableButtonBlock = ({ + el, + tableUid, + }: { + el: HTMLElement; + tableUid: string; + }): { uid: string; text: string } | null => { + const tableEl = el.closest("table"); + const rowEl = el.closest("tr"); + const cellEl = el.closest("td"); + if (!tableEl || !rowEl || !cellEl) return null; + + const tableRows = Array.from(tableEl.querySelectorAll("tr")).filter( + (row) => row.querySelectorAll("td").length > 0 + ); + const rowIndex = tableRows.indexOf(rowEl); + const cells = Array.from(rowEl.querySelectorAll("td")); + const columnIndex = cells.indexOf(cellEl); + if (rowIndex < 0 || columnIndex < 0) return null; + + const uidRows = getRoamTableUidRows( + getBasicTreeByParentUid(tableUid) as unknown as TableNode[] + ); + const uid = uidRows[rowIndex]?.[columnIndex]; + if (!uid) return null; + + return { uid, text: getTextByBlockUid(uid) }; + }; + const registerElAsSmartBlockTrigger = ({ textContent, text, @@ -612,7 +661,13 @@ export default runExtension(async ({ extensionAPI }) => { }) => { // We include textcontent here bc there could be multiple smartblocks in a block const label = textContent.trim(); - const parsed = parseSmartBlockButton(label, text); + const tableResolution = + ROAM_TABLE_REGEX.test(text) && !!el.closest("table") + ? resolveRoamTableButtonBlock({ el, tableUid: parentUid }) + : null; + const resolvedParentUid = tableResolution?.uid || parentUid; + const resolvedText = tableResolution?.text || text; + const parsed = parseSmartBlockButton(label, resolvedText); if (parsed) { const { index, full, buttonContent, workflowName, variables } = parsed; const clickListener = () => { @@ -627,7 +682,7 @@ export default runExtension(async ({ extensionAPI }) => { text: "Could not find custom workflow with the name:", children: [{ text: workflowName }], }, - parentUid, + parentUid: resolvedParentUid, }); } else { const keepButton = @@ -651,29 +706,29 @@ export default runExtension(async ({ extensionAPI }) => { mutableCursor: !( workflows.find((w) => w.uid === srcUid)?.name || "" ).includes("<%NOCURSOR%>"), - triggerUid: parentUid, + triggerUid: resolvedParentUid, }; if (applyToSibling) { const sbParentTree = getShallowTreeByParentUid( - getParentUidByBlockUid(parentUid) + getParentUidByBlockUid(resolvedParentUid) ); const siblingIndex = - sbParentTree.findIndex((obj) => obj.uid === parentUid) + + sbParentTree.findIndex((obj) => obj.uid === resolvedParentUid) + (applyToSibling === "previous" ? -1 : 1); const siblingUid = sbParentTree[siblingIndex]?.uid; const siblingText = getTextByBlockUid(siblingUid); updateBlock({ - uid: parentUid, + uid: resolvedParentUid, text: clearBlock && keepButton ? full : clearBlock ? "" : keepButton - ? text - : `${text.substring(0, index)}${text.substring( + ? resolvedText + : `${resolvedText.substring(0, index)}${resolvedText.substring( index + full.length )}`, }); @@ -692,7 +747,7 @@ export default runExtension(async ({ extensionAPI }) => { ) : createBlock({ node: { text: "" }, - parentUid: getParentUidByBlockUid(parentUid), + parentUid: getParentUidByBlockUid(resolvedParentUid), order: siblingIndex === -1 ? 0 : siblingIndex, }).then((targetUid) => sbBomb({ @@ -717,7 +772,7 @@ export default runExtension(async ({ extensionAPI }) => { }) : createBlock({ node: { text: "" }, - parentUid, + parentUid: resolvedParentUid, order, }).then((targetUid) => sbBomb({ @@ -733,23 +788,23 @@ export default runExtension(async ({ extensionAPI }) => { ), clearBlock ? updateBlock({ - uid: parentUid, + uid: resolvedParentUid, text: full, }) : ""; } else { updateBlock({ - uid: parentUid, + uid: resolvedParentUid, text: clearBlock ? "" - : `${text.substring(0, index)}${text.substring( + : `${resolvedText.substring(0, index)}${resolvedText.substring( index + full.length )}`, }).then(() => sbBomb({ ...props, target: { - uid: parentUid, + uid: resolvedParentUid, start: index, end: index, }, From 5e6cea513111a3067ef1eeac6860beea87d936c0 Mon Sep 17 00:00:00 2001 From: salmonumbrella <182032677+salmonumbrella@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:32:11 -0800 Subject: [PATCH 2/2] fix: correctly map table row/column to cell uid for SmartBlock buttons (#118) --- src/index.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6e59b5e..c21f42c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -605,17 +605,13 @@ export default runExtension(async ({ extensionAPI }) => { }; const getRoamTableUidRows = (nodes: TableNode[] = []): string[][] => - nodes.flatMap((node) => { - if (!node?.uid) return []; - const children = (node.children || []).filter( - (c): c is TableNode => !!c?.uid + nodes + .filter((node): node is TableNode => !!node?.uid) + .map((rowNode) => + (rowNode.children || []) + .filter((cell): cell is TableNode => !!cell?.uid) + .map((cell) => cell.uid) ); - if (!children.length) return [[node.uid]]; - const childRows = getRoamTableUidRows(children); - return childRows.length - ? childRows.map((row) => [node.uid, ...row]) - : [[node.uid]]; - }); const resolveRoamTableButtonBlock = ({ el,