From ab6b2e8afa1e22df996503181ae7f9d3dad5b58a Mon Sep 17 00:00:00 2001 From: ziggabyte Date: Mon, 13 Jan 2025 13:17:21 +0100 Subject: [PATCH 1/5] Solve bug where image block was not inserted correctly. --- src/zui/ZUIEditor/BlockToolbar.tsx | 23 +++++++------- src/zui/ZUIEditor/ImageExtensionUI.tsx | 20 ++++++++----- .../ZUIEditor/extensions/ImageExtension.ts | 30 +++++++++++++------ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/zui/ZUIEditor/BlockToolbar.tsx b/src/zui/ZUIEditor/BlockToolbar.tsx index ccf1e3f5d..38ea7e94c 100644 --- a/src/zui/ZUIEditor/BlockToolbar.tsx +++ b/src/zui/ZUIEditor/BlockToolbar.tsx @@ -10,20 +10,16 @@ import { findParentNode, isNodeSelection, ProsemirrorNode } from 'remirror'; const BlockToolbar: FC = () => { const [typing, setTyping] = useState(false); - const [curBlockPos, setCurBlockPos] = useState(-1); + const [curBlockY, setCurBlockY] = useState(-1); const [curBlockType, setCurBlockType] = useState(); const view = useEditorView(); const state = useEditorState(); - const { convertParagraph, setImageFile, toggleHeading } = useCommands(); + const { convertParagraph, toggleHeading, pickImage } = useCommands(); useEditorEvent('keyup', () => { setTyping(true); }); - useEditorEvent('blur', () => { - setCurBlockPos(-1); - }); - useEffect(() => { if (view.root) { const handleMove = () => { @@ -72,13 +68,12 @@ const BlockToolbar: FC = () => { if (node && nodeElem) { const editorRect = view.dom.getBoundingClientRect(); const nodeRect = nodeElem.getBoundingClientRect(); - setCurBlockPos(nodeRect.y - editorRect.y); + setCurBlockY(nodeRect.y - editorRect.y); setCurBlockType(node.type.name); } }, [state.selection]); - const showBar = - curBlockType && curBlockPos >= 0 && view.hasFocus() && !typing; + const showBar = curBlockType && curBlockY >= 0 && view.hasFocus() && !typing; return ( @@ -88,7 +83,7 @@ const BlockToolbar: FC = () => { opacity: showBar ? 1 : 0, pointerEvents: showBar ? 'auto' : 'none', position: 'absolute', - top: curBlockPos - 50, + top: curBlockY - 50, transition: 'opacity 0.5s', zIndex: 10000, }} @@ -96,7 +91,13 @@ const BlockToolbar: FC = () => { {curBlockType} {curBlockType == 'zimage' && ( - + )} {curBlockType == 'heading' && ( - )} - {curBlockType == 'heading' && ( - - )} - {curBlockType == 'paragraph' && ( - - )} - - - - ); -}; - -export default BlockToolbar; diff --git a/src/zui/ZUIEditor/BlockInsert.tsx b/src/zui/ZUIEditor/Tools/BlockInsert.tsx similarity index 56% rename from src/zui/ZUIEditor/BlockInsert.tsx rename to src/zui/ZUIEditor/Tools/BlockInsert.tsx index c551ea911..df58ab535 100644 --- a/src/zui/ZUIEditor/BlockInsert.tsx +++ b/src/zui/ZUIEditor/Tools/BlockInsert.tsx @@ -1,55 +1,19 @@ import { Add } from '@mui/icons-material'; import { IconButton, Paper } from '@mui/material'; import { Box } from '@mui/system'; -import { useCommands, useEditorState, useEditorView } from '@remirror/react'; -import { FC, useEffect, useState } from 'react'; +import { useCommands } from '@remirror/react'; +import { FC } from 'react'; -type BlockDividerData = { - pos: number; - y: number; +import { BlockDividerData } from './index'; + +type BlockInsertProps = { + blockDividers: BlockDividerData[]; + mouseY: number; }; -const BlockInsert: FC = () => { - const view = useEditorView(); - const state = useEditorState(); - const [mouseY, setMouseY] = useState(-Infinity); +const BlockInsert: FC = ({ blockDividers, mouseY }) => { const { insertParagraph, focus } = useCommands(); - useEffect(() => { - const handleMouseMove = (ev: Event) => { - if (ev.type == 'mousemove') { - const mouseEvent = ev as MouseEvent; - const editorRect = view.dom.getBoundingClientRect(); - setMouseY(mouseEvent.clientY - editorRect.y); - } - }; - - view.root.addEventListener('mousemove', handleMouseMove); - - return () => { - view.root.removeEventListener('mousemove', handleMouseMove); - }; - }, [view.root]); - - let pos = 0; - const blockDividers: BlockDividerData[] = [ - { - pos: 0, - y: 0, - }, - ...state.doc.children.map((blockNode) => { - pos += blockNode.nodeSize; - const rect = view.coordsAtPos(pos - 1); - - const containerRect = view.dom.getBoundingClientRect(); - - return { - pos: pos, - y: rect.bottom - containerRect.top, - }; - }), - ]; - return ( {blockDividers.map(({ pos, y }, index) => { diff --git a/src/zui/ZUIEditor/BlockMenu.tsx b/src/zui/ZUIEditor/Tools/BlockMenu.tsx similarity index 96% rename from src/zui/ZUIEditor/BlockMenu.tsx rename to src/zui/ZUIEditor/Tools/BlockMenu.tsx index 47fc6a348..158813476 100644 --- a/src/zui/ZUIEditor/BlockMenu.tsx +++ b/src/zui/ZUIEditor/Tools/BlockMenu.tsx @@ -7,7 +7,7 @@ import { } from '@remirror/react'; import { FC, useState } from 'react'; -import BlockMenuExtension from './extensions/BlockMenuExtension'; +import BlockMenuExtension from '../extensions/BlockMenuExtension'; type Props = { blocks: { diff --git a/src/zui/ZUIEditor/Tools/BlockToolbar.tsx b/src/zui/ZUIEditor/Tools/BlockToolbar.tsx new file mode 100644 index 000000000..d8cc463a6 --- /dev/null +++ b/src/zui/ZUIEditor/Tools/BlockToolbar.tsx @@ -0,0 +1,54 @@ +import { Box, Button, Paper } from '@mui/material'; +import { useCommands } from '@remirror/react'; +import { FC } from 'react'; + +type BlockToolbarProps = { + curBlockType: string; + curBlockY: number; + pos: number; +}; + +const BlockToolbar: FC = ({ + curBlockType, + curBlockY, + pos, +}) => { + const { convertParagraph, toggleHeading, pickImage } = useCommands(); + + return ( + + + + {curBlockType} + {curBlockType == 'zimage' && ( + + )} + {curBlockType == 'heading' && ( + + )} + {curBlockType == 'paragraph' && ( + + )} + + + + ); +}; + +export default BlockToolbar; diff --git a/src/zui/ZUIEditor/Tools/index.tsx b/src/zui/ZUIEditor/Tools/index.tsx new file mode 100644 index 000000000..238584579 --- /dev/null +++ b/src/zui/ZUIEditor/Tools/index.tsx @@ -0,0 +1,130 @@ +import { useEditorEvent, useEditorState, useEditorView } from '@remirror/react'; +import { FC, useEffect, useState } from 'react'; +import { findParentNode, isNodeSelection, ProsemirrorNode } from 'remirror'; + +import BlockToolbar from './BlockToolbar'; +import BlockInsert from './BlockInsert'; +import BlockMenu from './BlockMenu'; + +export type BlockDividerData = { + pos: number; + y: number; +}; + +type Props = { + blocks: { + id: string; + label: string; + }[]; +}; + +const Tools: FC = ({ blocks }) => { + const view = useEditorView(); + const state = useEditorState(); + + const [typing, setTyping] = useState(false); + const [curBlockY, setCurBlockY] = useState(-1); + const [curBlockType, setCurBlockType] = useState(); + const [mouseY, setMouseY] = useState(-Infinity); + + useEditorEvent('keyup', () => { + setTyping(true); + }); + + useEffect(() => { + const handleMouseMove = (ev: Event) => { + if (ev.type == 'mousemove') { + const mouseEvent = ev as MouseEvent; + const editorRect = view.dom.getBoundingClientRect(); + setMouseY(mouseEvent.clientY - editorRect.y); + } + setTyping(false); + }; + + view.root.addEventListener('mousemove', handleMouseMove); + + return () => { + view.root.removeEventListener('mousemove', handleMouseMove); + }; + }, [view.root]); + + useEffect(() => { + let node: ProsemirrorNode | null = null; + let nodeElem: HTMLElement | null = null; + if (isNodeSelection(state.selection)) { + const elem = view.nodeDOM(state.selection.$from.pos); + if (elem instanceof HTMLElement) { + node = state.selection.node; + nodeElem = elem; + } + } else { + const result = findParentNode({ + predicate: () => true, + selection: state.selection, + }); + + if (result) { + node = result.node; + let elem = view.nodeDOM(result.start); + + while ( + elem && + elem.parentNode && + elem.parentElement?.contentEditable != 'true' + ) { + elem = elem.parentNode; + } + + if (elem instanceof HTMLElement) { + nodeElem = elem; + } + } + } + + if (node && nodeElem) { + const editorRect = view.dom.getBoundingClientRect(); + const nodeRect = nodeElem.getBoundingClientRect(); + setCurBlockY(nodeRect.y - editorRect.y); + setCurBlockType(node.type.name); + } + }, [state.selection]); + + //View.hasFocus() returnerar true om klick kommer från resten av sidan + const showBlockToolbar = + !!curBlockType && curBlockY >= 0 && view.hasFocus() && !typing; + + let pos = 0; + const blockDividers: BlockDividerData[] = [ + { + pos: 0, + y: 0, + }, + ...state.doc.children.map((blockNode) => { + pos += blockNode.nodeSize; + const rect = view.coordsAtPos(pos - 1); + + const containerRect = view.dom.getBoundingClientRect(); + + return { + pos: pos, + y: rect.bottom - containerRect.top, + }; + }), + ]; + + return ( + <> + {showBlockToolbar && ( + + )} + + + + ); +}; + +export default Tools; diff --git a/src/zui/ZUIEditor/index.tsx b/src/zui/ZUIEditor/index.tsx index 3d7a65e26..4ea973b00 100644 --- a/src/zui/ZUIEditor/index.tsx +++ b/src/zui/ZUIEditor/index.tsx @@ -10,9 +10,6 @@ import { Box } from '@mui/material'; import LinkExtension from './extensions/LinkExtension'; import ButtonExtension from './extensions/ButtonExtension'; -import BlockToolbar from './BlockToolbar'; -import BlockInsert from './BlockInsert'; -import BlockMenu from './BlockMenu'; import BlockMenuExtension from './extensions/BlockMenuExtension'; import EmptyBlockPlaceholder from './EmptyBlockPlaceholder'; import ImageExtension from './extensions/ImageExtension'; @@ -20,6 +17,7 @@ import ImageExtensionUI from './ImageExtensionUI'; import { useNumericRouteParams } from 'core/hooks'; import { useMessages } from 'core/i18n'; import messageIds from 'zui/l10n/messageIds'; +import Tools from './Tools'; type ZetkinExtension = ButtonExtension | HeadingExtension | ImageExtension; @@ -111,15 +109,13 @@ const ZUIEditor: FC = ({ enableButton, enableHeading, enableImage }) => { >
- - - - ({ id: ext.name, label: messages.blockLabels[ext.name](), }))} /> + {enableImage && } Date: Mon, 13 Jan 2025 18:22:06 +0100 Subject: [PATCH 3/5] Move logic to control if BlockMenu is open into a hook and into Tools component. --- src/zui/ZUIEditor/Tools/BlockMenu.tsx | 91 ++++++------------------- src/zui/ZUIEditor/Tools/index.tsx | 41 +++++++++-- src/zui/ZUIEditor/Tools/useBlockMenu.ts | 58 ++++++++++++++++ 3 files changed, 111 insertions(+), 79 deletions(-) create mode 100644 src/zui/ZUIEditor/Tools/useBlockMenu.ts diff --git a/src/zui/ZUIEditor/Tools/BlockMenu.tsx b/src/zui/ZUIEditor/Tools/BlockMenu.tsx index 158813476..28ce6dcf8 100644 --- a/src/zui/ZUIEditor/Tools/BlockMenu.tsx +++ b/src/zui/ZUIEditor/Tools/BlockMenu.tsx @@ -1,13 +1,7 @@ import { Box, MenuItem, Paper } from '@mui/material'; -import { - useCommands, - useExtensionEvent, - useMenuNavigation, - usePositioner, -} from '@remirror/react'; -import { FC, useState } from 'react'; +import { FC } from 'react'; -import BlockMenuExtension from '../extensions/BlockMenuExtension'; +import useBlockMenu from './useBlockMenu'; type Props = { blocks: { @@ -17,72 +11,25 @@ type Props = { }; const BlockMenu: FC = ({ blocks }) => { - const [query, setQuery] = useState(null); - const [ignore, setIgnore] = useState(false); - const positioner = usePositioner('cursor'); - const { insertBlock } = useCommands(); - - useExtensionEvent(BlockMenuExtension, 'onBlockQuery', (newQuery) => { - setQuery(newQuery); - if (newQuery == null) { - setIgnore(false); - } - }); - - const filteredBlocks = blocks.filter( - (block) => - !query || - query == '' || - block.id.toLowerCase().startsWith(query.toLowerCase()) || - block.label.toLowerCase().startsWith(query.toLowerCase()) - ); - - const isOpen = query !== null && !ignore; - - const menu = useMenuNavigation({ - isOpen: isOpen, - items: filteredBlocks, - onDismiss: () => { - setQuery(null); - setIgnore(true); - return true; - }, - onSubmit: (blockType) => { - setQuery(null); - insertBlock(blockType.id); - return true; - }, - }); + const { filteredBlocks, menu } = useBlockMenu(blocks); return ( - - - - - {isOpen && - filteredBlocks.map((item, index) => { - const props = menu.getItemProps({ index, item }); - return ( - - {item.label} - - ); - })} - - - + + + {filteredBlocks.map((item, index) => { + const props = menu.getItemProps({ index, item }); + return ( + + {item.label} + + ); + })} + ); }; diff --git a/src/zui/ZUIEditor/Tools/index.tsx b/src/zui/ZUIEditor/Tools/index.tsx index 238584579..24fc8e00c 100644 --- a/src/zui/ZUIEditor/Tools/index.tsx +++ b/src/zui/ZUIEditor/Tools/index.tsx @@ -1,10 +1,17 @@ -import { useEditorEvent, useEditorState, useEditorView } from '@remirror/react'; +import { + useEditorEvent, + useEditorState, + useEditorView, + usePositioner, +} from '@remirror/react'; import { FC, useEffect, useState } from 'react'; import { findParentNode, isNodeSelection, ProsemirrorNode } from 'remirror'; +import { Box } from '@mui/material'; import BlockToolbar from './BlockToolbar'; import BlockInsert from './BlockInsert'; import BlockMenu from './BlockMenu'; +import useBlockMenu from './useBlockMenu'; export type BlockDividerData = { pos: number; @@ -21,6 +28,8 @@ type Props = { const Tools: FC = ({ blocks }) => { const view = useEditorView(); const state = useEditorState(); + const positioner = usePositioner('cursor'); + const { isOpen: showBlockMenu } = useBlockMenu(blocks); const [typing, setTyping] = useState(false); const [curBlockY, setCurBlockY] = useState(-1); @@ -89,10 +98,6 @@ const Tools: FC = ({ blocks }) => { } }, [state.selection]); - //View.hasFocus() returnerar true om klick kommer från resten av sidan - const showBlockToolbar = - !!curBlockType && curBlockY >= 0 && view.hasFocus() && !typing; - let pos = 0; const blockDividers: BlockDividerData[] = [ { @@ -112,6 +117,15 @@ const Tools: FC = ({ blocks }) => { }), ]; + const showBlockToolbar = + !showBlockMenu && + !!curBlockType && + curBlockY >= 0 && + view.hasFocus() && + !typing; + + const showBlockInsert = !showBlockMenu && !typing; + return ( <> {showBlockToolbar && ( @@ -121,8 +135,21 @@ const Tools: FC = ({ blocks }) => { pos={state.selection.$anchor.pos} /> )} - - + {showBlockInsert && ( + + )} + + + {showBlockMenu && } + + ); }; diff --git a/src/zui/ZUIEditor/Tools/useBlockMenu.ts b/src/zui/ZUIEditor/Tools/useBlockMenu.ts new file mode 100644 index 000000000..06cce554a --- /dev/null +++ b/src/zui/ZUIEditor/Tools/useBlockMenu.ts @@ -0,0 +1,58 @@ +import { + useCommands, + useExtensionEvent, + useMenuNavigation, +} from '@remirror/react'; +import { useState } from 'react'; + +import BlockMenuExtension from '../extensions/BlockMenuExtension'; + +export default function useBlockMenu( + blocks: { + id: string; + label: string; + }[] +) { + const [query, setQuery] = useState(null); + const [ignore, setIgnore] = useState(false); + + const { insertBlock } = useCommands(); + + useExtensionEvent(BlockMenuExtension, 'onBlockQuery', (newQuery) => { + setQuery(newQuery); + if (newQuery == null) { + setIgnore(false); + } + }); + + const isOpen = query !== null && !ignore; + + const filteredBlocks = blocks.filter( + (block) => + !query || + query == '' || + block.id.toLowerCase().startsWith(query.toLowerCase()) || + block.label.toLowerCase().startsWith(query.toLowerCase()) + ); + + const menu = useMenuNavigation({ + isOpen: isOpen, + items: filteredBlocks, + onDismiss: () => { + setQuery(null); + setIgnore(true); + return true; + }, + onSubmit: (blockType) => { + setQuery(null); + insertBlock(blockType.id); + return true; + }, + }); + + return { + filteredBlocks, + isOpen, + menu, + }; +} From 124b17deceb795dd0691869707333cf899c3594a Mon Sep 17 00:00:00 2001 From: ziggabyte Date: Tue, 14 Jan 2025 13:47:00 +0100 Subject: [PATCH 4/5] Rename to EditorOverlays. --- .../{Tools => EditorOverlays}/BlockInsert.tsx | 0 .../{Tools => EditorOverlays}/BlockMenu.tsx | 0 .../BlockToolbar.tsx | 0 .../{Tools => EditorOverlays}/index.tsx | 96 ++++++++++++------- .../{Tools => EditorOverlays}/useBlockMenu.ts | 0 src/zui/ZUIEditor/index.tsx | 4 +- 6 files changed, 65 insertions(+), 35 deletions(-) rename src/zui/ZUIEditor/{Tools => EditorOverlays}/BlockInsert.tsx (100%) rename src/zui/ZUIEditor/{Tools => EditorOverlays}/BlockMenu.tsx (100%) rename src/zui/ZUIEditor/{Tools => EditorOverlays}/BlockToolbar.tsx (100%) rename src/zui/ZUIEditor/{Tools => EditorOverlays}/index.tsx (74%) rename src/zui/ZUIEditor/{Tools => EditorOverlays}/useBlockMenu.ts (100%) diff --git a/src/zui/ZUIEditor/Tools/BlockInsert.tsx b/src/zui/ZUIEditor/EditorOverlays/BlockInsert.tsx similarity index 100% rename from src/zui/ZUIEditor/Tools/BlockInsert.tsx rename to src/zui/ZUIEditor/EditorOverlays/BlockInsert.tsx diff --git a/src/zui/ZUIEditor/Tools/BlockMenu.tsx b/src/zui/ZUIEditor/EditorOverlays/BlockMenu.tsx similarity index 100% rename from src/zui/ZUIEditor/Tools/BlockMenu.tsx rename to src/zui/ZUIEditor/EditorOverlays/BlockMenu.tsx diff --git a/src/zui/ZUIEditor/Tools/BlockToolbar.tsx b/src/zui/ZUIEditor/EditorOverlays/BlockToolbar.tsx similarity index 100% rename from src/zui/ZUIEditor/Tools/BlockToolbar.tsx rename to src/zui/ZUIEditor/EditorOverlays/BlockToolbar.tsx diff --git a/src/zui/ZUIEditor/Tools/index.tsx b/src/zui/ZUIEditor/EditorOverlays/index.tsx similarity index 74% rename from src/zui/ZUIEditor/Tools/index.tsx rename to src/zui/ZUIEditor/EditorOverlays/index.tsx index 24fc8e00c..1d7e35c08 100644 --- a/src/zui/ZUIEditor/Tools/index.tsx +++ b/src/zui/ZUIEditor/EditorOverlays/index.tsx @@ -4,7 +4,7 @@ import { useEditorView, usePositioner, } from '@remirror/react'; -import { FC, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useState } from 'react'; import { findParentNode, isNodeSelection, ProsemirrorNode } from 'remirror'; import { Box } from '@mui/material'; @@ -25,39 +25,20 @@ type Props = { }[]; }; -const Tools: FC = ({ blocks }) => { +const EditorOverlays: FC = ({ blocks }) => { const view = useEditorView(); const state = useEditorState(); const positioner = usePositioner('cursor'); const { isOpen: showBlockMenu } = useBlockMenu(blocks); const [typing, setTyping] = useState(false); - const [curBlockY, setCurBlockY] = useState(-1); - const [curBlockType, setCurBlockType] = useState(); const [mouseY, setMouseY] = useState(-Infinity); - useEditorEvent('keyup', () => { - setTyping(true); - }); - - useEffect(() => { - const handleMouseMove = (ev: Event) => { - if (ev.type == 'mousemove') { - const mouseEvent = ev as MouseEvent; - const editorRect = view.dom.getBoundingClientRect(); - setMouseY(mouseEvent.clientY - editorRect.y); - } - setTyping(false); - }; - - view.root.addEventListener('mousemove', handleMouseMove); - - return () => { - view.root.removeEventListener('mousemove', handleMouseMove); - }; - }, [view.root]); + //ett "curBlock"-state + const [curBlockRect, setCurBlockRect] = useState(null); + const [curBlockType, setCurBlockType] = useState(); - useEffect(() => { + const findSelectedNode = useCallback(() => { let node: ProsemirrorNode | null = null; let nodeElem: HTMLElement | null = null; if (isNodeSelection(state.selection)) { @@ -93,9 +74,51 @@ const Tools: FC = ({ blocks }) => { if (node && nodeElem) { const editorRect = view.dom.getBoundingClientRect(); const nodeRect = nodeElem.getBoundingClientRect(); - setCurBlockY(nodeRect.y - editorRect.y); + const x = nodeRect.x - editorRect.x; + const y = nodeRect.y - editorRect.y; setCurBlockType(node.type.name); + setCurBlockRect({ + ...nodeRect.toJSON(), + left: x, + top: y, + x: x, + y: y, + }); } + }, [view, state.selection]); + + useEffect(() => { + const observer = new ResizeObserver(findSelectedNode); + if (view.dom) { + observer.observe(view.dom); + } + + return () => observer.disconnect(); + }, [view.dom, findSelectedNode, state.selection]); + + useEditorEvent('keyup', () => { + setTyping(true); + }); + + useEffect(() => { + const handleMouseMove = (ev: Event) => { + if (ev.type == 'mousemove') { + const mouseEvent = ev as MouseEvent; + const editorRect = view.dom.getBoundingClientRect(); + setMouseY(mouseEvent.clientY - editorRect.y); + } + setTyping(false); + }; + + view.root.addEventListener('mousemove', handleMouseMove); + + return () => { + view.root.removeEventListener('mousemove', handleMouseMove); + }; + }, [view.root]); + + useEffect(() => { + findSelectedNode(); }, [state.selection]); let pos = 0; @@ -118,20 +141,27 @@ const Tools: FC = ({ blocks }) => { ]; const showBlockToolbar = - !showBlockMenu && - !!curBlockType && - curBlockY >= 0 && - view.hasFocus() && - !typing; + !showBlockMenu && !!curBlockType && view.hasFocus() && !typing; const showBlockInsert = !showBlockMenu && !typing; return ( <> + + + {showBlockToolbar && ( )} @@ -154,4 +184,4 @@ const Tools: FC = ({ blocks }) => { ); }; -export default Tools; +export default EditorOverlays; diff --git a/src/zui/ZUIEditor/Tools/useBlockMenu.ts b/src/zui/ZUIEditor/EditorOverlays/useBlockMenu.ts similarity index 100% rename from src/zui/ZUIEditor/Tools/useBlockMenu.ts rename to src/zui/ZUIEditor/EditorOverlays/useBlockMenu.ts diff --git a/src/zui/ZUIEditor/index.tsx b/src/zui/ZUIEditor/index.tsx index 4ea973b00..218cc1402 100644 --- a/src/zui/ZUIEditor/index.tsx +++ b/src/zui/ZUIEditor/index.tsx @@ -17,7 +17,7 @@ import ImageExtensionUI from './ImageExtensionUI'; import { useNumericRouteParams } from 'core/hooks'; import { useMessages } from 'core/i18n'; import messageIds from 'zui/l10n/messageIds'; -import Tools from './Tools'; +import EditorOverlays from './EditorOverlays'; type ZetkinExtension = ButtonExtension | HeadingExtension | ImageExtension; @@ -109,7 +109,7 @@ const ZUIEditor: FC = ({ enableButton, enableHeading, enableImage }) => { >
- ({ id: ext.name, label: messages.blockLabels[ext.name](), From 6d0a2c4b2741e09975754714323a3fdf5a2ce6bf Mon Sep 17 00:00:00 2001 From: ziggabyte Date: Tue, 14 Jan 2025 14:02:49 +0100 Subject: [PATCH 5/5] Create unified state for currently selected block. --- src/zui/ZUIEditor/EditorOverlays/index.tsx | 58 ++++++++++++---------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/zui/ZUIEditor/EditorOverlays/index.tsx b/src/zui/ZUIEditor/EditorOverlays/index.tsx index 1d7e35c08..9fac08316 100644 --- a/src/zui/ZUIEditor/EditorOverlays/index.tsx +++ b/src/zui/ZUIEditor/EditorOverlays/index.tsx @@ -18,6 +18,11 @@ export type BlockDividerData = { y: number; }; +type BlockData = { + rect: DOMRect; + type: string; +}; + type Props = { blocks: { id: string; @@ -33,10 +38,7 @@ const EditorOverlays: FC = ({ blocks }) => { const [typing, setTyping] = useState(false); const [mouseY, setMouseY] = useState(-Infinity); - - //ett "curBlock"-state - const [curBlockRect, setCurBlockRect] = useState(null); - const [curBlockType, setCurBlockType] = useState(); + const [currentBlock, setCurrentBlock] = useState(null); const findSelectedNode = useCallback(() => { let node: ProsemirrorNode | null = null; @@ -76,13 +78,15 @@ const EditorOverlays: FC = ({ blocks }) => { const nodeRect = nodeElem.getBoundingClientRect(); const x = nodeRect.x - editorRect.x; const y = nodeRect.y - editorRect.y; - setCurBlockType(node.type.name); - setCurBlockRect({ - ...nodeRect.toJSON(), - left: x, - top: y, - x: x, - y: y, + setCurrentBlock({ + rect: { + ...nodeRect.toJSON(), + left: x, + top: y, + x: x, + y: y, + }, + type: node.type.name, }); } }, [view, state.selection]); @@ -141,27 +145,31 @@ const EditorOverlays: FC = ({ blocks }) => { ]; const showBlockToolbar = - !showBlockMenu && !!curBlockType && view.hasFocus() && !typing; + !showBlockMenu && !!currentBlock && view.hasFocus() && !typing; const showBlockInsert = !showBlockMenu && !typing; + const showSelectedBlockOutline = !!currentBlock; + return ( <> - - - + {showSelectedBlockOutline && ( + + + + )} {showBlockToolbar && ( )}