diff --git a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx index 01a2c289d..4bfdfc8e9 100644 --- a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx @@ -2,12 +2,13 @@ import { Alert, Button, ControlGroup, + Icon, InputGroup, Intent, HTMLTable, Tooltip, } from "@blueprintjs/core"; -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; import refreshConfigTree from "~/utils/refreshConfigTree"; import createPage from "roamjs-components/writes/createPage"; @@ -31,6 +32,18 @@ type DiscourseNodeConfigPanelProps = React.ComponentProps< setSelectedTabId: (id: string) => void; }; +const getFirstAvailableShortcut = ( + label: string, + takenShortcuts: Set, +): string => { + for (const char of label.toUpperCase()) { + if (/[A-Z0-9]/.test(char) && !takenShortcuts.has(char)) { + return char; + } + } + return ""; +}; + const DiscourseNodeConfigPanel: React.FC = ({ isPopup, setSelectedTabId, @@ -49,6 +62,19 @@ const DiscourseNodeConfigPanel: React.FC = ({ DiscourseRelation[] >([]); const [nodeTypeIdToDelete, setNodeTypeIdToDelete] = useState(""); + const duplicateShortcuts = useMemo(() => { + const counts = new Map(); + nodes.forEach((n) => { + const shortcut = n.shortcut.toUpperCase(); + if (!shortcut) return; + counts.set(shortcut, (counts.get(shortcut) ?? 0) + 1); + }); + return new Set( + Array.from(counts.entries()) + .filter(([, count]) => count > 1) + .map(([shortcut]) => shortcut), + ); + }, [nodes]); const navigateToNode = (uid: string) => { if (isPopup) { setSelectedTabId(uid); @@ -82,15 +108,12 @@ const DiscourseNodeConfigPanel: React.FC = ({ className="select-none" disabled={!label} onClick={() => { - const candidateShortcut = label.slice(0, 1).toUpperCase(); - const existingShortcuts = new Set( + const takenShortcuts = new Set( getDiscourseNodes() .map((n) => n.shortcut.toUpperCase()) .filter(Boolean), ); - const shortcut = existingShortcuts.has(candidateShortcut) - ? "" - : candidateShortcut; + const shortcut = getFirstAvailableShortcut(label, takenShortcuts); const format = `[[${label.slice(0, 3).toUpperCase()}]] - {content}`; posthog.capture("Discourse Node: Type Created", { label: label }); void createPage({ @@ -123,10 +146,10 @@ const DiscourseNodeConfigPanel: React.FC = ({ setNodes([ ...nodes, { - format: "", + format, type: valueUid, text: label, - shortcut: "", + shortcut, tag: "", specification: [], backedBy: "user", @@ -144,6 +167,7 @@ const DiscourseNodeConfigPanel: React.FC = ({ Node + Shortcut Actions @@ -165,6 +189,21 @@ const DiscourseNodeConfigPanel: React.FC = ({ {n.text} + navigateToNode(n.type)} + style={{ verticalAlign: "middle" }} + > +
+ {n.shortcut} + {duplicateShortcuts.has(n.shortcut.toUpperCase()) && ( + + + + )} +
+