From e31faa4ba45179b1f63f97915987e96680f94f7c Mon Sep 17 00:00:00 2001 From: hyonun321 Date: Tue, 26 Nov 2024 11:17:26 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20=EB=B3=91=ED=95=A9=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @noctaCrdt/Crdt.ts | 2 +- client/src/features/editor/Editor.tsx | 28 +- .../editor/hooks/useMarkdownGrammer.ts | 274 +----------------- server/src/crdt/crdt.gateway.ts | 8 +- 4 files changed, 15 insertions(+), 297 deletions(-) diff --git a/@noctaCrdt/Crdt.ts b/@noctaCrdt/Crdt.ts index 04abd400..c6a29bf7 100644 --- a/@noctaCrdt/Crdt.ts +++ b/@noctaCrdt/Crdt.ts @@ -135,7 +135,7 @@ export class EditorCRDT extends CRDT { newNode.next = operation.node.next; newNode.prev = operation.node.prev; - + newNode.indent = operation.node.indent; this.LinkedList.insertById(newNode); this.clock = Math.max(this.clock, operation.node.id.clock) + 1; diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index f6e15de2..3df05bcd 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -8,7 +8,7 @@ import { RemoteCharInsertOperation, serializedEditorDataProps, } from "node_modules/@noctaCrdt/Interfaces.ts"; -import { useRef, useState, useCallback, useEffect, useMemo } from "react"; +import { useRef, useState, useCallback, useEffect } from "react"; import { useSocketStore } from "@src/stores/useSocketStore.ts"; import { setCaretPosition, getAbsoluteCaretPosition } from "@src/utils/caretUtils.ts"; import { @@ -134,12 +134,11 @@ export const Editor = ({ ) as HTMLDivElement; if (!clickedElement) return; - editorCRDT.current.currentBlock = - editorCRDT.current.LinkedList.nodeMap[JSON.stringify(blockId)]; + editorCRDT.currentBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(blockId)]; const caretPosition = getAbsoluteCaretPosition(clickedElement); // 계산된 캐럿 위치 저장 - editorCRDT.current.currentBlock.crdt.currentCaret = caretPosition; + editorCRDT.currentBlock.crdt.currentCaret = caretPosition; } }; @@ -189,7 +188,7 @@ export const Editor = ({ sendCharDeleteOperation(operationNode); // 캐럿 위치 업데이트 - editorCRDT.current.currentBlock!.crdt.currentCaret = deletePosition; + editorCRDT.currentBlock!.crdt.currentCaret = deletePosition; } } setEditorState({ @@ -316,9 +315,8 @@ export const Editor = ({ onRemoteCharUpdate: (operation) => { console.log(operation, "char : 업데이트 확인합니다이"); - if (!editorCRDT.current) return; - const targetBlock = - editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; + if (!editorCRDT) return; + const targetBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(operation.blockId)]; targetBlock.crdt.remoteUpdate(operation); setEditorState({ clock: editorCRDT.clock, @@ -326,18 +324,6 @@ export const Editor = ({ }); }, - 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, "커서위치 수신"); }, @@ -353,7 +339,7 @@ export const Editor = ({ if (!editorCRDT) return; const index = editorCRDT.LinkedList.spread().length; const operation = editorCRDT.localInsert(index, ""); - editorCRDT.current.currentBlock = operation.node; + editorCRDT.currentBlock = operation.node; sendBlockInsertOperation({ node: operation.node, pageId }); setEditorState(() => ({ clock: editorCRDT.clock, diff --git a/client/src/features/editor/hooks/useMarkdownGrammer.ts b/client/src/features/editor/hooks/useMarkdownGrammer.ts index 036c44f8..0e84f161 100644 --- a/client/src/features/editor/hooks/useMarkdownGrammer.ts +++ b/client/src/features/editor/hooks/useMarkdownGrammer.ts @@ -300,6 +300,7 @@ export const useMarkdownGrammer = ({ blockId: targetBlock.id, linkedList: editorCRDT.LinkedList, position: Math.min(caretPosition, targetBlock.crdt.read().length), + rootElement: editorRef.current, }); break; } @@ -321,6 +322,7 @@ export const useMarkdownGrammer = ({ blockId: prevBlock.id, linkedList: editorCRDT.LinkedList, position: prevBlock.crdt.read().length, + rootElement: editorRef.current, }); } break; @@ -339,6 +341,7 @@ export const useMarkdownGrammer = ({ blockId: nextBlock.id, linkedList: editorCRDT.LinkedList, position: 0, + rootElement: editorRef.current, }); } break; @@ -360,274 +363,3 @@ export const useMarkdownGrammer = ({ return { handleKeyDown }; }; - -/* -switch (e.key) { - case "Enter": { - e.preventDefault(); - const selection = window.getSelection(); - if (!selection) return; - const caretPosition = selection.focusOffset; - const currentContent = currentBlock.crdt.read(); - const afterText = currentContent.slice(caretPosition); - - if (!currentContent && currentBlock.type !== "p") { - currentBlock.type = "p"; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - break; - } - - if (!currentContent && currentBlock.type === "p") { - // 새로운 기본 블록 생성 - const operation = createNewBlock(currentIndex + 1); - operation.node.indent = currentBlock.indent; - operation.node.crdt = new BlockCRDT(editorCRDT.client); - - sendBlockInsertOperation({ node: operation.node, pageId }); - updateEditorState(operation.node.id); - break; - } - - // 현재 캐럿 위치 이후의 텍스트가 있으면 현재 블록 내용 업데이트 - if (afterText) { - // 캐럿 이후의 텍스트만 제거 - for (let i = currentContent.length - 1; i >= caretPosition; i--) { - sendCharDeleteOperation(currentBlock.crdt.localDelete(i, currentBlock.id, pageId)); - } - } - - // 새 블록 생성 - const operation = createNewBlock(currentIndex + 1); - operation.node.crdt = new BlockCRDT(editorCRDT.client); - operation.node.indent = currentBlock.indent; - sendBlockInsertOperation({ node: operation.node, pageId }); - // 캐럿 이후의 텍스트 있으면 새 블록에 추가 - if (afterText) { - afterText.split("").forEach((char, i) => { - sendCharInsertOperation( - operation.node.crdt.localInsert(i, char, operation.node.id, pageId), - ); - }); - } - - // 현재 블록이 li나 checkbox면 동일한 타입으로 생성 - if (["ul", "ol", "checkbox"].includes(currentBlock.type)) { - operation.node.type = currentBlock.type; - sendBlockUpdateOperation(editorCRDT.localUpdate(operation.node, pageId)); - } - updateEditorState(operation.node.id); - break; - } - - case "Backspace": { - const selection = window.getSelection(); - const caretPosition = selection?.focusOffset || 0; - const currentContent = currentBlock.crdt.read(); - if (currentContent === "") { - e.preventDefault(); - if (currentBlock.indent > 0) { - currentBlock.indent -= 1; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - break; - } - - if (currentBlock.type !== "p") { - // 마지막 블록이면 기본 블록으로 변경 - currentBlock.type = "p"; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - break; - } - - const prevBlock = - currentIndex > 0 ? editorCRDT.LinkedList.findByIndex(currentIndex - 1) : null; - if (prevBlock) { - sendBlockDeleteOperation(editorCRDT.localDelete(currentIndex, undefined, pageId)); - prevBlock.crdt.currentCaret = prevBlock.crdt.read().length; - editorCRDT.currentBlock = prevBlock; - updateEditorState(prevBlock.id); - } - break; - } else { - if (caretPosition === 0) { - if (currentBlock.indent > 0) { - currentBlock.indent -= 1; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - break; - } - if (currentBlock.type !== "p") { - currentBlock.type = "p"; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - // FIX: 서윤님 피드백 반영 - } else { - const prevBlock = - currentIndex > 0 ? editorCRDT.LinkedList.findByIndex(currentIndex - 1) : null; - if (prevBlock) { - const prevBlockEndCaret = prevBlock.crdt.read().length; - currentContent.split("").forEach((char) => { - sendCharInsertOperation( - prevBlock.crdt.localInsert( - prevBlock.crdt.read().length, - char, - prevBlock.id, - pageId, - ), - ); - sendCharDeleteOperation( - currentBlock.crdt.localDelete(caretPosition, currentBlock.id, pageId), - ); - }); - prevBlock.crdt.currentCaret = prevBlockEndCaret; - sendBlockDeleteOperation(editorCRDT.localDelete(currentIndex, undefined, pageId)); - updateEditorState(prevBlock.id); - e.preventDefault(); - } - } - } - break; - } - } - - case "Tab": { - e.preventDefault(); - - if (currentBlock) { - if (e.shiftKey) { - // shift + tab: 들여쓰기 감소 - if (currentBlock.indent > 0) { - currentBlock.indent -= 1; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - } - } else { - // tab: 들여쓰기 증가 - const maxIndent = 3; - if (currentBlock.indent < maxIndent) { - currentBlock.indent += 1; - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - } - } - } - break; - } - - case " ": { - // 여기 수정함 - const selection = window.getSelection(); - if (!selection) return; - const currentContent = currentBlock.crdt.read(); - const markdownElement = checkMarkdownPattern(currentContent); - if (markdownElement && currentBlock.type === "p") { - e.preventDefault(); - // 마크다운 패턴 매칭 시 타입 변경하고 내용 비우기 - currentBlock.type = markdownElement.type; - let deleteCount = 0; - while (deleteCount < markdownElement.length) { - sendCharDeleteOperation(currentBlock.crdt.localDelete(0, currentBlock.id, pageId)); - deleteCount += 1; - } - sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId)); - currentBlock.crdt.currentCaret = 0; - editorCRDT.currentBlock = currentBlock; - updateEditorState(); - } - - break; - } - - case "ArrowUp": - case "ArrowDown": { - const hasPrevBlock = currentIndex > 0; - const hasNextBlock = currentIndex < editorCRDT.LinkedList.spread().length - 1; - if (e.key === "ArrowUp" && !hasPrevBlock) { - e.preventDefault(); - return; - } - if (e.key === "ArrowDown" && !hasNextBlock) { - e.preventDefault(); - return; - } - - const selection = window.getSelection(); - const caretPosition = selection?.focusOffset || 0; - - // 이동할 블록 결정 - const targetIndex = e.key === "ArrowUp" ? currentIndex - 1 : currentIndex + 1; - const targetBlock = editorCRDT.LinkedList.findByIndex(targetIndex); - if (!targetBlock) return; - e.preventDefault(); - targetBlock.crdt.currentCaret = Math.min(caretPosition, targetBlock.crdt.read().length); - editorCRDT.currentBlock = targetBlock; - setCaretPosition({ - blockId: targetBlock.id, - linkedList: editorCRDT.LinkedList, - position: Math.min(caretPosition, targetBlock.crdt.read().length), - rootElement: editorRef.current, - }); - break; - } - case "ArrowLeft": - case "ArrowRight": { - const selection = window.getSelection(); - const caretPosition = selection?.focusOffset || 0; - const textLength = currentBlock.crdt.read().length; - - // 왼쪽 끝에서 이전 블록으로 - if (e.key === "ArrowLeft" && caretPosition === 0 && currentIndex > 0) { - e.preventDefault(); // 기본 동작 방지 - const prevBlock = editorCRDT.LinkedList.findByIndex(currentIndex - 1); - if (prevBlock) { - prevBlock.crdt.currentCaret = prevBlock.crdt.read().length; - editorCRDT.currentBlock = prevBlock; - setCaretPosition({ - blockId: prevBlock.id, - linkedList: editorCRDT.LinkedList, - position: prevBlock.crdt.read().length, - rootElement: editorRef.current, - }); - } - break; - // 오른쪽 끝에서 다음 블록으로 - } else if ( - e.key === "ArrowRight" && - caretPosition === textLength && - currentIndex < editorCRDT.LinkedList.spread().length - 1 - ) { - e.preventDefault(); // 기본 동작 방지 - const nextBlock = editorState.linkedList.findByIndex(currentIndex + 1); - if (nextBlock) { - nextBlock.crdt.currentCaret = 0; - editorCRDT.currentBlock = nextBlock; - setCaretPosition({ - blockId: nextBlock.id, - linkedList: editorCRDT.LinkedList, - position: 0, - rootElement: editorRef.current, - }); - } - break; - // 블록 내에서 이동하는 경우 - } else { - if (e.key === "ArrowLeft") { - currentBlock.crdt.currentCaret -= 1; - } else { - currentBlock.crdt.currentCaret += 1; - } - } - - break; - } - } - */ diff --git a/server/src/crdt/crdt.gateway.ts b/server/src/crdt/crdt.gateway.ts index d5960c88..a5833e78 100644 --- a/server/src/crdt/crdt.gateway.ts +++ b/server/src/crdt/crdt.gateway.ts @@ -245,9 +245,9 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa `Page delete 연산 수신 - Client ID: ${clientInfo?.clientId}, Data:`, JSON.stringify(data), ); - + const { userId } = client.data; // 현재 워크스페이스 가져오기 - const currentWorkspace = this.workSpaceService.getWorkspace(); + const currentWorkspace = this.workSpaceService.getWorkspace(userId); // pageList에서 해당 페이지 찾기 const pageIndex = currentWorkspace.pageList.findIndex((page) => page.id === data.pageId); @@ -531,9 +531,9 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa `Update 연산 수신 - Client ID: ${clientInfo?.clientId}, Data:`, JSON.stringify(data), ); - + const { userId } = client.data; const currentPage = this.workSpaceService - .getWorkspace() + .getWorkspace(userId) .pageList.find((p) => p.id === data.pageId); if (!currentPage) { throw new Error(`Page with id ${data.pageId} not found`);