Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#175 캐럿 관리방식 변경 #176

Merged
merged 12 commits into from
Nov 25, 2024
183 changes: 96 additions & 87 deletions client/src/features/editor/hooks/useMarkdownGrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { BlockId } from "@noctaCrdt/NodeId";
import { useCallback } from "react";
import { EditorStateProps } from "@features/editor/Editor";
import { checkMarkdownPattern } from "@src/features/editor/utils/markdownPatterns";
import { setCaretPosition } from "@src/utils/caretUtils";

interface useMarkdownGrammerProps {
editorCRDT: EditorCRDT;
Expand All @@ -19,7 +20,6 @@ interface useMarkdownGrammerProps {
React.SetStateAction<{
clock: number;
linkedList: BlockLinkedList;
currentBlock: BlockId | null;
}>
>;
pageId: string;
Expand All @@ -44,22 +44,26 @@ export const useMarkdownGrammer = ({
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const createNewBlock = (index: number): RemoteBlockInsertOperation => {
console.log("createNewBlock");
const operation = editorCRDT.localInsert(index, "");
// TODO: 블록 타입이 초기화가 안됨???
operation.node.type = "p";
return operation;
};

const updateEditorState = (newBlockId: BlockId | null = null) => {
if (
newBlockId !== null &&
editorCRDT.currentBlock !== editorCRDT.LinkedList.getNode(newBlockId)
) {
editorCRDT.currentBlock = editorCRDT.LinkedList.getNode(newBlockId);
}
setEditorState((prev) => ({
clock: editorCRDT.clock,
linkedList: editorCRDT.LinkedList,
currentBlock: newBlockId || prev.currentBlock,
}));
};

const currentBlockId = editorState.currentBlock;
const currentBlockId = editorCRDT.currentBlock ? editorCRDT.currentBlock.id : null;
if (!currentBlockId) return;

const currentBlock = editorCRDT.LinkedList.getNode(currentBlockId);
Expand All @@ -72,15 +76,16 @@ export const useMarkdownGrammer = ({
switch (e.key) {
case "Enter": {
e.preventDefault();
const caretPosition = currentBlock.crdt.currentCaret;
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";
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
editorCRDT.currentBlock = currentBlock;
updateEditorState();
break;
}
Expand All @@ -92,7 +97,6 @@ export const useMarkdownGrammer = ({
operation.node.crdt = new BlockCRDT(editorCRDT.client);

sendBlockInsertOperation({ node: operation.node, pageId });

updateEditorState(operation.node.id);
break;
}
Expand All @@ -101,8 +105,6 @@ export const useMarkdownGrammer = ({
if (afterText) {
// 캐럿 이후의 텍스트만 제거
for (let i = currentContent.length - 1; i >= caretPosition; i--) {
// char remote delete 보내기
// currentBlock.crdt.localDelete(i, currentBlock.id);
sendCharDeleteOperation(currentBlock.crdt.localDelete(i, currentBlock.id, pageId));
}
}
Expand All @@ -115,8 +117,6 @@ export const useMarkdownGrammer = ({
// 캐럿 이후의 텍스트 있으면 새 블록에 추가
if (afterText) {
afterText.split("").forEach((char, i) => {
// char remote insert 보내기
// newBlock.crdt.localInsert(i, char, newBlock.id);
sendCharInsertOperation(
operation.node.crdt.localInsert(i, char, operation.node.id, pageId),
);
Expand All @@ -126,65 +126,57 @@ export const useMarkdownGrammer = ({
// 현재 블록이 li나 checkbox면 동일한 타입으로 생성
if (["ul", "ol", "checkbox"].includes(currentBlock.type)) {
operation.node.type = currentBlock.type;
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: operation.node, pageId });
sendBlockUpdateOperation(editorCRDT.localUpdate(operation.node, pageId));
}
// !! TODO socket.update
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;
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
editorCRDT.currentBlock = currentBlock;
updateEditorState();
break;
}

if (currentBlock.type !== "p") {
// 마지막 블록이면 기본 블록으로 변경
currentBlock.type = "p";
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
editorCRDT.currentBlock = currentBlock;
updateEditorState();
break;
}

const prevBlock =
currentIndex > 0 ? editorCRDT.LinkedList.findByIndex(currentIndex - 1) : null;
if (prevBlock) {
// remote delete 보내기
// editorCRDT.localDelete(currentIndex, undefined, pageId);
sendBlockDeleteOperation(editorCRDT.localDelete(currentIndex, undefined, pageId));
prevBlock.crdt.currentCaret = prevBlock.crdt.read().length;
editorCRDT.currentBlock = prevBlock;
updateEditorState(prevBlock.id);
}
break;
} else {
const { currentCaret } = currentBlock.crdt;
if (currentCaret === 0) {
if (caretPosition === 0) {
if (currentBlock.indent > 0) {
currentBlock.indent -= 1;
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
editorCRDT.currentBlock = currentBlock;
updateEditorState();
break;
}
if (currentBlock.type !== "p") {
currentBlock.type = "p";
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
editorCRDT.currentBlock = currentBlock;
updateEditorState();
// FIX: 서윤님 피드백 반영
} else {
Expand All @@ -193,8 +185,6 @@ export const useMarkdownGrammer = ({
if (prevBlock) {
const prevBlockEndCaret = prevBlock.crdt.read().length;
currentContent.split("").forEach((char) => {
// char remote insert 보내기
// prevBlock.crdt.localInsert(prevBlock.crdt.read().length, char, prevBlock.id);
sendCharInsertOperation(
prevBlock.crdt.localInsert(
prevBlock.crdt.read().length,
Expand All @@ -204,12 +194,10 @@ export const useMarkdownGrammer = ({
),
);
sendCharDeleteOperation(
currentBlock.crdt.localDelete(currentCaret, currentBlock.id, pageId),
currentBlock.crdt.localDelete(caretPosition, currentBlock.id, pageId),
);
});
prevBlock.crdt.currentCaret = prevBlockEndCaret;
// remote delete 보내기
// editorCRDT.localDelete(currentIndex, undefined, pageId);
sendBlockDeleteOperation(editorCRDT.localDelete(currentIndex, undefined, pageId));
updateEditorState(prevBlock.id);
e.preventDefault();
Expand All @@ -228,28 +216,28 @@ export const useMarkdownGrammer = ({
// shift + tab: 들여쓰기 감소
if (currentBlock.indent > 0) {
currentBlock.indent -= 1;
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
updateEditorState(currentBlock.id);
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
editorCRDT.currentBlock = currentBlock;
updateEditorState();
}
} else {
// tab: 들여쓰기 증가
console.log("tab");
const maxIndent = 3;
if (currentBlock.indent < maxIndent) {
currentBlock.indent += 1;
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
updateEditorState(currentBlock.id);
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") {
Expand All @@ -258,75 +246,96 @@ export const useMarkdownGrammer = ({
currentBlock.type = markdownElement.type;
let deleteCount = 0;
while (deleteCount < markdownElement.length) {
// char remote delete 보내기
// currentBlock.crdt.localDelete(0, currentBlock.id);
sendCharDeleteOperation(currentBlock.crdt.localDelete(0, currentBlock.id, pageId));
deleteCount += 1;
}
// TODO: Update요청
// remote update 보내기
sendBlockUpdateOperation({ node: currentBlock, pageId });
// !!TODO emit 송신
sendBlockUpdateOperation(editorCRDT.localUpdate(currentBlock, pageId));
currentBlock.crdt.currentCaret = 0;
updateEditorState(currentBlock.id);
editorCRDT.currentBlock = currentBlock;
updateEditorState();
}

break;
}

case "ArrowUp":
case "ArrowDown": {
e.preventDefault();
const blocks = editorCRDT.LinkedList.spread();
// 이전/다음 블록 존재 여부 확인
const hasPrevBlock = currentIndex > 0;
const hasNextBlock = currentIndex < blocks.length - 1;
// 방향키에 따라 이동 가능 여부 확인
if (e.key === "ArrowUp" && !hasPrevBlock) return;
if (e.key === "ArrowDown" && !hasNextBlock) return;
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 targetIndex = e.key === "ArrowUp" ? currentIndex - 1 : currentIndex + 1;
const selection = window.getSelection();
const caretPosition = selection?.focusOffset || 0;

const targetBlock = blocks[targetIndex];
targetBlock.crdt.currentCaret = Math.min(
currentBlock.crdt.currentCaret,
targetBlock.crdt.read().length,
);
updateEditorState(targetBlock.id);
// 이동할 블록 결정
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),
});
break;
}
// TODO: ArrowLeft, ArrowRight 블록 이동시 캐럿 이상하게 움직이는 것 수정
case "ArrowLeft":
case "ArrowRight": {
const { currentCaret } = currentBlock.crdt;
const selection = window.getSelection();
const caretPosition = selection?.focusOffset || 0;
const textLength = currentBlock.crdt.read().length;
if (e.key === "ArrowLeft" && currentCaret > 0) {
// currentBlock.crdt.currentCaret = currentCaret - 1;
currentBlock.crdt.currentCaret -= 1;
break;
}
if (e.key === "ArrowRight" && currentCaret < textLength) {
// currentBlock.crdt.currentCaret = currentCaret + 1;
currentBlock.crdt.currentCaret += 1;
break;
}
if (e.key === "ArrowLeft" && currentCaret === 0 && currentIndex > 0) {

// 왼쪽 끝에서 이전 블록으로
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;
updateEditorState(prevBlock.id);
editorCRDT.currentBlock = prevBlock;
setCaretPosition({
blockId: prevBlock.id,
linkedList: editorCRDT.LinkedList,
position: prevBlock.crdt.read().length,
});
}
break;
}
if (e.key === "ArrowRight" && currentCaret === textLength) {
// TODO: 다음 블록 없을 때 처리 crdt에 추가
const nextBlock = editorCRDT.LinkedList.findByIndex(currentIndex + 1);
// 오른쪽 끝에서 다음 블록으로
} 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;
updateEditorState(nextBlock.id);
editorCRDT.currentBlock = nextBlock;
setCaretPosition({
blockId: nextBlock.id,
linkedList: editorCRDT.LinkedList,
position: 0,
});
}
break;
// 블록 내에서 이동하는 경우
} else {
if (e.key === "ArrowLeft") {
currentBlock.crdt.currentCaret -= 1;
} else {
currentBlock.crdt.currentCaret += 1;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전에 오른쪽에 요소가 없을때 오른쪽 화살표키를 누르면 에러가 났는데 이젠 해결이 됐군요..! 😊

}

break;
}
}
},
Expand Down