diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/CollapsiblePlugin/index.ts b/packages/web/src/javascripts/Components/SuperEditor/Plugins/CollapsiblePlugin/index.ts index 781d1def5af..f1144bf9525 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/CollapsiblePlugin/index.ts +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/CollapsiblePlugin/index.ts @@ -12,6 +12,8 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext import { $findMatchingParent, mergeRegister, $insertNodeToNearestRoot } from '@lexical/utils' import { $createParagraphNode, + $getRoot, + $isElementNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, @@ -21,6 +23,7 @@ import { KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, + LexicalNode, } from 'lexical' import { useEffect } from 'react' @@ -37,6 +40,28 @@ import { import { $createCollapsibleTitleNode, $isCollapsibleTitleNode, CollapsibleTitleNode } from './CollapsibleTitleNode' export const INSERT_COLLAPSIBLE_COMMAND = createCommand() +export const SET_ALL_COLLAPSIBLES_OPEN_COMMAND = createCommand('SET_ALL_COLLAPSIBLES_OPEN_COMMAND') + +export function $hasCollapsibleContainers(): boolean { + const containsCollapsibleContainer = (node: LexicalNode): boolean => + $isCollapsibleContainerNode(node) || ($isElementNode(node) && node.getChildren().some(containsCollapsibleContainer)) + + return $getRoot().getChildren().some(containsCollapsibleContainer) +} + +function $setAllCollapsiblesOpen(open: boolean): void { + const traverse = (node: LexicalNode) => { + if ($isCollapsibleContainerNode(node)) { + node.setOpen(open) + } + + if ($isElementNode(node)) { + node.getChildren().forEach(traverse) + } + } + + $getRoot().getChildren().forEach(traverse) +} export default function CollapsiblePlugin(): JSX.Element | null { const [editor] = useLexicalComposerContext() @@ -160,6 +185,14 @@ export default function CollapsiblePlugin(): JSX.Element | null { }, COMMAND_PRIORITY_LOW, ), + editor.registerCommand( + SET_ALL_COLLAPSIBLES_OPEN_COMMAND, + (open) => { + $setAllCollapsiblesOpen(open) + return true + }, + COMMAND_PRIORITY_LOW, + ), editor.registerCommand( INSERT_COLLAPSIBLE_COMMAND, () => { diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx index feaeef85927..1cf4a616bf3 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ToolbarPlugin/ToolbarPlugin.tsx @@ -48,6 +48,7 @@ import { CenterAlignBlock, JustifyAlignBlock, LeftAlignBlock, RightAlignBlock } import { BulletedListBlock, ChecklistBlock, NumberedListBlock } from '../Blocks/List' import { CodeBlock } from '../Blocks/Code' import { CollapsibleBlock } from '../Blocks/Collapsible' +import { $hasCollapsibleContainers, SET_ALL_COLLAPSIBLES_OPEN_COMMAND } from '../CollapsiblePlugin' import { DividerBlock } from '../Blocks/Divider' import { H1Block, H2Block, H3Block } from '../Blocks/Headings' import { IndentBlock, OutdentBlock } from '../Blocks/IndentOutdent' @@ -223,6 +224,7 @@ const ToolbarPlugin = () => { const [isHighlight, setIsHighlight] = useState(false) const [hasNonCollapsedSelection, setHasNonCollapsedSelection] = useState(false) + const [hasCollapsibleContainers, setHasCollapsibleContainers] = useState(false) const [linkNode, setLinkNode] = useState(null) const [linkTextNode, setLinkTextNode] = useState(null) @@ -243,6 +245,9 @@ const ToolbarPlugin = () => { const [isInsertMenuOpen, setIsInsertMenuOpen] = useState(false) const insertAnchorRef = useRef(null) + const [isCollapsibleMenuOpen, setIsCollapsibleMenuOpen] = useState(false) + const collapsibleAnchorRef = useRef(null) + const [canUndo, setCanUndo] = useState(false) const [canRedo, setCanRedo] = useState(false) @@ -309,6 +314,8 @@ const ToolbarPlugin = () => { }, [activeEditor, isMobile, isToolbarFixedRef]) const $updateToolbar = useCallback(() => { + setHasCollapsibleContainers($hasCollapsibleContainers()) + const selection = $getSelection() if (!$isRangeSelection(selection)) { return @@ -479,6 +486,10 @@ const ToolbarPlugin = () => { }, [editor, $updateToolbar]) useEffect(() => { + activeEditor.getEditorState().read(() => { + setHasCollapsibleContainers($hasCollapsibleContainers()) + }) + return mergeRegister( activeEditor.registerUpdateListener(({ editorState }) => { editorState.read(() => { @@ -816,6 +827,18 @@ const ToolbarPlugin = () => { }} disabled={!hasNonCollapsedSelection} /> + { + setIsCollapsibleMenuOpen(!isCollapsibleMenuOpen) + }} + ref={collapsibleAnchorRef} + className={isCollapsibleMenuOpen ? 'md:bg-default' : ''} + disabled={!hasCollapsibleContainers} + > + + + {isMobile && (