Skip to content

Commit

Permalink
feat: 구현한 텍스트 수정 로직 에디터와 블록 구조에 반영
Browse files Browse the repository at this point in the history
  • Loading branch information
Ludovico7 committed Nov 23, 2024
1 parent c2fd847 commit 07315a0
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 37 deletions.
54 changes: 23 additions & 31 deletions client/src/features/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Block } from "./components/block/Block.tsx";
import { useBlockDragAndDrop } from "./hooks/useBlockDragAndDrop";
import { useBlockOptionSelect } from "./hooks/useBlockOption.ts";
import { useMarkdownGrammer } from "./hooks/useMarkdownGrammer";
import { useTextOptionSelect } from "./hooks/useTextOptions.ts";

interface EditorProps {
onTitleChange: (title: string) => void;
Expand Down Expand Up @@ -84,6 +85,13 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
sendCharInsertOperation,
});

const { onTextStyleUpdate } = useTextOptionSelect({
editorCRDT: editorCRDT.current,
setEditorState,
pageId,
sendBlockUpdateOperation,
});

const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onTitleChange(e.target.value);
};
Expand All @@ -109,49 +117,21 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
const [addedChar] = newContent;
charNode = block.crdt.localInsert(0, addedChar, block.id, pageId);
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
} else if (caretPosition > currentContent.length) {
const addedChar = newContent[newContent.length - 1];
charNode = block.crdt.localInsert(currentContent.length, addedChar, block.id, pageId);
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
} else {
const addedChar = newContent[caretPosition - 1];
charNode = block.crdt.localInsert(caretPosition - 1, addedChar, block.id, pageId);
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
}
sendCharInsertOperation({ node: charNode.node, blockId: block.id, pageId });
} else if (newContent.length < currentContent.length) {
// 문자가 삭제된 경우
operationNode = block.crdt.localDelete(caretPosition, block.id, pageId);
sendCharDeleteOperation(operationNode);
editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition;
requestAnimationFrame(() => {
setCaretPosition({
blockId: block.id,
linkedList: editorCRDT.current.LinkedList,
position: caretPosition,
});
});
}
setEditorState({
clock: editorCRDT.current.clock,
Expand All @@ -170,7 +150,8 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
linkedList: editorCRDT.current.LinkedList,
position: editorCRDT.current.currentBlock?.crdt.currentCaret,
});
}, [editorCRDT.current.currentBlock?.crdt.read().length]);
// 서윤님 피드백 반영
}, [editorCRDT.current.currentBlock?.id.serialize()]);

useEffect(() => {
if (subscriptionRef.current) return;
Expand Down Expand Up @@ -224,8 +205,6 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
onRemoteBlockUpdate: (operation) => {
console.log(operation, "block : 업데이트 확인합니다이");
if (!editorCRDT.current) return;
// ??
console.log("타입", operation.node);
editorCRDT.current.remoteUpdate(operation.node, operation.pageId);
setEditorState({
clock: editorCRDT.current.clock,
Expand All @@ -243,6 +222,18 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
});
},

onRemoteCharUpdate: (operation) => {
console.log(operation, "char : 업데이트 확인합니다이");
if (!editorCRDT.current) return;
const targetBlock =
editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)];
targetBlock.crdt.remoteUpdate(operation);
setEditorState({
clock: editorCRDT.current.clock,
linkedList: editorCRDT.current.LinkedList,
});
},

onRemoteCursor: (position) => {
console.log(position, "커서위치 수신");
},
Expand Down Expand Up @@ -295,6 +286,7 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
onTypeSelect={handleTypeSelect}
onCopySelect={handleCopySelect}
onDeleteSelect={handleDeleteSelect}
onTextStyleUpdate={onTextStyleUpdate}
/>
))}
</SortableContext>
Expand Down
57 changes: 51 additions & 6 deletions client/src/features/editor/components/block/Block.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { AnimationType, ElementType } from "@noctaCrdt/Interfaces";
import { Block as CRDTBlock } from "@noctaCrdt/Node";
import { AnimationType, ElementType, TextStyleType } from "@noctaCrdt/Interfaces";
import { Block as CRDTBlock, Char } from "@noctaCrdt/Node";
import { BlockId } from "@noctaCrdt/NodeId";
import { motion } from "framer-motion";
import { memo, useRef } from "react";
import { memo, useRef, useState } from "react";
import { useModal } from "@src/components/modal/useModal";
import { useBlockAnimation } from "../../hooks/useBlockAnimtaion";
import { IconBlock } from "../IconBlock/IconBlock";
import { MenuBlock } from "../MenuBlock/MenuBlock";
import { TextOptionModal } from "../TextOptionModal/TextOptionModal";
import { blockAnimation } from "./Block.animation";
import { textContainerStyle, blockContainerStyle, contentWrapperStyle } from "./Block.style";

Expand All @@ -22,8 +24,8 @@ interface BlockProps {
onTypeSelect: (blockId: BlockId, type: ElementType) => void;
onCopySelect: (blockId: BlockId) => void;
onDeleteSelect: (blockId: BlockId) => void;
onTextStyleUpdate: (styleType: TextStyleType, blockId: BlockId, nodes: Array<Char>) => void;
}

export const Block: React.FC<BlockProps> = memo(
({
id,
Expand All @@ -36,11 +38,12 @@ export const Block: React.FC<BlockProps> = memo(
onTypeSelect,
onCopySelect,
onDeleteSelect,
onTextStyleUpdate,
}: BlockProps) => {
console.log("블록 초기화 상태", block);
const blockRef = useRef<HTMLDivElement>(null);
const blockCRDTRef = useRef<CRDTBlock>(block);

const { isOpen, openModal, closeModal } = useModal();
const [selectedNodes, setSelectedNodes] = useState<Array<Char> | null>(null);
const { isAnimationStart } = useBlockAnimation(blockRef);
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
Expand Down Expand Up @@ -70,6 +73,39 @@ export const Block: React.FC<BlockProps> = memo(
onDeleteSelect(block.id);
};

const handleMouseUp = () => {
const selection = window.getSelection();
if (!selection || selection.isCollapsed || !blockRef.current) {
setSelectedNodes(null);
closeModal();
return;
}

const range = selection.getRangeAt(0);
if (!blockRef.current.contains(range.commonAncestorContainer)) {
setSelectedNodes(null);
closeModal();
return;
}

const nodes = blockCRDTRef.current.crdt.LinkedList.getNodesBetween(
range.startOffset,
range.endOffset,
);

if (nodes.length > 0) {
setSelectedNodes(nodes);
openModal();
}
};

const handleStyleSelect = (styleType: TextStyleType) => {
if (selectedNodes) {
onTextStyleUpdate(styleType, block.id, selectedNodes);
closeModal();
}
};

return (
// TODO: eslint 규칙을 수정해야 할까?
// TODO: ol일때 index 순서 처리
Expand Down Expand Up @@ -103,6 +139,7 @@ export const Block: React.FC<BlockProps> = memo(
onKeyDown={onKeyDown}
onInput={handleInput}
onClick={() => onClick(block.id)}
onMouseUp={handleMouseUp}
contentEditable
suppressContentEditableWarning
className={textContainerStyle({
Expand All @@ -112,6 +149,14 @@ export const Block: React.FC<BlockProps> = memo(
{blockCRDTRef.current.crdt.read()}
</div>
</motion.div>
<TextOptionModal
isOpen={isOpen}
onClose={closeModal}
onBoldSelect={() => handleStyleSelect("bold")}
onItalicSelect={() => handleStyleSelect("italic")}
onUnderlineSelect={() => handleStyleSelect("underline")}
onStrikeSelect={() => handleStyleSelect("strikethrough")}
/>
</motion.div>
);
},
Expand Down

0 comments on commit 07315a0

Please sign in to comment.