From fe791e50374f417ef7e12778a75498ebda9e1562 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 16 Apr 2025 22:47:51 +0530 Subject: [PATCH 1/7] its working but can't reason about it --- apps/roam/src/components/DiscourseNodeMenu.tsx | 5 ++++- apps/roam/src/utils/formatUtils.ts | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeMenu.tsx b/apps/roam/src/components/DiscourseNodeMenu.tsx index 2ff83744b..6ac0c9435 100644 --- a/apps/roam/src/components/DiscourseNodeMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeMenu.tsx @@ -150,7 +150,10 @@ const NodeMenu = ({ text={item.text} active={i === activeIndex} onMouseEnter={() => setActiveIndex(i)} - onClick={() => onSelect(i)} + onClick={(e) => { + e.stopPropagation(); + onSelect(i); + }} className="flex items-center" icon={
[0]; const renderFormDialog = createOverlayRender( @@ -42,7 +43,12 @@ export const getNewDiscourseNodeText = async ({ label: `Create ${nodeName} Node`, }, }, - onSubmit: (data: Record) => { + onSubmit: async (data: Record) => { + console.log("data.textField", data.textField, blockUid); + await updateBlock({ + text: text, + uid: blockUid || "", + }); resolve(data.textField as string); }, onClose: () => { From f3455db33194f50a368fa4ed2d9f29a4b487614d Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 21 Apr 2025 17:38:57 +0530 Subject: [PATCH 2/7] minimal working blueprintjs dialog --- .../roam/src/components/MinimalFormDialog.tsx | 119 ++++++++++++++++++ apps/roam/src/utils/formatUtils.ts | 34 +---- 2 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 apps/roam/src/components/MinimalFormDialog.tsx diff --git a/apps/roam/src/components/MinimalFormDialog.tsx b/apps/roam/src/components/MinimalFormDialog.tsx new file mode 100644 index 000000000..1b791b479 --- /dev/null +++ b/apps/roam/src/components/MinimalFormDialog.tsx @@ -0,0 +1,119 @@ +import React, { useState, useCallback } from "react"; +import ReactDOM from "react-dom"; +import { + Button, + Classes, + Dialog, + InputGroup, + Intent, + Label, +} from "@blueprintjs/core"; +import updateBlock from "roamjs-components/writes/updateBlock"; + +interface MinimalFormDialogProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (value: string) => void; + nodeName: string; +} + +const MinimalFormDialog = ({ + isOpen, + onClose, + onSubmit, + nodeName, +}: MinimalFormDialogProps): React.ReactElement => { + const [value, setValue] = useState(""); + + const handleSubmitClick = useCallback(() => { + onSubmit(value); + }, [onSubmit, value]); + + const handleCloseClick = useCallback(() => { + onClose(); + }, [onClose]); + + return ( + +
+ +
+
+
+
+
+
+ ); +}; + +export const renderMinimalFormDialog = ({ + nodeName, + resolve, + blockUid, +}: { + nodeName: string; + resolve: (value: string) => void; + blockUid?: string; +}) => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const unmount = () => { + ReactDOM.unmountComponentAtNode(container); + if (container.parentNode) { + container.parentNode.removeChild(container); + } + }; + + const handleDialogSubmit = async (value: string) => { + // This makes the solution possible not sure why, maybe the same bug that you talked about in roam. + setTimeout(() => { + console.log( + "handleDialogSubmit: Before updateBlock. Value:", + value, + "BlockUID:", + blockUid, + ); + unmount(); + resolve(value); + }); + }; + + const handleDialogClose = () => { + unmount(); + resolve(""); + }; + + ReactDOM.render( + , + container, + ); +}; diff --git a/apps/roam/src/utils/formatUtils.ts b/apps/roam/src/utils/formatUtils.ts index 0f505ee95..d6ee0d2bb 100644 --- a/apps/roam/src/utils/formatUtils.ts +++ b/apps/roam/src/utils/formatUtils.ts @@ -5,20 +5,13 @@ import { PullBlock } from "roamjs-components/types"; import getDiscourseNodes, { DiscourseNode } from "./getDiscourseNodes"; import compileDatalog from "./compileDatalog"; import discourseNodeFormatToDatalog from "./discourseNodeFormatToDatalog"; -import createOverlayRender from "roamjs-components/util/createOverlayRender"; import { render as renderToast } from "roamjs-components/components/Toast"; -import FormDialog from "roamjs-components/components/FormDialog"; import { QBClause, Result } from "./types"; import findDiscourseNode from "./findDiscourseNode"; import extractTag from "roamjs-components/util/extractTag"; import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; import updateBlock from "roamjs-components/writes/updateBlock"; - -type FormDialogProps = Parameters[0]; -const renderFormDialog = createOverlayRender( - "form-dialog", - FormDialog, -); +import { renderMinimalFormDialog } from "../components/MinimalFormDialog"; export const getNewDiscourseNodeText = async ({ text, @@ -35,26 +28,11 @@ export const getNewDiscourseNodeText = async ({ newText = await new Promise((resolve) => { const nodeName = discourseNodes.find((n) => n.type === nodeType)?.text || "Discourse"; - renderFormDialog({ - title: `Create ${nodeName} Node`, - fields: { - textField: { - type: "text", - label: `Create ${nodeName} Node`, - }, - }, - onSubmit: async (data: Record) => { - console.log("data.textField", data.textField, blockUid); - await updateBlock({ - text: text, - uid: blockUid || "", - }); - resolve(data.textField as string); - }, - onClose: () => { - resolve(""); - }, - isOpen: true, + + renderMinimalFormDialog({ + nodeName, + resolve, + blockUid, }); }); } From 06aae8777197cec2e547194cdfd33402f3512ede Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 6 May 2025 23:48:34 +0530 Subject: [PATCH 3/7] minimal version in one file --- .../src/components/MinimalReproduction.tsx | 239 ++++++++++++++++++ .../utils/initializeObserversAndListeners.ts | 6 +- 2 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 apps/roam/src/components/MinimalReproduction.tsx diff --git a/apps/roam/src/components/MinimalReproduction.tsx b/apps/roam/src/components/MinimalReproduction.tsx new file mode 100644 index 000000000..184c1369f --- /dev/null +++ b/apps/roam/src/components/MinimalReproduction.tsx @@ -0,0 +1,239 @@ +import React, { + useState, + useCallback, + useMemo, + useRef, + useEffect, +} from "react"; +import ReactDOM from "react-dom"; +import { + Button, + Classes, + Dialog, + InputGroup, + Label, + Menu, + MenuItem, + Popover, + Position, +} from "@blueprintjs/core"; +import updateBlock from "roamjs-components/writes/updateBlock"; +import getUids from "roamjs-components/dom/getUids"; +import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu"; + +interface MinimalFormDialogProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (value: string) => void; + title: string; +} + +const MinimalFormDialog = ({ + isOpen, + onClose, + onSubmit, + title, +}: MinimalFormDialogProps): React.ReactElement => { + const [value, setValue] = useState(""); + + const handleSubmitClick = useCallback(() => { + onSubmit(value); + }, [onSubmit, value]); + + return ( + +
+ +
+
+
+
+
+
+ ); +}; + +export const renderMinimalFormDialog = ({ + title, + resolve, +}: { + title: string; + resolve: (value: string) => void; +}) => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const unmount = () => { + ReactDOM.unmountComponentAtNode(container); + if (container.parentNode) { + container.parentNode.removeChild(container); + } + }; + + const handleDialogSubmit = (value: string) => { + unmount(); + resolve(value); + }; + + const handleDialogClose = () => { + unmount(); + resolve(""); // Resolve with empty string on cancel or close + }; + + ReactDOM.render( + , + container, + ); +}; + +const PRECONFIGURED_MENU_ITEMS = [ + { text: "Option Alpha", id: "alpha" }, + { text: "Option Beta", id: "beta" }, + { text: "Option Gamma", id: "gamma" }, +]; + +export const NodeMenu = ({ + onClose, + textarea, + extensionAPI, +}: { onClose: () => void } & Props) => { + const menuItems = useMemo(() => PRECONFIGURED_MENU_ITEMS, []); + const blockUid = useMemo(() => getUids(textarea).blockUid, [textarea]); + const menuRef = useRef(null); + const [activeIndex, setActiveIndex] = useState(0); + + const onSelectMenuItem = useCallback( + (index: number) => { + const selectedItem = menuItems[index]; + if (!selectedItem) return; + + onClose(); + + renderMinimalFormDialog({ + title: `Input for ${selectedItem.text}`, + resolve: (dialogValue: string) => { + if (dialogValue && blockUid) { + updateBlock({ text: dialogValue, uid: blockUid }); + console.log( + `Block ${blockUid} updated with: ${dialogValue} via ${selectedItem.text}`, + ); + } + }, + }); + }, + [menuItems, blockUid, onClose], + ); + + const keydownListener = useCallback( + (e: KeyboardEvent) => { + if (e.key === "ArrowRight" || e.key === "ArrowDown") { + const count = menuItems.length; + setActiveIndex((prevIndex) => (prevIndex + 1) % count); + e.stopPropagation(); + e.preventDefault(); + } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + const count = menuItems.length; + setActiveIndex((prevIndex) => (prevIndex - 1 + count) % count); + e.stopPropagation(); + e.preventDefault(); + } else if (e.key === "Enter") { + onSelectMenuItem(activeIndex); + e.stopPropagation(); + e.preventDefault(); + } else if (e.key === "Escape") { + onClose(); + e.stopPropagation(); + e.preventDefault(); + } + }, + [menuItems, activeIndex, onSelectMenuItem, onClose, setActiveIndex], + ); + + useEffect(() => { + textarea.addEventListener("keydown", keydownListener, true); + textarea.addEventListener("input", onClose); + return () => { + textarea.removeEventListener("keydown", keydownListener); + textarea.removeEventListener("input", onClose); + }; + }, [keydownListener, onClose, textarea]); + + return ( + } + position={Position.BOTTOM_LEFT} + modifiers={{ + flip: { enabled: false }, + preventOverflow: { enabled: false }, + }} + autoFocus={false} + enforceFocus={false} + content={ + + {menuItems.map((item, i) => { + return ( + setActiveIndex(i)} + onClick={(e) => { + e.stopPropagation(); + onSelectMenuItem(i); + }} + className="flex items-center" + /> + ); + })} + + } + /> + ); +}; + +export const render = (props: Props) => { + const parent = document.createElement("span"); + const coords = getCoordsFromTextarea(props.textarea); + parent.style.position = "absolute"; + parent.style.left = `${coords.left}px`; + parent.style.top = `${coords.top}px`; + props.textarea.parentElement?.insertBefore(parent, props.textarea); + ReactDOM.render( + { + ReactDOM.unmountComponentAtNode(parent); + parent.remove(); + }} + />, + parent, + ); +}; diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index df2a2f36d..6edd33004 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -28,10 +28,8 @@ import refreshConfigTree from "~/utils/refreshConfigTree"; import { render as renderGraphOverviewExport } from "~/components/ExportDiscourseContext"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; import { getSettingValueFromTree } from "roamjs-components/util"; -import { - getModifiersFromCombo, - render as renderDiscourseNodeMenu, -} from "~/components/DiscourseNodeMenu"; +import { getModifiersFromCombo } from "~/components/DiscourseNodeMenu"; +import { render as renderDiscourseNodeMenu } from "~/components/MinimalReproduction"; import { IKeyCombo } from "@blueprintjs/core"; import { configPageTabs } from "~/utils/configPageTabs"; From 8d5aa7cca482c6d069bb8999c70ff37441aff28c Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 6 May 2025 23:53:56 +0530 Subject: [PATCH 4/7] revert --- .../roam/src/components/MinimalFormDialog.tsx | 119 ------------------ apps/roam/src/utils/formatUtils.ts | 77 ++++++++++-- 2 files changed, 64 insertions(+), 132 deletions(-) delete mode 100644 apps/roam/src/components/MinimalFormDialog.tsx diff --git a/apps/roam/src/components/MinimalFormDialog.tsx b/apps/roam/src/components/MinimalFormDialog.tsx deleted file mode 100644 index 1b791b479..000000000 --- a/apps/roam/src/components/MinimalFormDialog.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState, useCallback } from "react"; -import ReactDOM from "react-dom"; -import { - Button, - Classes, - Dialog, - InputGroup, - Intent, - Label, -} from "@blueprintjs/core"; -import updateBlock from "roamjs-components/writes/updateBlock"; - -interface MinimalFormDialogProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (value: string) => void; - nodeName: string; -} - -const MinimalFormDialog = ({ - isOpen, - onClose, - onSubmit, - nodeName, -}: MinimalFormDialogProps): React.ReactElement => { - const [value, setValue] = useState(""); - - const handleSubmitClick = useCallback(() => { - onSubmit(value); - }, [onSubmit, value]); - - const handleCloseClick = useCallback(() => { - onClose(); - }, [onClose]); - - return ( - -
- -
-
-
-
-
-
- ); -}; - -export const renderMinimalFormDialog = ({ - nodeName, - resolve, - blockUid, -}: { - nodeName: string; - resolve: (value: string) => void; - blockUid?: string; -}) => { - const container = document.createElement("div"); - document.body.appendChild(container); - - const unmount = () => { - ReactDOM.unmountComponentAtNode(container); - if (container.parentNode) { - container.parentNode.removeChild(container); - } - }; - - const handleDialogSubmit = async (value: string) => { - // This makes the solution possible not sure why, maybe the same bug that you talked about in roam. - setTimeout(() => { - console.log( - "handleDialogSubmit: Before updateBlock. Value:", - value, - "BlockUID:", - blockUid, - ); - unmount(); - resolve(value); - }); - }; - - const handleDialogClose = () => { - unmount(); - resolve(""); - }; - - ReactDOM.render( - , - container, - ); -}; diff --git a/apps/roam/src/utils/formatUtils.ts b/apps/roam/src/utils/formatUtils.ts index d6ee0d2bb..814d67027 100644 --- a/apps/roam/src/utils/formatUtils.ts +++ b/apps/roam/src/utils/formatUtils.ts @@ -5,13 +5,19 @@ import { PullBlock } from "roamjs-components/types"; import getDiscourseNodes, { DiscourseNode } from "./getDiscourseNodes"; import compileDatalog from "./compileDatalog"; import discourseNodeFormatToDatalog from "./discourseNodeFormatToDatalog"; +import createOverlayRender from "roamjs-components/util/createOverlayRender"; import { render as renderToast } from "roamjs-components/components/Toast"; +import FormDialog from "roamjs-components/components/FormDialog"; import { QBClause, Result } from "./types"; import findDiscourseNode from "./findDiscourseNode"; import extractTag from "roamjs-components/util/extractTag"; import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; -import updateBlock from "roamjs-components/writes/updateBlock"; -import { renderMinimalFormDialog } from "../components/MinimalFormDialog"; + +type FormDialogProps = Parameters[0]; +const renderFormDialog = createOverlayRender( + "form-dialog", + FormDialog, +); export const getNewDiscourseNodeText = async ({ text, @@ -28,20 +34,65 @@ export const getNewDiscourseNodeText = async ({ newText = await new Promise((resolve) => { const nodeName = discourseNodes.find((n) => n.type === nodeType)?.text || "Discourse"; - - renderMinimalFormDialog({ - nodeName, - resolve, - blockUid, + renderFormDialog({ + title: `Create ${nodeName} Node`, + fields: { + textField: { + type: "text", + label: `Create ${nodeName} Node`, + }, + }, + onSubmit: (data: Record) => { + const textValue = data.textField as string; + if (textValue?.trim()) { + resolve(textValue); + } else { + renderToast({ + content: "Text field cannot be empty.", + id: "roamjs-create-discourse-node-dialog-error", + intent: "warning", + }); + return false; + } + }, + onClose: () => { + resolve(""); + }, + isOpen: true, }); + + const setupButtonControl = () => { + const dialogs = document.querySelectorAll(".bp3-dialog"); + const dialog = dialogs[dialogs.length - 1] as HTMLElement; + + const input = dialog.querySelector( + 'input[type="text"].bp3-input', + ) as HTMLInputElement; + const submitBtn = dialog.querySelector( + ".bp3-dialog-footer .bp3-button.bp3-intent-primary", + ) as HTMLButtonElement; + + const updateButtonState = () => { + const currentValue = input.value; + const hasValue = currentValue.trim().length > 0; + const shouldBeDisabled = !hasValue; + submitBtn.disabled = shouldBeDisabled; + }; + + updateButtonState(); + + const listenerKey = "_discourseNodeListenerAttached"; + if (!(input as any)[listenerKey]) { + input.addEventListener("input", updateButtonState); + (input as any)[listenerKey] = true; + } + }; + requestAnimationFrame(setupButtonControl); }); } - if (!newText) { - renderToast({ - content: "No text provided.", - id: "roamjs-create-discourse-node-dialog-error", - intent: "warning", - }); + + if (!newText || !newText.trim()) { + return ""; } const indexedByType = Object.fromEntries( From 889c4942a5815c6465db0d2658cd0a8cd9faf10c Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 7 May 2025 00:00:10 +0530 Subject: [PATCH 5/7] reverting --- apps/roam/src/utils/formatUtils.ts | 49 +++++------------------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/apps/roam/src/utils/formatUtils.ts b/apps/roam/src/utils/formatUtils.ts index 814d67027..debfa7824 100644 --- a/apps/roam/src/utils/formatUtils.ts +++ b/apps/roam/src/utils/formatUtils.ts @@ -43,56 +43,21 @@ export const getNewDiscourseNodeText = async ({ }, }, onSubmit: (data: Record) => { - const textValue = data.textField as string; - if (textValue?.trim()) { - resolve(textValue); - } else { - renderToast({ - content: "Text field cannot be empty.", - id: "roamjs-create-discourse-node-dialog-error", - intent: "warning", - }); - return false; - } + resolve(data.textField as string); }, onClose: () => { resolve(""); }, isOpen: true, }); - - const setupButtonControl = () => { - const dialogs = document.querySelectorAll(".bp3-dialog"); - const dialog = dialogs[dialogs.length - 1] as HTMLElement; - - const input = dialog.querySelector( - 'input[type="text"].bp3-input', - ) as HTMLInputElement; - const submitBtn = dialog.querySelector( - ".bp3-dialog-footer .bp3-button.bp3-intent-primary", - ) as HTMLButtonElement; - - const updateButtonState = () => { - const currentValue = input.value; - const hasValue = currentValue.trim().length > 0; - const shouldBeDisabled = !hasValue; - submitBtn.disabled = shouldBeDisabled; - }; - - updateButtonState(); - - const listenerKey = "_discourseNodeListenerAttached"; - if (!(input as any)[listenerKey]) { - input.addEventListener("input", updateButtonState); - (input as any)[listenerKey] = true; - } - }; - requestAnimationFrame(setupButtonControl); }); } - - if (!newText || !newText.trim()) { - return ""; + if (!newText) { + renderToast({ + content: "No text provided.", + id: "roamjs-create-discourse-node-dialog-error", + intent: "warning", + }); } const indexedByType = Object.fromEntries( From 3543e13a1be62af8487b4ec569dcde2b8f51ace4 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 7 May 2025 17:20:39 +0530 Subject: [PATCH 6/7] undo unused code --- apps/roam/src/components/DiscourseNodeMenu.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeMenu.tsx b/apps/roam/src/components/DiscourseNodeMenu.tsx index 6ac0c9435..2ff83744b 100644 --- a/apps/roam/src/components/DiscourseNodeMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeMenu.tsx @@ -150,10 +150,7 @@ const NodeMenu = ({ text={item.text} active={i === activeIndex} onMouseEnter={() => setActiveIndex(i)} - onClick={(e) => { - e.stopPropagation(); - onSelect(i); - }} + onClick={() => onSelect(i)} className="flex items-center" icon={
Date: Wed, 7 May 2025 21:52:47 -0600 Subject: [PATCH 7/7] Refactor MinimalReproduction component: remove as much as possible --- .../src/components/MinimalReproduction.tsx | 266 +++++++----------- .../utils/initializeObserversAndListeners.ts | 22 +- 2 files changed, 105 insertions(+), 183 deletions(-) diff --git a/apps/roam/src/components/MinimalReproduction.tsx b/apps/roam/src/components/MinimalReproduction.tsx index 184c1369f..a9b03659c 100644 --- a/apps/roam/src/components/MinimalReproduction.tsx +++ b/apps/roam/src/components/MinimalReproduction.tsx @@ -1,215 +1,134 @@ -import React, { - useState, - useCallback, - useMemo, - useRef, - useEffect, -} from "react"; +import React, { useState, useEffect } from "react"; import ReactDOM from "react-dom"; import { Button, - Classes, Dialog, InputGroup, - Label, Menu, MenuItem, Popover, - Position, } from "@blueprintjs/core"; -import updateBlock from "roamjs-components/writes/updateBlock"; -import getUids from "roamjs-components/dom/getUids"; -import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu"; -interface MinimalFormDialogProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (value: string) => void; - title: string; -} +const PRECONFIGURED_MENU_ITEMS = [ + { text: "Option Alpha", id: "alpha" }, + { text: "Option Beta", id: "beta" }, + { text: "Option Gamma", id: "gamma" }, +]; const MinimalFormDialog = ({ - isOpen, - onClose, onSubmit, - title, -}: MinimalFormDialogProps): React.ReactElement => { +}: { + onSubmit: (value: string) => void; +}) => { const [value, setValue] = useState(""); - const handleSubmitClick = useCallback(() => { - onSubmit(value); - }, [onSubmit, value]); - return ( - -
- + +
+ setValue(e.target.value)} + />
-
-
-
+
+
); }; export const renderMinimalFormDialog = ({ - title, - resolve, + updateBlock, }: { - title: string; - resolve: (value: string) => void; + updateBlock: (value: string) => void; }) => { const container = document.createElement("div"); document.body.appendChild(container); - const unmount = () => { - ReactDOM.unmountComponentAtNode(container); - if (container.parentNode) { - container.parentNode.removeChild(container); - } - }; - - const handleDialogSubmit = (value: string) => { - unmount(); - resolve(value); - }; - - const handleDialogClose = () => { - unmount(); - resolve(""); // Resolve with empty string on cancel or close - }; - ReactDOM.render( { + updateBlock(value); + ReactDOM.unmountComponentAtNode(container); + if (container.parentNode) { + container.parentNode.removeChild(container); + } + }} />, container, ); }; -const PRECONFIGURED_MENU_ITEMS = [ - { text: "Option Alpha", id: "alpha" }, - { text: "Option Beta", id: "beta" }, - { text: "Option Gamma", id: "gamma" }, -]; - export const NodeMenu = ({ onClose, textarea, - extensionAPI, -}: { onClose: () => void } & Props) => { - const menuItems = useMemo(() => PRECONFIGURED_MENU_ITEMS, []); - const blockUid = useMemo(() => getUids(textarea).blockUid, [textarea]); - const menuRef = useRef(null); - const [activeIndex, setActiveIndex] = useState(0); - - const onSelectMenuItem = useCallback( - (index: number) => { - const selectedItem = menuItems[index]; - if (!selectedItem) return; - - onClose(); - - renderMinimalFormDialog({ - title: `Input for ${selectedItem.text}`, - resolve: (dialogValue: string) => { - if (dialogValue && blockUid) { - updateBlock({ text: dialogValue, uid: blockUid }); - console.log( - `Block ${blockUid} updated with: ${dialogValue} via ${selectedItem.text}`, - ); - } - }, - }); - }, - [menuItems, blockUid, onClose], - ); +}: { + onClose: () => void; + textarea: HTMLTextAreaElement; +}) => { + const id = textarea.id; + const blockUid = id.substring(id.length - 9, id.length); + + const onSelectMenuItem = (index: number) => { + const selectedItem = PRECONFIGURED_MENU_ITEMS[index]; + if (!selectedItem) return; + + onClose(); + + renderMinimalFormDialog({ + updateBlock: (dialogValue: string) => { + if (dialogValue && blockUid) { + window.roamAlphaAPI.data.block.update({ + block: { + string: dialogValue, + uid: blockUid, + }, + }); + console.log( + `window.roamAlphaAPI.data.block.update({ + block: { + string: "${dialogValue}", + uid: "${blockUid}", + }, + });`, + ); + } + }, + }); + }; - const keydownListener = useCallback( - (e: KeyboardEvent) => { - if (e.key === "ArrowRight" || e.key === "ArrowDown") { - const count = menuItems.length; - setActiveIndex((prevIndex) => (prevIndex + 1) % count); - e.stopPropagation(); - e.preventDefault(); - } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { - const count = menuItems.length; - setActiveIndex((prevIndex) => (prevIndex - 1 + count) % count); - e.stopPropagation(); - e.preventDefault(); - } else if (e.key === "Enter") { - onSelectMenuItem(activeIndex); - e.stopPropagation(); - e.preventDefault(); - } else if (e.key === "Escape") { - onClose(); - e.stopPropagation(); - e.preventDefault(); - } - }, - [menuItems, activeIndex, onSelectMenuItem, onClose, setActiveIndex], - ); + const keydownListener = (e: KeyboardEvent) => { + if (e.key === "Enter") { + onSelectMenuItem(0); + e.stopPropagation(); + e.preventDefault(); + } + }; useEffect(() => { textarea.addEventListener("keydown", keydownListener, true); - textarea.addEventListener("input", onClose); - return () => { - textarea.removeEventListener("keydown", keydownListener); - textarea.removeEventListener("input", onClose); - }; - }, [keydownListener, onClose, textarea]); + return () => textarea.removeEventListener("keydown", keydownListener); + }, [keydownListener]); return ( } - position={Position.BOTTOM_LEFT} - modifiers={{ - flip: { enabled: false }, - preventOverflow: { enabled: false }, - }} + position="bottom-left" autoFocus={false} enforceFocus={false} content={ - - {menuItems.map((item, i) => { + + {PRECONFIGURED_MENU_ITEMS.map((item, i) => { return ( setActiveIndex(i)} - onClick={(e) => { - e.stopPropagation(); - onSelectMenuItem(i); - }} - className="flex items-center" + active={i === 0} + onClick={() => onSelectMenuItem(0)} /> ); })} @@ -219,16 +138,12 @@ export const NodeMenu = ({ ); }; -export const render = (props: Props) => { +export const render = (textarea: HTMLTextAreaElement) => { const parent = document.createElement("span"); - const coords = getCoordsFromTextarea(props.textarea); - parent.style.position = "absolute"; - parent.style.left = `${coords.left}px`; - parent.style.top = `${coords.top}px`; - props.textarea.parentElement?.insertBefore(parent, props.textarea); + textarea.parentElement?.insertBefore(parent, textarea); ReactDOM.render( { ReactDOM.unmountComponentAtNode(parent); parent.remove(); @@ -237,3 +152,28 @@ export const render = (props: Props) => { parent, ); }; + +// export const initializeNodeMenuTrigger = () => { +// const handleNodeMenuRender = (target: HTMLElement, evt: KeyboardEvent) => { +// if ( +// target.tagName === "TEXTAREA" && +// target.classList.contains("rm-block-input") +// ) { +// render(target as HTMLTextAreaElement); +// evt.preventDefault(); +// evt.stopPropagation(); +// } +// }; + +// const nodeMenuTriggerListener = (e: Event) => { +// const evt = e as KeyboardEvent; +// const target = evt.target as HTMLElement; + +// if (evt.key === "\\") { +// handleNodeMenuRender(target, evt); +// } +// }; + +// document.addEventListener("keydown", nodeMenuTriggerListener); +// return () => document.removeEventListener("keydown", nodeMenuTriggerListener); +// }; diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index 6edd33004..4ebe61ace 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -145,10 +145,7 @@ export const initObservers = async ({ target.tagName === "TEXTAREA" && target.classList.contains("rm-block-input") ) { - renderDiscourseNodeMenu({ - textarea: target as HTMLTextAreaElement, - extensionAPI: onloadArgs.extensionAPI, - }); + renderDiscourseNodeMenu(target as HTMLTextAreaElement); evt.preventDefault(); evt.stopPropagation(); } @@ -158,22 +155,7 @@ export const initObservers = async ({ const evt = e as KeyboardEvent; const target = evt.target as HTMLElement; - // Personal Trigger overrides Global Trigger - if (personalTrigger) { - if (evt.key !== personalTrigger) return; - if ( - (personalModifiers.includes("ctrl") && !evt.ctrlKey) || - (personalModifiers.includes("shift") && !evt.shiftKey) || - (personalModifiers.includes("alt") && !evt.altKey) || - (personalModifiers.includes("meta") && !evt.metaKey) - ) { - return; - } - handleNodeMenuRender(target, evt); - return; - } - - if (evt.key === globalTrigger) { + if (evt.key === "\\") { handleNodeMenuRender(target, evt); } };