diff --git a/apps/roam/src/components/DiscourseNodeMenu.tsx b/apps/roam/src/components/DiscourseNodeMenu.tsx index 46fdcc8ed..f5e569aea 100644 --- a/apps/roam/src/components/DiscourseNodeMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeMenu.tsx @@ -4,11 +4,11 @@ import { Popover, Position, Button, - InputGroup, getKeyCombo, IKeyCombo, Icon, } from "@blueprintjs/core"; +import SettingKeycapInput from "~/components/settings/components/SettingKeycapInput"; import React, { useCallback, useEffect, @@ -496,7 +496,8 @@ export const NodeMenuTriggerComponent = ({ const shortcut = useMemo(() => comboToString(comboKey), [comboKey]); return ( - { const [progress, setProgress] = useState(0); @@ -225,6 +232,21 @@ const ExportDialog: ExportDialogComponent = ({ if (initialPanel) setSelectedTabId(INITIAL_PANEL_TO_TAB_ID[initialPanel]); }, [initialPanel, sharingEnabled]); const [includeDiscourseContext, setIncludeDiscourseContext] = useState(false); + const [exportOptionsOpen, setExportOptionsOpen] = useState(false); + const exportOptionsOpened = useRef(false); + // Collapse unmounts the panels while closed, so each open seeds them from current values. + const exportGlobalSettings = useMemo( + () => bulkReadSettings().globalSettings, + // eslint-disable-next-line react-hooks/exhaustive-deps + [exportOptionsOpen], + ); + + // Option panels also write legacy config blocks, so refresh the tree as SettingsDialog + // does, but only if they were opened: refreshConfigTree re-reads every node page. + const closeDialog = (): void => { + if (exportOptionsOpened.current) refreshConfigTree(); + onClose(); + }; const [gitHubAccessToken, setGitHubAccessToken] = useState( getSetting("oauth-github", null), ); @@ -756,7 +778,7 @@ const ExportDialog: ExportDialogComponent = ({ }); } finally { setLoading(false); - onClose(); + closeDialog(); } }; @@ -796,7 +818,7 @@ const ExportDialog: ExportDialogComponent = ({ fileCount: files.length, }); } - onClose(); + closeDialog(); } catch (e) { setError("Failed to export files."); posthog.capture("Export Dialog: Export Failed", { @@ -888,7 +910,7 @@ const ExportDialog: ExportDialogComponent = ({ : "success", id: "query-builder-publish-success", }); - if (hasPublishedNodes) onClose(); + if (hasPublishedNodes) closeDialog(); } catch (e) { internalError({ error: e as Error, @@ -951,41 +973,60 @@ const ExportDialog: ExportDialogComponent = ({ /> -
+
+ + { + setIncludeDiscourseContext( + (e.target as HTMLInputElement).checked, + ); + }} + labelElement={ + + Discourse context + + } + /> + +
+
+
+ +
+ +
+
{error} -
-
@@ -93,6 +97,7 @@ const DiscourseGraphExport = ({ options={["alias", "wikilinks", "roam url"]} uid={exportSettings.linkType.uid} parentUid={parentUid} + compact />
); }; -export default DiscourseGraphExport; +export default ExportOptions; diff --git a/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx b/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx index ea32cd0e9..e387f6ec3 100644 --- a/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeSpecification.tsx @@ -155,7 +155,8 @@ const NodeSpecification = ({ -
- -
+ {enabled && ( +
+ +
+ )}
); }; diff --git a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx index 67a546c92..5af703692 100644 --- a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo } from "react"; -import { Button, Intent } from "@blueprintjs/core"; +import { Button, Collapse, Intent } from "@blueprintjs/core"; import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; import getSubTree from "roamjs-components/util/getSubTree"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; @@ -17,6 +17,7 @@ import { TEMPLATE_SETTING_KEYS, } from "~/components/settings/utils/settingKeys"; import { RenderRoamBlock } from "~/utils/roamReactComponents"; +import Description from "~/components/settings/SettingsDescription"; import { ROAM_DOCS, withDocsLink } from "./utils/docs"; const DiscourseNodeSuggestiveRules = ({ @@ -42,6 +43,7 @@ const DiscourseNodeSuggestiveRules = ({ }).uid || "", [nodeUid], ); + const [isTemplateOpen, setIsTemplateOpen] = useState(false); const [isUpdating, setIsUpdating] = useState(false); @@ -68,18 +70,39 @@ const DiscourseNodeSuggestiveRules = ({ }; return ( -
- +
+
+
+
+ {/* Collapse unmounts its children, so the buffer block only exists while open. */} + +
+ +
+
+
= { + [nodeConfigSegmentIds.index]: "Index", + [nodeConfigSegmentIds.template]: "Template", +}; + const GrammarNodesRoute = ({ onloadArgs, }: { @@ -19,7 +27,7 @@ const GrammarNodesRoute = ({ const { segments, goToDepth } = useSettingsNav(); const nodes = getDiscourseNodes().filter(excludeDefaultNodes); - const [nodeTypeUid] = segments; + const [nodeTypeUid, subPage] = segments; const node = nodeTypeUid ? nodes.find((n) => n.type === nodeTypeUid) : undefined; @@ -30,8 +38,14 @@ const GrammarNodesRoute = ({ if (isStalePath) goToDepth(0); }, [isStalePath, goToDepth]); - const resolveLabel = (segment: string): string => - nodes.find((n) => n.type === segment)?.text ?? segment; + const resolveLabel = (segment: string, segmentIndex: number): string => + segmentIndex === 0 + ? (nodes.find((n) => n.type === segment)?.text ?? segment) + : (SUB_PAGE_LABELS[segment] ?? segment); + + const dotColor = subPage + ? undefined + : formatHexColor(node?.canvasSettings?.color ?? "") || undefined; return (
@@ -39,17 +53,19 @@ const GrammarNodesRoute = ({ ancestorLabels={NODES_ANCESTOR_LABELS} rootLabel="Nodes" resolveLabel={resolveLabel} - dotColor={ - formatHexColor(node?.canvasSettings?.color ?? "") || undefined - } + dotColor={dotColor} />
- {node ? ( - - ) : ( + {!node ? (
+ ) : subPage === nodeConfigSegmentIds.index ? ( + + ) : subPage === nodeConfigSegmentIds.template ? ( + + ) : ( + )}
diff --git a/apps/roam/src/components/settings/KeyboardShortcutInput.tsx b/apps/roam/src/components/settings/KeyboardShortcutInput.tsx index 2c7314495..4defbc331 100644 --- a/apps/roam/src/components/settings/KeyboardShortcutInput.tsx +++ b/apps/roam/src/components/settings/KeyboardShortcutInput.tsx @@ -1,14 +1,8 @@ import React, { useState, useCallback, useMemo, useRef } from "react"; import { OnloadArgs } from "roamjs-components/types"; -import { - InputGroup, - Button, - getKeyCombo, - IKeyCombo, - Label, -} from "@blueprintjs/core"; -import Description from "~/components/settings/SettingsDescription"; -import { settingAnchor } from "~/components/settings/utils/settingAnchor"; +import { Button, getKeyCombo, IKeyCombo } from "@blueprintjs/core"; +import SettingKeycapInput from "~/components/settings/components/SettingKeycapInput"; +import SettingItemRow from "~/components/settings/components/SettingItemRow"; import { DISCOURSE_TOOL_SHORTCUT_KEY } from "~/data/userSettings"; import { setPersonalSetting } from "~/components/settings/utils/accessors"; import { comboToString } from "~/components/DiscourseNodeMenu"; @@ -93,26 +87,32 @@ const KeyboardShortcutInput = ({ }, [extensionAPI, settingKey, blockPropKey]); return ( -
)} - + + { + if (colorWriteTimeoutRef.current) { + window.clearTimeout(colorWriteTimeoutRef.current); + colorWriteTimeoutRef.current = null; + } + pendingColorRef.current = null; + setColor(""); + persistColorValue(""); + }} + /> + + + } + /> ); }; @@ -168,13 +162,7 @@ const generateTagPlaceholder = (node: DiscourseNode): string => { return `#${nodeTextPrefix}-candidate`; // Evidence = #evi-candidate }; -const NodeConfig = ({ - node, - onloadArgs, -}: { - node: DiscourseNode; - onloadArgs: OnloadArgs; -}) => { +const NodeConfig = ({ node }: { node: DiscourseNode }) => { const getUid = (key: string) => getSubTree({ parentUid: node.type, @@ -184,19 +172,17 @@ const NodeConfig = ({ 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"); const attributeNode = getSubTree({ parentUid: node.type, key: "Attributes", }); - const [selectedTabId, setSelectedTabId] = useState("general"); + const nav = useSettingsNav(); const [tagError, setTagError] = useState(""); const [formatError, setFormatError] = useState(""); const [shortcutError, setShortcutError] = useState(""); @@ -302,220 +288,214 @@ const NodeConfig = ({ ); return ( - <> - setSelectedTabId(id)} - selectedTabId={selectedTabId} - renderActiveTabPanelOnly={true} - > - - - -
- -
- -
+
+ + nav.push(nodeConfigSegmentIds.index)} + /> } /> - - -
- } + + + - - - - + description={withDocsLink( + `The format ${node.text} pages should have.`, + ROAM_DOCS.grammarNodes, + )} + settingKeys={[DISCOURSE_NODE_KEYS.format]} + initialValue={node.format} + error={formatError} + onChange={setFormatValue} + order={3} + parentUid={node.type} + uid={formatUid} + /> + { + validate({ + tag: tagValue, + format: formatValue, + isSpecificationEnabled, + }); + }} + /> } /> - - - + nav.push(nodeConfigSegmentIds.index)} + /> } /> - - >( - node.type, - [DISCOURSE_NODE_KEYS.attributes], - )} - /> - c.text)} - initialValue={ - getDiscourseNodeSetting(node.type, [ - DISCOURSE_NODE_KEYS.overlay, - ]) ?? "" - } - order={0} - parentUid={node.type} - uid={overlayUid} - /> - + + + + + nav.push(nodeConfigSegmentIds.template)} + /> } /> - - - - + + + + + + + + + { + validate({ + tag: tagValue, + format: formatValue, + isSpecificationEnabled, + }); + }} + /> } /> - {isSyncEnabled() && ( - - - - } + + + {isSyncEnabled() && ( + + - )} - - + + )} + + + >( + node.type, + [DISCOURSE_NODE_KEYS.attributes], + )} + /> + c.text)} + initialValue={ + getDiscourseNodeSetting(node.type, [ + DISCOURSE_NODE_KEYS.overlay, + ]) ?? "" + } + order={0} + parentUid={node.type} + uid={overlayUid} + /> + + ); }; diff --git a/apps/roam/src/components/settings/NodeIndexPage.tsx b/apps/roam/src/components/settings/NodeIndexPage.tsx new file mode 100644 index 000000000..c39558a82 --- /dev/null +++ b/apps/roam/src/components/settings/NodeIndexPage.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { getSubTree } from "roamjs-components/util"; +import { OnloadArgs } from "roamjs-components/types"; +import { DiscourseNode } from "~/utils/getDiscourseNodes"; +import DiscourseNodeIndex from "./DiscourseNodeIndex"; + +const NodeIndexPage = ({ + node, + onloadArgs, +}: { + node: DiscourseNode; + onloadArgs: OnloadArgs; +}): JSX.Element => { + const indexUid = getSubTree({ parentUid: node.type, key: "Index" }).uid; + return ( +
+ +
+ ); +}; + +export default NodeIndexPage; diff --git a/apps/roam/src/components/settings/NodeTemplatePage.tsx b/apps/roam/src/components/settings/NodeTemplatePage.tsx new file mode 100644 index 000000000..94947976d --- /dev/null +++ b/apps/roam/src/components/settings/NodeTemplatePage.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { getSubTree } from "roamjs-components/util"; +import { DiscourseNode } from "~/utils/getDiscourseNodes"; +import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; +import { TEMPLATE_SETTING_KEYS } from "~/components/settings/utils/settingKeys"; +import { ROAM_DOCS, withDocsLink } from "./utils/docs"; + +const NodeTemplatePage = ({ node }: { node: DiscourseNode }): JSX.Element => { + const templateUid = getSubTree({ parentUid: node.type, key: "Template" }).uid; + return ( +
+ +
+ ); +}; + +export default NodeTemplatePage; diff --git a/apps/roam/src/components/settings/PageGroupPanel.tsx b/apps/roam/src/components/settings/PageGroupPanel.tsx index 584cb83b5..c68bf84fc 100644 --- a/apps/roam/src/components/settings/PageGroupPanel.tsx +++ b/apps/roam/src/components/settings/PageGroupPanel.tsx @@ -1,12 +1,12 @@ import React, { useState, useCallback } from "react"; -import { Label, Button, Intent, Tag, InputGroup } from "@blueprintjs/core"; -import Description from "~/components/settings/SettingsDescription"; +import { Button, Intent, Tag, InputGroup } from "@blueprintjs/core"; import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; import createBlock from "roamjs-components/writes/createBlock"; import deleteBlock from "roamjs-components/writes/deleteBlock"; import getAllPageNames from "roamjs-components/queries/getAllPageNames"; import { type PageGroup } from "~/utils/getSuggestiveModeConfigSettings"; import { setGlobalSetting } from "~/components/settings/utils/accessors"; +import SettingItemRow from "./components/SettingItemRow"; import { GLOBAL_KEYS, SUGGESTIVE_MODE_KEYS, @@ -129,107 +129,109 @@ const PageGroupsPanel = ({ }, []); return ( -