diff --git a/apps/roam/src/components/left-sidebar/LeftSidebarPersonalSettings.tsx b/apps/roam/src/components/left-sidebar/LeftSidebarPersonalSettings.tsx new file mode 100644 index 000000000..87699cbf0 --- /dev/null +++ b/apps/roam/src/components/left-sidebar/LeftSidebarPersonalSettings.tsx @@ -0,0 +1,611 @@ +import React, { + useCallback, + useEffect, + useMemo, + useState, + memo, + Dispatch, + SetStateAction, +} from "react"; +import { + Button, + ButtonGroup, + Dialog, + InputGroup, + NumericInput, +} from "@blueprintjs/core"; +import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; +import getAllPageNames from "roamjs-components/queries/getAllPageNames"; +import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; +import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; +import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid"; +import { extractRef } from "roamjs-components/util"; +import { render as renderToast } from "roamjs-components/components/Toast"; +import { + getPersonalSetting, + setPersonalSetting, +} from "~/components/settings/block-prop/utils/accessors"; +import type { PersonalSection } from "~/components/settings/block-prop/utils/zodSchema"; +import { PersonalSettingsSchema } from "~/components/settings/block-prop/utils/zodSchema"; +import { FlagPanel } from "../settings/block-prop/components/FlagPanel"; +import { CollapsiblePanel } from "../settings/block-prop/components/CollapsiblePanel"; +import { getPersonalSettingsKey } from "~/components/settings/block-prop/utils/init"; + +type SectionEntry = { + name: string; + data: PersonalSection; +}; + +type ChildEntry = PersonalSection["Children"][number]; + +const SectionItem = memo( + ({ + section, + sections, + setSettingsDialogSectionName, + pageNames, + setSections, + index, + isFirst, + isLast, + onMoveSection, + }: { + section: SectionEntry; + sections: SectionEntry[]; + setSections: Dispatch>; + setSettingsDialogSectionName: (name: string | null) => void; + pageNames: string[]; + index: number; + isFirst: boolean; + isLast: boolean; + onMoveSection: (index: number, direction: "up" | "down") => void; + }) => { + const ref = extractRef(section.name); + const blockText = getTextByBlockUid(ref); + const originalName = blockText || section.name; + const [childInput, setChildInput] = useState(""); + const [childInputKey, setChildInputKey] = useState(0); + const [childSettingsIndex, setChildSettingsIndex] = useState( + null, + ); + + const removeSection = useCallback( + (sectionName: string) => { + const updated = sections.filter((s) => s.name !== sectionName); + const newRecord: Record = {}; + updated.forEach((s) => { + newRecord[s.name] = s.data; + }); + setPersonalSetting(["Left Sidebar"], newRecord); + setSections(updated); + }, + [sections, setSections], + ); + + const addChildToSection = useCallback( + (sectionName: string, childName: string) => { + if (!childName) return; + + const targetPage = + getPageUidByPageTitle(childName) || childName.trim(); + const newChild: ChildEntry = { + Page: targetPage, + Alias: "", + }; + + const updated = sections.map((s) => { + if (s.name === sectionName) { + return { + ...s, + data: { + ...s.data, + Children: [...s.data.Children, newChild], + }, + }; + } + return s; + }); + + const newRecord: Record = {}; + updated.forEach((s) => { + newRecord[s.name] = s.data; + }); + setPersonalSetting(["Left Sidebar"], newRecord); + setSections(updated); + }, + [sections, setSections], + ); + + const removeChild = useCallback( + (sectionName: string, childIndex: number) => { + const updated = sections.map((s) => { + if (s.name === sectionName) { + return { + ...s, + data: { + ...s.data, + Children: s.data.Children.filter((_, i) => i !== childIndex), + }, + }; + } + return s; + }); + + const newRecord: Record = {}; + updated.forEach((s) => { + newRecord[s.name] = s.data; + }); + setPersonalSetting(["Left Sidebar"], newRecord); + setSections(updated); + }, + [sections, setSections], + ); + + const moveChild = useCallback( + (sectionName: string, childIndex: number, direction: "up" | "down") => { + const currentSection = sections.find((s) => s.name === sectionName); + if (!currentSection) return; + + const children = [...currentSection.data.Children]; + if (direction === "up" && childIndex === 0) return; + if (direction === "down" && childIndex === children.length - 1) return; + + const newIndex = direction === "up" ? childIndex - 1 : childIndex + 1; + const [removed] = children.splice(childIndex, 1); + children.splice(newIndex, 0, removed); + + const updated = sections.map((s) => { + if (s.name === sectionName) { + return { + ...s, + data: { + ...s.data, + Children: children, + }, + }; + } + return s; + }); + + const newRecord: Record = {}; + updated.forEach((s) => { + newRecord[s.name] = s.data; + }); + setPersonalSetting(["Left Sidebar"], newRecord); + setSections(updated); + }, + [sections, setSections], + ); + + const updateChildAlias = useCallback( + (sectionName: string, childIndex: number, alias: string) => { + const updated = sections.map((s) => { + if (s.name === sectionName) { + const children = [...s.data.Children]; + children[childIndex] = { ...children[childIndex], Alias: alias }; + return { + ...s, + data: { + ...s.data, + Children: children, + }, + }; + } + return s; + }); + + const newRecord: Record = {}; + updated.forEach((s) => { + newRecord[s.name] = s.data; + }); + setPersonalSetting(["Left Sidebar"], newRecord); + setSections(updated); + }, + [sections, setSections], + ); + + const handleAddChild = useCallback(() => { + if (childInput) { + addChildToSection(section.name, childInput); + setChildInput(""); + setChildInputKey((prev) => prev + 1); + } + }, [childInput, section.name, addChildToSection]); + + return ( + +
+ {originalName} +
+ +