From 4075de9c46d9a19ebae6940c8b90ab17ebc6a1c2 Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 13 Jan 2026 23:24:03 +0530 Subject: [PATCH 1/4] ENG-1225: Discourse node migration --- .../settings/DiscourseNodeCanvasSettings.tsx | 126 ++++++++++-------- .../src/components/settings/NodeConfig.tsx | 79 +++++------ .../components/BlockPropSettingPanels.tsx | 65 +++++++++ apps/roam/src/index.ts | 13 +- 4 files changed, 179 insertions(+), 104 deletions(-) diff --git a/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx b/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx index ac26daf7f..9fa7df1bc 100644 --- a/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx @@ -8,10 +8,11 @@ import { ControlGroup, Checkbox, } from "@blueprintjs/core"; -import React, { useState, useMemo } from "react"; -import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; -import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree"; -import setInputSetting from "roamjs-components/util/setInputSetting"; +import React, { useState } from "react"; +import { + getDiscourseNodeSetting, + setDiscourseNodeSetting, +} from "./utils/accessors"; export const formatHexColor = (color: string) => { if (!color) return ""; @@ -25,24 +26,40 @@ export const formatHexColor = (color: string) => { return ""; }; -const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { - const tree = useMemo(() => getBasicTreeByParentUid(uid), [uid]); +const DiscourseNodeCanvasSettings = ({ nodeType }: { nodeType: string }) => { const [color, setColor] = useState(() => { - const color = getSettingValueFromTree({ tree, key: "color" }); - return formatHexColor(color); + const storedColor = + getDiscourseNodeSetting(nodeType, ["canvasSettings", "color"]) ?? + ""; + return formatHexColor(storedColor); }); - const [alias, setAlias] = useState(() => - getSettingValueFromTree({ tree, key: "alias" }), - ); - const [queryBuilderAlias, setQueryBuilderAlias] = useState(() => - getSettingValueFromTree({ tree, key: "query-builder-alias" }), + const [alias, setAlias] = useState( + () => + getDiscourseNodeSetting(nodeType, ["canvasSettings", "alias"]) ?? + "", ); - const [isKeyImage, setIsKeyImage] = useState( - () => getSettingValueFromTree({ tree, key: "key-image" }) === "true", + const [queryBuilderAlias, setQueryBuilderAlias] = useState( + () => + getDiscourseNodeSetting(nodeType, [ + "canvasSettings", + "query-builder-alias", + ]) ?? "", ); - const [keyImageOption, setKeyImageOption] = useState(() => - getSettingValueFromTree({ tree, key: "key-image-option" }), + const [isKeyImage, setIsKeyImage] = useState(() => { + const value = getDiscourseNodeSetting(nodeType, [ + "canvasSettings", + "key-image", + ]); + return value === true; + }); + const [keyImageOption, setKeyImageOption] = useState( + () => + getDiscourseNodeSetting(nodeType, [ + "canvasSettings", + "key-image-option", + ]) ?? "first-image", ); + return (
@@ -51,14 +68,14 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { { setColor(e.target.value); - setInputSetting({ - blockUid: uid, - key: "color", - value: e.target.value.replace("#", ""), // remove hash to not create roam link - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "color"], + e.target.value.replace("#", ""), // remove hash to not create roam link + ); }} /> @@ -67,11 +84,11 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { icon={color ? "delete" : "info-sign"} onClick={() => { setColor(""); - setInputSetting({ - blockUid: uid, - key: "color", - value: "", - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "color"], + "", + ); }} /> @@ -83,11 +100,11 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { value={alias} onChange={(e) => { setAlias(e.target.value); - setInputSetting({ - blockUid: uid, - key: "alias", - value: e.target.value, - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "alias"], + e.target.value, + ); }} /> @@ -99,17 +116,17 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { setIsKeyImage(target.checked); if (target.checked) { if (!keyImageOption) setKeyImageOption("first-image"); - setInputSetting({ - blockUid: uid, - key: "key-image", - value: "true", - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "key-image"], + true, + ); } else { - setInputSetting({ - blockUid: uid, - key: "key-image", - value: "false", - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "key-image"], + false, + ); } }} > @@ -122,19 +139,18 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { /> - {/* */} { const target = e.target as HTMLInputElement; setKeyImageOption(target.value); - setInputSetting({ - blockUid: uid, - key: "key-image-option", - value: target.value, - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "key-image-option"], + target.value, + ); }} > @@ -155,11 +171,11 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { value={queryBuilderAlias} onChange={(e) => { setQueryBuilderAlias(e.target.value); - setInputSetting({ - blockUid: uid, - key: "query-builder-alias", - value: e.target.value, - }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "query-builder-alias"], + e.target.value, + ); }} />
diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index 35092c225..8af0c128f 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -5,10 +5,8 @@ import React, { useEffect, } from "react"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; -import FlagPanel from "roamjs-components/components/ConfigPanels/FlagPanel"; import SelectPanel from "roamjs-components/components/ConfigPanels/SelectPanel"; import BlocksPanel from "roamjs-components/components/ConfigPanels/BlocksPanel"; -import TextPanel from "roamjs-components/components/ConfigPanels/TextPanel"; import { getSubTree } from "roamjs-components/util"; import Description from "roamjs-components/components/Description"; import { @@ -24,12 +22,13 @@ import DiscourseNodeAttributes from "./DiscourseNodeAttributes"; import DiscourseNodeCanvasSettings from "./DiscourseNodeCanvasSettings"; import DiscourseNodeIndex from "./DiscourseNodeIndex"; import { OnloadArgs } from "roamjs-components/types"; -import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; -import createBlock from "roamjs-components/writes/createBlock"; -import updateBlock from "roamjs-components/writes/updateBlock"; import DiscourseNodeSuggestiveRules from "./DiscourseNodeSuggestiveRules"; import { useFeatureFlag } from "./utils/hooks"; -import refreshConfigTree from "~/utils/refreshConfigTree"; +import { + DiscourseNodeTextPanel, + DiscourseNodeFlagPanel, +} from "./components/BlockPropSettingPanels"; +import { setDiscourseNodeSetting } from "./utils/accessors"; export const getCleanTagText = (tag: string): string => { return tag.replace(/^#+/, "").trim().toUpperCase(); @@ -100,10 +99,11 @@ const ValidatedTextareaPanel = ({
); -const useDebouncedRoamUpdater = < +const useDebouncedBlockPropUpdater = < T extends HTMLInputElement | HTMLTextAreaElement, >( - uid: string, + nodeType: string, + settingKey: string, initialValue: string, isValid: boolean, ) => { @@ -112,7 +112,7 @@ const useDebouncedRoamUpdater = < const isValidRef = useRef(isValid); isValidRef.current = isValid; - const saveToRoam = useCallback( + const saveToBlockProp = useCallback( (text: string, timeout: boolean) => { window.clearTimeout(debounceRef.current); debounceRef.current = window.setTimeout( @@ -120,33 +120,26 @@ const useDebouncedRoamUpdater = < if (!isValidRef.current) { return; } - const existingBlock = getBasicTreeByParentUid(uid)[0]; - if (existingBlock) { - if (existingBlock.text !== text) { - void updateBlock({ uid: existingBlock.uid, text }); - } - } else if (text) { - void createBlock({ parentUid: uid, node: { text } }); - } + setDiscourseNodeSetting(nodeType, [settingKey], text); }, timeout ? 500 : 0, ); }, - [uid], + [nodeType, settingKey], ); const handleChange = useCallback( (e: React.ChangeEvent) => { const newValue = e.target.value; setValue(newValue); - saveToRoam(newValue, true); + saveToBlockProp(newValue, true); }, - [saveToRoam], + [saveToBlockProp], ); const handleBlur = useCallback(() => { - saveToRoam(value, false); - }, [value, saveToRoam]); + saveToBlockProp(value, false); + }, [value, saveToBlockProp]); return { value, handleChange, handleBlur }; }; @@ -172,19 +165,14 @@ const NodeConfig = ({ onloadArgs: OnloadArgs; }) => { const suggestiveModeEnabled = useFeatureFlag("Suggestive Mode Enabled"); + // UIDs still needed for deferred complex settings (template, specification, etc.) const getUid = (key: string) => getSubTree({ parentUid: node.type, key: key, }).uid; - const formatUid = getUid("Format"); - const descriptionUid = getUid("Description"); - const shortcutUid = getUid("Shortcut"); - const tagUid = getUid("Tag"); const templateUid = getUid("Template"); const overlayUid = getUid("Overlay"); - const canvasUid = getUid("Canvas"); - const graphOverviewUid = getUid("Graph Overview"); const specificationUid = getUid("Specification"); const indexUid = getUid("Index"); const suggestiveRulesUid = getUid("Suggestive Rules"); @@ -202,8 +190,9 @@ const NodeConfig = ({ value: tagValue, handleChange: handleTagChange, handleBlur: handleTagBlurFromHook, - } = useDebouncedRoamUpdater( - tagUid, + } = useDebouncedBlockPropUpdater( + node.type, + "tag", node.tag || "", isConfigurationValid, ); @@ -211,8 +200,9 @@ const NodeConfig = ({ value: formatValue, handleChange: handleFormatChange, handleBlur: handleFormatBlurFromHook, - } = useDebouncedRoamUpdater( - formatUid, + } = useDebouncedBlockPropUpdater( + node.type, + "format", node.format, isConfigurationValid, ); @@ -220,8 +210,9 @@ const NodeConfig = ({ value: descriptionValue, handleChange: handleDescriptionChange, handleBlur: handleDescriptionBlur, - } = useDebouncedRoamUpdater( - descriptionUid, + } = useDebouncedBlockPropUpdater( + node.type, + "description", node.description || "", true, ); @@ -293,12 +284,11 @@ const NodeConfig = ({ onChange={handleDescriptionChange} onBlur={handleDescriptionBlur} /> - - - + } diff --git a/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx b/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx index cf2bfa54c..323710df7 100644 --- a/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx +++ b/apps/roam/src/components/settings/components/BlockPropSettingPanels.tsx @@ -17,6 +17,8 @@ import { setPersonalSetting, getFeatureFlag, setFeatureFlag, + getDiscourseNodeSetting, + setDiscourseNodeSetting, } from "../utils/accessors"; import type { json } from "~/utils/getBlockProps"; import type { FeatureFlags } from "../utils/zodSchema"; @@ -331,3 +333,66 @@ export const PersonalSelectPanel = ( export const PersonalMultiTextPanel = ( props: WrapperProps & { defaultValue?: string[] }, ) => ; + +const createDiscourseNodeGetter = + (nodeType: string) => + (keys: string[]): T | undefined => + getDiscourseNodeSetting(nodeType, keys); + +const createDiscourseNodeSetter = + (nodeType: string) => + (keys: string[], value: json): void => + setDiscourseNodeSetting(nodeType, keys, value); + +type DiscourseNodeWrapperProps = WrapperProps & { + nodeType: string; +}; + +export const DiscourseNodeTextPanel = ({ + nodeType, + ...props +}: DiscourseNodeWrapperProps & { defaultValue?: string; placeholder?: string }) => ( + +); + +export const DiscourseNodeFlagPanel = ({ + nodeType, + ...props +}: DiscourseNodeWrapperProps & { + defaultValue?: boolean; + disabled?: boolean; + onBeforeChange?: (checked: boolean) => Promise; + onChange?: (checked: boolean) => void; +}) => ( + +); + +export const DiscourseNodeSelectPanel = ({ + nodeType, + ...props +}: DiscourseNodeWrapperProps & { options: string[]; defaultValue?: string }) => ( + +); + +export const DiscourseNodeNumberPanel = ({ + nodeType, + ...props +}: DiscourseNodeWrapperProps & { defaultValue?: number; min?: number; max?: number }) => ( + +); diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index 5b71e2785..3bc256ab9 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -38,7 +38,10 @@ import { DISALLOW_DIAGNOSTICS, } from "./data/userSettings"; import { initSchema } from "./components/settings/utils/init"; -import { setupPullWatchSettings } from "./components/settings/utils/pullWatchers"; +import { + setupPullWatchSettings, + setupPullWatchDiscourseNodes, +} from "./components/settings/utils/pullWatchers"; import { getFeatureFlag } from "./components/settings/utils/accessors"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; @@ -74,8 +77,9 @@ export default runExtension(async (onloadArgs) => { initPluginTimer(); - const { blockUids } = await initSchema(); - const cleanupPullWatch = setupPullWatchSettings(blockUids); + const { blockUids, nodePageUids } = await initSchema(); + const cleanupPullWatchSettings = setupPullWatchSettings(blockUids); + const cleanupPullWatchNodes = setupPullWatchDiscourseNodes(nodePageUids); addGraphViewNodeStyling(); registerCommandPaletteCommands(onloadArgs); createSettingsPanel(onloadArgs); @@ -157,7 +161,8 @@ export default runExtension(async (onloadArgs) => { ], observers: observers, unload: () => { - cleanupPullWatch(); + cleanupPullWatchSettings(); + cleanupPullWatchNodes(); setSyncActivity(false); window.roamjs.extension?.smartblocks?.unregisterCommand("QUERYBUILDER"); // @ts-expect-error - tldraw throws a warning on multiple loads From e8a4bee9178b5369ea363fc8aa7b0847e3d13c32 Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 13 Jan 2026 23:51:58 +0530 Subject: [PATCH 2/4] Migrate personal settings text inputs --- .../roam/src/components/DiscourseNodeMenu.tsx | 51 +++---- .../components/DiscourseNodeSearchMenu.tsx | 19 ++- .../src/components/canvas/uiOverrides.tsx | 14 +- .../settings/HomePersonalSettings.tsx | 9 +- .../settings/KeyboardShortcutInput.tsx | 130 ++++-------------- .../settings/utils/zodSchema.example.ts | 4 +- .../components/settings/utils/zodSchema.ts | 4 +- .../utils/initializeObserversAndListeners.ts | 12 +- 8 files changed, 79 insertions(+), 164 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeMenu.tsx b/apps/roam/src/components/DiscourseNodeMenu.tsx index f8b657984..d5dbcb1c2 100644 --- a/apps/roam/src/components/DiscourseNodeMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeMenu.tsx @@ -27,6 +27,10 @@ import { getNewDiscourseNodeText } from "~/utils/formatUtils"; import { OnloadArgs } from "roamjs-components/types"; import { formatHexColor } from "./settings/DiscourseNodeCanvasSettings"; import posthog from "posthog-js"; +import { + getPersonalSetting, + setPersonalSetting, +} from "./settings/utils/accessors"; type Props = { textarea?: HTMLTextAreaElement; @@ -395,7 +399,9 @@ const normalizeKeyCombo = (combo: string) => { }); }; -export const getModifiersFromCombo = (comboKey: IKeyCombo) => { +export const getModifiersFromCombo = ( + comboKey: IKeyCombo | { key: string; modifiers: number } | undefined, +) => { if (!comboKey) return []; return [ comboKey.modifiers & MODIFIER_BIT_MASKS.alt && "alt", @@ -405,32 +411,26 @@ export const getModifiersFromCombo = (comboKey: IKeyCombo) => { ].filter(Boolean); }; -export const NodeMenuTriggerComponent = ({ - extensionAPI, -}: { - extensionAPI: OnloadArgs["extensionAPI"]; -}) => { +export const NodeMenuTriggerComponent = () => { const inputRef = useRef(null); const [isActive, setIsActive] = useState(false); - const [comboKey, setComboKey] = useState( - () => - (extensionAPI.settings.get( - "personal-node-menu-trigger", - ) as IKeyCombo) || { modifiers: 0, key: "" }, - ); + const [comboKey, setComboKey] = useState(() => { + const saved = getPersonalSetting<{ key: string; modifiers: number }>([ + "Personal Node Menu Trigger", + ]); + return saved ?? { modifiers: 0, key: "" }; + }); - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - e.stopPropagation(); - e.preventDefault(); - const comboObj = getKeyCombo(e.nativeEvent); - if (!comboObj.key) return; + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + e.stopPropagation(); + e.preventDefault(); + const comboObj = getKeyCombo(e.nativeEvent); + if (!comboObj.key) return; - setComboKey({ key: comboObj.key, modifiers: comboObj.modifiers }); - extensionAPI.settings.set("personal-node-menu-trigger", comboObj); - }, - [extensionAPI], - ); + const newCombo = { key: comboObj.key, modifiers: comboObj.modifiers }; + setComboKey(newCombo); + setPersonalSetting(["Personal Node Menu Trigger"], newCombo); + }, []); const shortcut = useMemo(() => { if (!comboKey.key) return ""; @@ -453,8 +453,9 @@ export const NodeMenuTriggerComponent = ({ hidden={!comboKey.key} icon={"remove"} onClick={() => { - setComboKey({ modifiers: 0, key: "" }); - extensionAPI.settings.set("personal-node-menu-trigger", ""); + const emptyCombo = { modifiers: 0, key: "" }; + setComboKey(emptyCombo); + setPersonalSetting(["Personal Node Menu Trigger"], emptyCombo); }} minimal /> diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 6d3656bea..0fd3386a0 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -25,6 +25,10 @@ import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; import getDiscourseNodeFormatExpression from "~/utils/getDiscourseNodeFormatExpression"; import { Result } from "~/utils/types"; import { getSetting } from "~/utils/extensionSettings"; +import { + getPersonalSetting, + setPersonalSetting, +} from "~/components/settings/utils/accessors"; import fuzzy from "fuzzy"; type Props = { @@ -605,14 +609,9 @@ export const renderDiscourseNodeSearchMenu = (props: Props) => { ); }; -export const NodeSearchMenuTriggerSetting = ({ - onloadArgs, -}: { - onloadArgs: OnloadArgs; -}) => { - const extensionAPI = onloadArgs.extensionAPI; - const [nodeSearchTrigger, setNodeSearchTrigger] = useState( - getSetting("node-search-trigger", "@"), +export const NodeSearchMenuTriggerSetting = () => { + const [nodeSearchTrigger, setNodeSearchTriggerState] = useState( + () => getPersonalSetting(["Node Search Menu Trigger"]) ?? "@", ); const handleNodeSearchTriggerChange = ( @@ -625,8 +624,8 @@ export const NodeSearchMenuTriggerSetting = ({ .replace(/\+/g, "\\+") .trim(); - setNodeSearchTrigger(trigger); - extensionAPI.settings.set("node-search-trigger", trigger); + setNodeSearchTriggerState(trigger); + setPersonalSetting(["Node Search Menu Trigger"], trigger); }; return ( ({ tools: (editor, tools) => { // Get the custom keyboard shortcut for the discourse tool - const discourseToolCombo = getSetting(DISCOURSE_TOOL_SHORTCUT_KEY, { - key: "", - modifiers: 0, - }) as IKeyCombo; - - // For discourse tool, just use the key directly since we don't allow modifiers - const discourseToolShortcut = discourseToolCombo?.key?.toUpperCase() || ""; + const discourseToolShortcut = + getPersonalSetting(["Discourse Tool Shortcut"])?.toUpperCase() || + ""; tools["discourse-tool"] = { id: "discourse-tool", diff --git a/apps/roam/src/components/settings/HomePersonalSettings.tsx b/apps/roam/src/components/settings/HomePersonalSettings.tsx index e54ce41c4..96e8923b9 100644 --- a/apps/roam/src/components/settings/HomePersonalSettings.tsx +++ b/apps/roam/src/components/settings/HomePersonalSettings.tsx @@ -15,7 +15,6 @@ import { hideDiscourseFloatingMenu, } from "~/components/DiscourseFloatingMenu"; import { NodeSearchMenuTriggerSetting } from "../DiscourseNodeSearchMenu"; -import { DISCOURSE_TOOL_SHORTCUT_KEY } from "~/data/userSettings"; import { enablePostHog, disablePostHog } from "~/utils/posthog"; import KeyboardShortcutInput from "./KeyboardShortcutInput"; import streamlineStyling from "~/styles/streamlineStyling"; @@ -23,7 +22,6 @@ import { useFeatureFlag } from "./utils/hooks"; import { PersonalFlagPanel } from "./components/BlockPropSettingPanels"; const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { - const extensionAPI = onloadArgs.extensionAPI; const overlayHandler = getOverlayHandler(onloadArgs); const suggestiveModeEnabled = useFeatureFlag("Suggestive Mode Enabled"); @@ -36,7 +34,7 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { "Override the global trigger for the Discourse Node Menu. Must refresh after editing." } /> - + { - const platform = - typeof navigator !== "undefined" ? navigator.platform : undefined; - return platform == null ? false : /Mac|iPod|iPhone|iPad/.test(platform); -}; - -const MODIFIER_BIT_MASKS = { - alt: 1, - ctrl: 2, - meta: 4, - shift: 8, -}; - -const ALIASES: { [key: string]: string } = { - cmd: "meta", - command: "meta", - escape: "esc", - minus: "-", - mod: isMac() ? "meta" : "ctrl", - option: "alt", - plus: "+", - return: "enter", - win: "meta", -}; - -const normalizeKeyCombo = (combo: string) => { - const keys = combo.replace(/\s/g, "").split("+"); - return keys.map((key) => { - const keyName = ALIASES[key] != null ? ALIASES[key] : key; - return keyName === "meta" ? (isMac() ? "cmd" : "win") : keyName; - }); -}; - -const getModifiersFromCombo = (comboKey: IKeyCombo) => { - if (!comboKey) return []; - return [ - comboKey.modifiers & MODIFIER_BIT_MASKS.alt && "alt", - comboKey.modifiers & MODIFIER_BIT_MASKS.ctrl && "ctrl", - comboKey.modifiers & MODIFIER_BIT_MASKS.shift && "shift", - comboKey.modifiers & MODIFIER_BIT_MASKS.meta && "meta", - ].filter(Boolean); -}; - +// Simple keyboard shortcut input for single-key shortcuts (no modifiers) const KeyboardShortcutInput = ({ - onloadArgs, - settingKey, label, description, + settingKey, placeholder = "Click to set shortcut", }: KeyboardShortcutInputProps) => { - const extensionAPI = onloadArgs.extensionAPI; const inputRef = useRef(null); const [isActive, setIsActive] = useState(false); - const [comboKey, setComboKey] = useState( - () => - (extensionAPI.settings.get(settingKey) as IKeyCombo) || { - modifiers: 0, - key: "", - }, + const [shortcutKey, setShortcutKey] = useState( + () => getPersonalSetting([settingKey]) ?? "", ); const handleKeyDown = useCallback( @@ -90,50 +36,26 @@ const KeyboardShortcutInput = ({ } e.stopPropagation(); e.preventDefault(); - // For discourse tool, only allow single keys without modifiers - if (settingKey === DISCOURSE_TOOL_SHORTCUT_KEY) { - // Ignore modifier keys - if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) { - return; - } - // Only allow single character keys - if (e.key.length === 1) { - const comboObj = { key: e.key.toLowerCase(), modifiers: 0 }; - setComboKey(comboObj); - extensionAPI.settings - .set(settingKey, comboObj) - .catch(() => console.error("Failed to set setting")); - } + // Ignore modifier keys + if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) { return; } - // For other shortcuts, use the full Blueprint logic - const comboObj = getKeyCombo(e.nativeEvent); - if (!comboObj.key) return; - - setComboKey({ key: comboObj.key, modifiers: comboObj.modifiers }); - extensionAPI.settings - .set(settingKey, comboObj) - .catch(() => console.error("Failed to set setting")); + // Only allow single character keys + if (e.key.length === 1) { + const key = e.key.toLowerCase(); + setShortcutKey(key); + setPersonalSetting([settingKey], key); + } }, - [extensionAPI, settingKey], + [settingKey], ); - const shortcut = useMemo(() => { - if (!comboKey.key) return ""; - - const modifiers = getModifiersFromCombo(comboKey); - const comboString = [...modifiers, comboKey.key].join("+"); - return normalizeKeyCombo(comboString).join("+"); - }, [comboKey]); - const handleClear = useCallback(() => { - setComboKey({ modifiers: 0, key: "" }); - extensionAPI.settings - .set(settingKey, { modifiers: 0, key: "" }) - .catch(() => console.error("Failed to set setting")); - }, [extensionAPI, settingKey]); + setShortcutKey(""); + setPersonalSetting([settingKey], ""); + }, [settingKey]); return (