diff --git a/client/src/features/editor/components/block/Block.tsx b/client/src/features/editor/components/block/Block.tsx index b7d33c45..b77dc97b 100644 --- a/client/src/features/editor/components/block/Block.tsx +++ b/client/src/features/editor/components/block/Block.tsx @@ -1,5 +1,4 @@ import { useSortable } from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; import { AnimationType, ElementType, @@ -20,11 +19,17 @@ import { MenuBlock } from "../MenuBlock/MenuBlock"; import { TextOptionModal } from "../TextOptionModal/TextOptionModal"; import { TypeOptionModal } from "../TypeOptionModal/TypeOptionModal"; import { blockAnimation } from "./Block.animation"; -import { textContainerStyle, blockContainerStyle, contentWrapperStyle } from "./Block.style"; +import { + textContainerStyle, + blockContainerStyle, + contentWrapperStyle, + dropIndicatorStyle, +} from "./Block.style"; interface BlockProps { id: string; block: CRDTBlock; + draggingBlock: BlockId[]; isActive: boolean; onInput: (e: React.FormEvent, block: CRDTBlock) => void; onCompositionEnd: (e: React.CompositionEvent, block: CRDTBlock) => void; @@ -64,6 +69,7 @@ export const Block: React.FC = memo( ({ id, block, + draggingBlock, isActive, onInput, onCompositionEnd, @@ -83,13 +89,24 @@ export const Block: React.FC = memo( const { isOpen, openModal, closeModal } = useModal(); const [selectedNodes, setSelectedNodes] = useState | null>(null); const { isAnimationStart } = useBlockAnimation(blockRef); - const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ - id, - data: { - type: "block", - block, - }, - }); + const { attributes, listeners, setNodeRef, isDragging, isOver, activeIndex, overIndex } = + useSortable({ + id, + data: { + type: "block", + block, + }, + }); + + // 현재 드래그 중인 부모 블록의 indent 확인 + const isChildOfDragging = draggingBlock.some( + (item) => item.clock === block.id.clock && item.client === block.id.client, + ); + + // NOTE 드롭 인디케이터 위치 계산 + // 현재 over 중인 블럭 위치 + 위/아래로 모두 인디케이터 표시 + 부모요소는 자식요소 내부로는 이동하지 못함 + const showTopIndicator = isOver && !isChildOfDragging && activeIndex >= overIndex; + const showBottomIndicator = isOver && !isChildOfDragging && activeIndex < overIndex; const [slashModalOpen, setSlashModalOpen] = useState(false); const [slashModalPosition, setSlashModalPosition] = useState({ top: 0, left: 0 }); @@ -216,6 +233,14 @@ export const Block: React.FC = memo( } }; + const Indicator = () => ( +
+ ); + useEffect(() => { if (blockRef.current) { console.log("setInnerHTML"); @@ -226,66 +251,66 @@ export const Block: React.FC = memo( return ( // TODO: eslint 규칙을 수정해야 할까? // TODO: ol일때 index 순서 처리 - +
+ {showTopIndicator && } - + + +
onKeyDown(e, blockRef.current, block)} + onInput={handleInput} + onClick={(e) => onClick(block.id, e)} + onCopy={(e) => onCopy(e, blockRef.current, block)} + onPaste={(e) => onPaste(e, blockRef.current, block)} + onMouseUp={handleMouseUp} + onCompositionEnd={(e) => onCompositionEnd(e, block)} + contentEditable={block.type !== "hr"} + spellCheck={false} + suppressContentEditableWarning + className={textContainerStyle({ + type: block.type, + })} + /> + + handleStyleSelect("bold")} + onItalicSelect={() => handleStyleSelect("italic")} + onUnderlineSelect={() => handleStyleSelect("underline")} + onStrikeSelect={() => handleStyleSelect("strikethrough")} + onTextColorSelect={handleTextColorSelect} + onTextBackgroundColorSelect={handleTextBackgroundColorSelect} /> - -
onKeyDown(e, blockRef.current, block)} - onInput={handleInput} - onClick={(e) => onClick(block.id, e)} - onCopy={(e) => onCopy(e, blockRef.current, block)} - onPaste={(e) => onPaste(e, blockRef.current, block)} - onMouseUp={handleMouseUp} - onCompositionEnd={(e) => onCompositionEnd(e, block)} - contentEditable={block.type !== "hr"} - spellCheck={false} - suppressContentEditableWarning - className={textContainerStyle({ - type: block.type, - })} + setSlashModalOpen(false)} + onTypeSelect={(type) => handleTypeSelect(type)} + position={slashModalPosition} /> - handleStyleSelect("bold")} - onItalicSelect={() => handleStyleSelect("italic")} - onUnderlineSelect={() => handleStyleSelect("underline")} - onStrikeSelect={() => handleStyleSelect("strikethrough")} - onTextColorSelect={handleTextColorSelect} - onTextBackgroundColorSelect={handleTextBackgroundColorSelect} - /> - setSlashModalOpen(false)} - onTypeSelect={(type) => handleTypeSelect(type)} - position={slashModalPosition} - /> - + {showBottomIndicator && } +
); }, );