From 72abe88941472118f83e531ac7836e598b3088dd Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 10:45:23 +0900 Subject: [PATCH 01/11] =?UTF-8?q?refactor:=20useCopyAndPaste=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=ED=9B=85=EC=9C=BC=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/features/editor/Editor.tsx | 115 ++-------------- .../features/editor/hooks/useCopyAndPaste.ts | 124 ++++++++++++++++++ 2 files changed, 132 insertions(+), 107 deletions(-) create mode 100644 client/src/features/editor/hooks/useCopyAndPaste.ts diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index c4f3ce0e..7cd10210 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -7,8 +7,6 @@ import { BlockId } from "@noctaCrdt/NodeId"; import { RemoteCharInsertOperation, serializedEditorDataProps, - TextColorType, - BackgroundColorType, } from "node_modules/@noctaCrdt/Interfaces.ts"; import { useRef, useState, useCallback, useEffect, useMemo } from "react"; import { useSocketStore } from "@src/stores/useSocketStore.ts"; @@ -22,6 +20,7 @@ import { import { Block } from "./components/block/Block"; import { useBlockDragAndDrop } from "./hooks/useBlockDragAndDrop"; import { useBlockOptionSelect } from "./hooks/useBlockOption"; +import { useCopyAndPaste } from "./hooks/useCopyAndPaste.ts"; import { useMarkdownGrammer } from "./hooks/useMarkdownGrammer"; import { useTextOptionSelect } from "./hooks/useTextOptions.ts"; import { getTextOffset } from "./utils/domSyncUtils.ts"; @@ -37,12 +36,7 @@ interface EditorProps { serializedEditorData: serializedEditorDataProps; pageTitle: string; } -interface ClipboardMetadata { - value: string; - style: string[]; - color: TextColorType | undefined; - backgroundColor: BackgroundColorType | undefined; -} + export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData }: EditorProps) => { const { sendCharInsertOperation, @@ -116,6 +110,12 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData }, ); + const { handleCopy, handlePaste } = useCopyAndPaste({ + editorCRDT: editorCRDT.current, + setEditorState, + pageId, + }); + const handleTitleChange = (e: React.ChangeEvent) => { const newTitle = e.target.value; setDisplayTitle(newTitle); // 로컬 상태 업데이트 @@ -269,105 +269,6 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData } }; - const handleCopy = ( - e: React.ClipboardEvent, - blockRef: HTMLDivElement | null, - block: CRDTBlock, - ) => { - e.preventDefault(); - if (!blockRef) return; - - const selection = window.getSelection(); - if (!selection || selection.isCollapsed || !blockRef) return; - - const range = selection.getRangeAt(0); - if (!blockRef.contains(range.commonAncestorContainer)) return; - - // 선택된 텍스트의 시작과 끝 위치 계산 - const startOffset = getTextOffset(blockRef, range.startContainer, range.startOffset); - const endOffset = getTextOffset(blockRef, range.endContainer, range.endOffset); - - // 선택된 텍스트와 스타일 정보 추출 - const selectedChars = block.crdt.LinkedList.spread().slice(startOffset, endOffset); - - // 커스텀 데이터 포맷으로 저장 - const customData = { - text: selectedChars.map((char) => char.value).join(""), - metadata: selectedChars.map( - (char) => - ({ - value: char.value, - style: char.style, - color: char.color, - backgroundColor: char.backgroundColor, - }) as ClipboardMetadata, - ), - }; - - // 일반 텍스트와 커스텀 데이터 모두 클립보드에 저장 - e.clipboardData.setData("text/plain", customData.text); - e.clipboardData.setData("application/x-nocta-formatted", JSON.stringify(customData)); - }; - - const handlePaste = (e: React.ClipboardEvent, block: CRDTBlock) => { - e.preventDefault(); - - const customData = e.clipboardData.getData("application/x-nocta-formatted"); - - if (customData) { - const { metadata } = JSON.parse(customData); - const caretPosition = block.crdt.currentCaret; - - metadata.forEach((char: ClipboardMetadata, index: number) => { - const insertPosition = caretPosition + index; - const charNode = block.crdt.localInsert( - insertPosition, - char.value, - block.id, - pageId, - char.style, - char.color, - char.backgroundColor, - ); - sendCharInsertOperation({ - node: charNode.node, - blockId: block.id, - pageId, - style: char.style, - color: char.color, - backgroundColor: char.backgroundColor, - }); - }); - - editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition + metadata.length; - } else { - const text = e.clipboardData.getData("text/plain"); - - if (!block || text.length === 0) return; - - const caretPosition = block.crdt.currentCaret; - - // 텍스트를 한 글자씩 순차적으로 삽입 - text.split("").forEach((char, index) => { - const insertPosition = caretPosition + index; - const charNode = block.crdt.localInsert(insertPosition, char, block.id, pageId); - sendCharInsertOperation({ - node: charNode.node, - blockId: block.id, - pageId, - }); - }); - - // 캐럿 위치 업데이트 - editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition + text.length; - } - - setEditorState({ - clock: editorCRDT.current.clock, - linkedList: editorCRDT.current.LinkedList, - }); - }; - const handleCompositionEnd = useCallback( (e: React.CompositionEvent, block: CRDTBlock) => { if (!editorCRDT) return; diff --git a/client/src/features/editor/hooks/useCopyAndPaste.ts b/client/src/features/editor/hooks/useCopyAndPaste.ts new file mode 100644 index 00000000..f7b5592f --- /dev/null +++ b/client/src/features/editor/hooks/useCopyAndPaste.ts @@ -0,0 +1,124 @@ +import { EditorCRDT } from "@noctaCrdt/Crdt"; +import { TextColorType, BackgroundColorType } from "@noctaCrdt/Interfaces"; +import { Block } from "@noctaCrdt/Node"; +import { useCallback } from "react"; +import { useSocketStore } from "@src/stores/useSocketStore"; +import { EditorStateProps } from "../Editor"; +import { getTextOffset } from "../utils/domSyncUtils"; + +interface ClipboardMetadata { + value: string; + style: string[]; + color: TextColorType | undefined; + backgroundColor: BackgroundColorType | undefined; +} + +interface UseCopyAndPasteProps { + editorCRDT: EditorCRDT; + pageId: string; + setEditorState: React.Dispatch>; +} + +export const useCopyAndPaste = ({ editorCRDT, pageId, setEditorState }: UseCopyAndPasteProps) => { + const { sendCharInsertOperation } = useSocketStore(); + + const handleCopy = useCallback( + (e: React.ClipboardEvent, blockRef: HTMLDivElement | null, block: Block) => { + e.preventDefault(); + if (!blockRef) return; + + const selection = window.getSelection(); + if (!selection || selection.isCollapsed || !blockRef) return; + + const range = selection.getRangeAt(0); + if (!blockRef.contains(range.commonAncestorContainer)) return; + + // 선택된 텍스트의 시작과 끝 위치 계산 + const startOffset = getTextOffset(blockRef, range.startContainer, range.startOffset); + const endOffset = getTextOffset(blockRef, range.endContainer, range.endOffset); + + // 선택된 텍스트와 스타일 정보 추출 + const selectedChars = block.crdt.LinkedList.spread().slice(startOffset, endOffset); + + // 커스텀 데이터 포맷으로 저장 + const customData = { + text: selectedChars.map((char) => char.value).join(""), + metadata: selectedChars.map( + (char) => + ({ + value: char.value, + style: char.style, + color: char.color, + backgroundColor: char.backgroundColor, + }) as ClipboardMetadata, + ), + }; + + // 일반 텍스트와 커스텀 데이터 모두 클립보드에 저장 + e.clipboardData.setData("text/plain", customData.text); + e.clipboardData.setData("application/x-nocta-formatted", JSON.stringify(customData)); + }, + [], + ); + + const handlePaste = useCallback((e: React.ClipboardEvent, block: Block) => { + e.preventDefault(); + + const customData = e.clipboardData.getData("application/x-nocta-formatted"); + + if (customData) { + const { metadata } = JSON.parse(customData); + const caretPosition = block.crdt.currentCaret; + + metadata.forEach((char: ClipboardMetadata, index: number) => { + const insertPosition = caretPosition + index; + const charNode = block.crdt.localInsert( + insertPosition, + char.value, + block.id, + pageId, + char.style, + char.color, + char.backgroundColor, + ); + sendCharInsertOperation({ + node: charNode.node, + blockId: block.id, + pageId, + style: char.style, + color: char.color, + backgroundColor: char.backgroundColor, + }); + }); + + editorCRDT.currentBlock!.crdt.currentCaret = caretPosition + metadata.length; + } else { + const text = e.clipboardData.getData("text/plain"); + + if (!block || text.length === 0) return; + + const caretPosition = block.crdt.currentCaret; + + // 텍스트를 한 글자씩 순차적으로 삽입 + text.split("").forEach((char, index) => { + const insertPosition = caretPosition + index; + const charNode = block.crdt.localInsert(insertPosition, char, block.id, pageId); + sendCharInsertOperation({ + node: charNode.node, + blockId: block.id, + pageId, + }); + }); + + // 캐럿 위치 업데이트 + editorCRDT.currentBlock!.crdt.currentCaret = caretPosition + text.length; + } + + setEditorState({ + clock: editorCRDT.clock, + linkedList: editorCRDT.LinkedList, + }); + }, []); + + return { handleCopy, handlePaste }; +}; From c6da022985342c7cd4c39ad3d1ed425bb9db59da Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 11:04:59 +0900 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20handleBlockClick,=20handleBlo?= =?UTF-8?q?ckInput,=20handleKeyDown=EC=9D=84=20useBlockOperation=20?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=ED=9B=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/features/editor/Editor.tsx | 178 +++--------------- .../editor/hooks/useBlockOperation.ts | 172 +++++++++++++++++ 2 files changed, 195 insertions(+), 155 deletions(-) create mode 100644 client/src/features/editor/hooks/useBlockOperation.ts diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index 7cd10210..0db3e2d3 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -4,13 +4,10 @@ import { EditorCRDT } from "@noctaCrdt/Crdt"; import { BlockLinkedList } from "@noctaCrdt/LinkedList"; import { Block as CRDTBlock } from "@noctaCrdt/Node"; import { BlockId } from "@noctaCrdt/NodeId"; -import { - RemoteCharInsertOperation, - serializedEditorDataProps, -} from "node_modules/@noctaCrdt/Interfaces.ts"; +import { serializedEditorDataProps } from "node_modules/@noctaCrdt/Interfaces.ts"; import { useRef, useState, useCallback, useEffect, useMemo } from "react"; import { useSocketStore } from "@src/stores/useSocketStore.ts"; -import { setCaretPosition, getAbsoluteCaretPosition } from "@src/utils/caretUtils.ts"; +import { setCaretPosition } from "@src/utils/caretUtils.ts"; import { editorContainer, editorTitleContainer, @@ -19,11 +16,11 @@ import { } from "./Editor.style"; import { Block } from "./components/block/Block"; import { useBlockDragAndDrop } from "./hooks/useBlockDragAndDrop"; +import { useBlockOperation } from "./hooks/useBlockOperation.ts"; import { useBlockOptionSelect } from "./hooks/useBlockOption"; import { useCopyAndPaste } from "./hooks/useCopyAndPaste.ts"; import { useMarkdownGrammer } from "./hooks/useMarkdownGrammer"; import { useTextOptionSelect } from "./hooks/useTextOptions.ts"; -import { getTextOffset } from "./utils/domSyncUtils.ts"; export interface EditorStateProps { clock: number; @@ -102,6 +99,14 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData sendCharInsertOperation, }); + const { handleBlockClick, handleBlockInput, handleKeyDown } = useBlockOperation({ + editorCRDT: editorCRDT.current, + setEditorState, + pageId, + onKeyDown, + handleHrInput, + }); + const { onTextStyleUpdate, onTextColorUpdate, onTextBackgroundColorUpdate } = useTextOptionSelect( { editorCRDT: editorCRDT.current, @@ -131,143 +136,6 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData onTitleChange(newTitle, true); } }; - const handleBlockClick = (blockId: BlockId, e: React.MouseEvent) => { - if (editorCRDT) { - const selection = window.getSelection(); - if (!selection) return; - - const clickedElement = (e.target as HTMLElement).closest( - '[contenteditable="true"]', - ) as HTMLDivElement; - if (!clickedElement) return; - - editorCRDT.current.currentBlock = - editorCRDT.current.LinkedList.nodeMap[JSON.stringify(blockId)]; - const caretPosition = getAbsoluteCaretPosition(clickedElement); - - // 계산된 캐럿 위치 저장 - editorCRDT.current.currentBlock.crdt.currentCaret = caretPosition; - } - }; - - const handleBlockInput = useCallback( - (e: React.FormEvent, block: CRDTBlock) => { - if (!block || !editorCRDT) return; - if ((e.nativeEvent as InputEvent).isComposing) { - return; - } - - let operationNode; - const element = e.currentTarget; - const newContent = element.textContent || ""; - const currentContent = block.crdt.read(); - const caretPosition = getAbsoluteCaretPosition(element); - - if (handleHrInput(block, newContent)) { - return; - } - - if (newContent.length > currentContent.length) { - let charNode: RemoteCharInsertOperation; - // 캐럿 위치 유효성 검사 - const validCaretPosition = Math.min(Math.max(0, caretPosition), currentContent.length); - // 맨 앞에 삽입 - if (caretPosition === 0) { - const [addedChar] = newContent; - charNode = block.crdt.localInsert(0, addedChar, block.id, pageId); - } else if (caretPosition > currentContent.length) { - // 맨 뒤에 삽입 - let prevChar; - if (currentContent.length > 0) { - prevChar = editorCRDT.current.currentBlock?.crdt.LinkedList.findByIndex( - currentContent.length - 1, - ); - } - const addedChar = newContent[newContent.length - 1]; - charNode = block.crdt.localInsert( - currentContent.length, - addedChar, - block.id, - pageId, - prevChar ? prevChar.style : [], - prevChar ? prevChar.color : undefined, - prevChar ? prevChar.backgroundColor : undefined, - ); - } else { - // 중간에 삽입 - const prevChar = editorCRDT.current.currentBlock?.crdt.LinkedList.findByIndex( - validCaretPosition - 1, - ); - const addedChar = newContent[validCaretPosition - 1]; - charNode = block.crdt.localInsert( - validCaretPosition - 1, - addedChar, - block.id, - pageId, - prevChar?.style, - prevChar?.color, - prevChar?.backgroundColor, - ); - } - editorCRDT.current.currentBlock!.crdt.currentCaret = caretPosition; - sendCharInsertOperation({ node: charNode.node, blockId: block.id, pageId }); - } else if (newContent.length < currentContent.length) { - // 문자가 삭제된 경우 - // 삭제 위치 계산 - const deletePosition = Math.max(0, caretPosition); - if (deletePosition >= 0 && deletePosition < currentContent.length) { - operationNode = block.crdt.localDelete(deletePosition, block.id, pageId); - sendCharDeleteOperation(operationNode); - - // 캐럿 위치 업데이트 - editorCRDT.current.currentBlock!.crdt.currentCaret = deletePosition; - } - } - setEditorState({ - clock: editorCRDT.current.clock, - linkedList: editorCRDT.current.LinkedList, - }); - }, - [sendCharInsertOperation, sendCharDeleteOperation, editorCRDT, pageId], - ); - - const handleKeyDown = ( - e: React.KeyboardEvent, - blockRef: HTMLDivElement | null, - block: CRDTBlock, - ) => { - if (!blockRef || !block) return; - const selection = window.getSelection(); - if (!selection || selection.isCollapsed || !blockRef) { - // 선택된 텍스트가 없으면 기존 onKeyDown 로직 실행 - onKeyDown(e); - return; - } - - if (e.key === "Backspace") { - e.preventDefault(); - - const range = selection.getRangeAt(0); - if (!blockRef.contains(range.commonAncestorContainer)) return; - - const startOffset = getTextOffset(blockRef, range.startContainer, range.startOffset); - const endOffset = getTextOffset(blockRef, range.endContainer, range.endOffset); - - // 선택된 범위의 문자들을 역순으로 삭제 - for (let i = endOffset - 1; i >= startOffset; i--) { - const operationNode = block.crdt.localDelete(i, block.id, pageId); - sendCharDeleteOperation(operationNode); - } - - block.crdt.currentCaret = startOffset; - setEditorState({ - clock: editorCRDT.current.clock, - linkedList: editorCRDT.current.LinkedList, - }); - } else { - onKeyDown(e); - } - }; const handleCompositionEnd = useCallback( (e: React.CompositionEvent, block: CRDTBlock) => { @@ -294,6 +162,18 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData [editorCRDT, pageId, sendCharInsertOperation], ); + const addNewBlock = () => { + if (!editorCRDT) return; + const index = editorCRDT.current.LinkedList.spread().length; + const operation = editorCRDT.current.localInsert(index, ""); + editorCRDT.current.currentBlock = operation.node; + sendBlockInsertOperation({ node: operation.node, pageId }); + setEditorState(() => ({ + clock: editorCRDT.current.clock, + linkedList: editorCRDT.current.LinkedList, + })); + }; + const subscriptionRef = useRef(false); useEffect(() => { @@ -410,18 +290,6 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData }; }, [editorCRDT, subscribeToRemoteOperations, pageId]); - const addNewBlock = () => { - if (!editorCRDT) return; - const index = editorCRDT.current.LinkedList.spread().length; - const operation = editorCRDT.current.localInsert(index, ""); - editorCRDT.current.currentBlock = operation.node; - sendBlockInsertOperation({ node: operation.node, pageId }); - setEditorState(() => ({ - clock: editorCRDT.current.clock, - linkedList: editorCRDT.current.LinkedList, - })); - }; - // 로딩 상태 체크 if (!editorCRDT || !editorState) { return
Loading editor data...
; diff --git a/client/src/features/editor/hooks/useBlockOperation.ts b/client/src/features/editor/hooks/useBlockOperation.ts new file mode 100644 index 00000000..18f08547 --- /dev/null +++ b/client/src/features/editor/hooks/useBlockOperation.ts @@ -0,0 +1,172 @@ +import { EditorCRDT } from "@noctaCrdt/Crdt"; +import { RemoteCharInsertOperation } from "@noctaCrdt/Interfaces"; +import { Block } from "@noctaCrdt/Node"; +import { BlockId } from "@noctaCrdt/NodeId"; +import { useCallback } from "react"; +import { useSocketStore } from "@src/stores/useSocketStore"; +import { getAbsoluteCaretPosition } from "@src/utils/caretUtils"; +import { EditorStateProps } from "../Editor"; +import { getTextOffset } from "../utils/domSyncUtils"; + +interface UseBlockOperationProps { + editorCRDT: EditorCRDT; + pageId: string; + setEditorState: React.Dispatch>; + onKeyDown: (e: React.KeyboardEvent) => void; + handleHrInput: (block: Block, content: string) => boolean; +} + +export const useBlockOperation = ({ + editorCRDT, + pageId, + setEditorState, + onKeyDown, + handleHrInput, +}: UseBlockOperationProps) => { + const { sendCharInsertOperation, sendCharDeleteOperation } = useSocketStore(); + + const handleBlockClick = useCallback( + (blockId: BlockId, e: React.MouseEvent) => { + if (editorCRDT) { + const selection = window.getSelection(); + if (!selection) return; + + const clickedElement = (e.target as HTMLElement).closest( + '[contenteditable="true"]', + ) as HTMLDivElement; + if (!clickedElement) return; + + editorCRDT.currentBlock = editorCRDT.LinkedList.nodeMap[JSON.stringify(blockId)]; + const caretPosition = getAbsoluteCaretPosition(clickedElement); + + // 계산된 캐럿 위치 저장 + editorCRDT.currentBlock.crdt.currentCaret = caretPosition; + } + }, + [editorCRDT], + ); + + const handleBlockInput = useCallback( + (e: React.FormEvent, block: Block) => { + if (!block || !editorCRDT) return; + if ((e.nativeEvent as InputEvent).isComposing) { + return; + } + + let operationNode; + const element = e.currentTarget; + const newContent = element.textContent || ""; + const currentContent = block.crdt.read(); + const caretPosition = getAbsoluteCaretPosition(element); + + if (handleHrInput(block, newContent)) { + return; + } + + if (newContent.length > currentContent.length) { + let charNode: RemoteCharInsertOperation; + // 캐럿 위치 유효성 검사 + const validCaretPosition = Math.min(Math.max(0, caretPosition), currentContent.length); + // 맨 앞에 삽입 + if (caretPosition === 0) { + const [addedChar] = newContent; + charNode = block.crdt.localInsert(0, addedChar, block.id, pageId); + } else if (caretPosition > currentContent.length) { + // 맨 뒤에 삽입 + let prevChar; + if (currentContent.length > 0) { + prevChar = editorCRDT.currentBlock?.crdt.LinkedList.findByIndex( + currentContent.length - 1, + ); + } + const addedChar = newContent[newContent.length - 1]; + charNode = block.crdt.localInsert( + currentContent.length, + addedChar, + block.id, + pageId, + prevChar ? prevChar.style : [], + prevChar ? prevChar.color : undefined, + prevChar ? prevChar.backgroundColor : undefined, + ); + } else { + // 중간에 삽입 + const prevChar = editorCRDT.currentBlock?.crdt.LinkedList.findByIndex( + validCaretPosition - 1, + ); + const addedChar = newContent[validCaretPosition - 1]; + charNode = block.crdt.localInsert( + validCaretPosition - 1, + addedChar, + block.id, + pageId, + prevChar?.style, + prevChar?.color, + prevChar?.backgroundColor, + ); + } + editorCRDT.currentBlock!.crdt.currentCaret = caretPosition; + sendCharInsertOperation({ node: charNode.node, blockId: block.id, pageId }); + } else if (newContent.length < currentContent.length) { + // 문자가 삭제된 경우 + // 삭제 위치 계산 + const deletePosition = Math.max(0, caretPosition); + if (deletePosition >= 0 && deletePosition < currentContent.length) { + operationNode = block.crdt.localDelete(deletePosition, block.id, pageId); + sendCharDeleteOperation(operationNode); + + // 캐럿 위치 업데이트 + editorCRDT.currentBlock!.crdt.currentCaret = deletePosition; + } + } + setEditorState({ + clock: editorCRDT.clock, + linkedList: editorCRDT.LinkedList, + }); + }, + [sendCharInsertOperation, sendCharDeleteOperation, editorCRDT, pageId], + ); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent, blockRef: HTMLDivElement | null, block: Block) => { + if (!blockRef || !block) return; + const selection = window.getSelection(); + if (!selection || selection.isCollapsed || !blockRef) { + // 선택된 텍스트가 없으면 기존 onKeyDown 로직 실행 + onKeyDown(e); + return; + } + + if (e.key === "Backspace") { + e.preventDefault(); + + const range = selection.getRangeAt(0); + if (!blockRef.contains(range.commonAncestorContainer)) return; + + const startOffset = getTextOffset(blockRef, range.startContainer, range.startOffset); + const endOffset = getTextOffset(blockRef, range.endContainer, range.endOffset); + + // 선택된 범위의 문자들을 역순으로 삭제 + for (let i = endOffset - 1; i >= startOffset; i--) { + const operationNode = block.crdt.localDelete(i, block.id, pageId); + sendCharDeleteOperation(operationNode); + } + + block.crdt.currentCaret = startOffset; + setEditorState({ + clock: editorCRDT.clock, + linkedList: editorCRDT.LinkedList, + }); + } else { + onKeyDown(e); + } + }, + [editorCRDT.LinkedList, sendCharDeleteOperation, pageId, onKeyDown], + ); + + return { + handleBlockClick, + handleBlockInput, + handleKeyDown, + }; +}; From 0d4492d6f4c25cd53db23fd1e1bcb73bab0593d5 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 11:08:36 +0900 Subject: [PATCH 03/11] =?UTF-8?q?design:=20=EB=A9=94=EB=89=B4=20=EB=B8=94?= =?UTF-8?q?=EB=A1=9D=20=ED=95=AD=EC=83=81=20=EC=99=BC=EC=AA=BD=20=EC=83=81?= =?UTF-8?q?=EB=8B=A8=EC=97=90=20=EC=9C=84=EC=B9=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/editor/components/MenuBlock/MenuBlock.style.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts b/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts index 4e64099a..23635f7b 100644 --- a/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts +++ b/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts @@ -3,7 +3,9 @@ import { css } from "@styled-system/css"; export const menuBlockStyle = css({ display: "flex", zIndex: 1, - position: "relative", + position: "absolute", + top: 0, + left: 0, justifyContent: "center", alignItems: "center", width: "20px", From e1d68a516d5e9f44775b2cf0e47b59ca39f88e86 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 11:12:27 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=AA=A8=EB=8B=AC=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=EC=B2=98=EB=9F=BC=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8A=94=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sidebar/components/pageIconButton/PageIconModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx b/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx index 2fab2321..7ec74bf0 100644 --- a/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx +++ b/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx @@ -32,7 +32,7 @@ export const PageIconModal = ({ onClose, onSelect, currentType }: PageIconModalP marginBottom: "12px", })} > -

{group.title}

+ {/*

{group.title}

*/}
Date: Wed, 27 Nov 2024 11:30:46 +0900 Subject: [PATCH 05/11] =?UTF-8?q?design:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EB=AA=A8=EB=8B=AC=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pageIconButton/PageIconButton.style.ts | 3 +-- .../pageIconButton/PageIconModal.tsx | 8 +------- .../pageIconButton/pageIconModal.style.ts | 20 ++++++++++--------- .../components/pageItem/PageItem.style.ts | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts b/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts index 00f97c52..3901bf9e 100644 --- a/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts +++ b/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts @@ -11,8 +11,7 @@ export const IconBox = css({ cursor: "pointer", "&:hover": { - transform: "scale(1.05)", - opacity: 0.8, + transform: "scale(1.1)", }, }); diff --git a/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx b/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx index 7ec74bf0..962a76da 100644 --- a/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx +++ b/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx @@ -2,13 +2,7 @@ import { PageIconType } from "@noctaCrdt/Interfaces"; import { RiCloseLine } from "react-icons/ri"; import { iconGroups, iconComponents } from "@constants/PageIconButton.config"; import { css } from "@styled-system/css"; -import { - IconModal, - IconModalContainer, - IconModalClose, - IconName, - IconButton, -} from "./pageIconModal.style"; +import { IconModal, IconModalContainer, IconModalClose, IconButton } from "./pageIconModal.style"; export interface PageIconModalProps { isOpen: boolean; diff --git a/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts b/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts index 563ccb97..ae892eeb 100644 --- a/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts +++ b/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts @@ -13,7 +13,7 @@ export const IconModalContainer = css({ borderRadius: "4px", width: "100%", maxHeight: "80vh", - padding: "16px", + padding: "28px 16px 12px 16px", backgroundColor: "white", boxShadow: "lg", overflowY: "auto", @@ -22,18 +22,19 @@ export const IconModalContainer = css({ export const IconModalClose = css({ display: "flex", position: "absolute", - top: "8px", - right: "8px", + top: "4px", + right: "4px", justifyContent: "center", alignItems: "center", border: "none", borderRadius: "md", - width: "24px", - height: "24px", + width: "32px", + height: "32px", + opacity: 0.5, backgroundColor: "transparent", cursor: "pointer", _hover: { - backgroundColor: "gray.100", + opacity: 1, }, }); @@ -52,11 +53,12 @@ export const IconButton = (isSelected: boolean) => justifyContent: "space-between", alignItems: "center", border: "none", - borderRadius: "md", + borderRadius: "4px", padding: "8px", - backgroundColor: isSelected ? "gray.100" : "transparent", + backgroundColor: isSelected ? "rgba(220, 215, 255, 0.65)" : "transparent", + transition: "all 0.1s ease-in-out", cursor: "pointer", _hover: { - backgroundColor: isSelected ? "gray.100" : "gray.50", + transform: "translateY(-2px) scale(1.1)", }, }); diff --git a/client/src/components/sidebar/components/pageItem/PageItem.style.ts b/client/src/components/sidebar/components/pageItem/PageItem.style.ts index 16d15009..24f0c904 100644 --- a/client/src/components/sidebar/components/pageItem/PageItem.style.ts +++ b/client/src/components/sidebar/components/pageItem/PageItem.style.ts @@ -3,7 +3,7 @@ import { css } from "@styled-system/css"; export const pageItemContainer = css({ display: "flex", position: "relative", - gap: "sm", + gap: "lg", alignItems: "center", width: "100%", height: "56px", From 9b15e8611f92e243eecdb4ef742cb7e0a82e5db3 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 11:45:42 +0900 Subject: [PATCH 06/11] =?UTF-8?q?design:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8B=80=EC=97=90=EB=8F=84=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/features/page/Page.tsx | 3 ++- .../page/components/PageTitle/PageTitle.style.ts | 10 ++++++++++ .../page/components/PageTitle/PageTitle.tsx | 15 ++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/client/src/features/page/Page.tsx b/client/src/features/page/Page.tsx index 2b0948ee..fdab49e4 100644 --- a/client/src/features/page/Page.tsx +++ b/client/src/features/page/Page.tsx @@ -25,6 +25,7 @@ export const Page = ({ y, title, zIndex, + icon, isActive, handlePageSelect, handlePageClose, @@ -71,7 +72,7 @@ export const Page = ({ onPointerDown={handlePageClick} >
- + handlePageClose(id)} onPageMaximize={pageMaximize} diff --git a/client/src/features/page/components/PageTitle/PageTitle.style.ts b/client/src/features/page/components/PageTitle/PageTitle.style.ts index f0d967cd..4fe1ed80 100644 --- a/client/src/features/page/components/PageTitle/PageTitle.style.ts +++ b/client/src/features/page/components/PageTitle/PageTitle.style.ts @@ -1,7 +1,17 @@ import { css } from "@styled-system/css"; +export const pageTitleContainer = css({ + display: "flex", + gap: "8px", + flexDirection: "row", + alignItems: "center", +}); + export const pageTitle = css({ textStyle: "display-medium24", + display: "flex", + alignItems: "center", + paddingTop: "3px", color: "gray.500", textOverflow: "ellipsis", overflow: "hidden", diff --git a/client/src/features/page/components/PageTitle/PageTitle.tsx b/client/src/features/page/components/PageTitle/PageTitle.tsx index ca24fe41..03b3e890 100644 --- a/client/src/features/page/components/PageTitle/PageTitle.tsx +++ b/client/src/features/page/components/PageTitle/PageTitle.tsx @@ -1,9 +1,18 @@ -import { pageTitle } from "./PageTitle.style"; +import { PageIconType } from "@noctaCrdt/Interfaces"; +import { iconComponents } from "@src/constants/PageIconButton.config"; +import { pageTitleContainer, pageTitle } from "./PageTitle.style"; interface PageTitleProps { title: string; + icon: PageIconType; } -export const PageTitle = ({ title }: PageTitleProps) => { - return

{title || "Title"}

; +export const PageTitle = ({ title, icon }: PageTitleProps) => { + const { icon: IconComponent, color } = iconComponents[icon]; + return ( +
+ +

{title || "Title"}

+
+ ); }; From 3e9d2481fb7748e03724085241db6f1c01719b7b Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 11:54:22 +0900 Subject: [PATCH 07/11] =?UTF-8?q?design:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=20=EB=B2=84=ED=8A=BC=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PageControlButton/PageControlButton.style.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/features/page/components/PageControlButton/PageControlButton.style.ts b/client/src/features/page/components/PageControlButton/PageControlButton.style.ts index 417da660..e064c6f8 100644 --- a/client/src/features/page/components/PageControlButton/PageControlButton.style.ts +++ b/client/src/features/page/components/PageControlButton/PageControlButton.style.ts @@ -13,6 +13,9 @@ export const pageControlContainer = css({ export const pageControlButton = cva({ base: { + display: "flex", + justifyContent: "center", + alignItems: "center", borderRadius: "full", width: "20px", height: "20px", @@ -30,6 +33,8 @@ export const pageControlButton = cva({ export const iconBox = css({ transform: "scale(0.8)", strokeWidth: "2.5px", + width: "14px", + height: "14px", color: "white/90", opacity: 0, transition: "all 0.1s ease", From 6c733c76a47578ee9be1ff15139d40aaad51f85a Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 11:56:13 +0900 Subject: [PATCH 08/11] =?UTF-8?q?chore:=20lint=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/features/editor/Editor.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index 0db3e2d3..53def475 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -3,7 +3,6 @@ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable" import { EditorCRDT } from "@noctaCrdt/Crdt"; import { BlockLinkedList } from "@noctaCrdt/LinkedList"; import { Block as CRDTBlock } from "@noctaCrdt/Node"; -import { BlockId } from "@noctaCrdt/NodeId"; import { serializedEditorDataProps } from "node_modules/@noctaCrdt/Interfaces.ts"; import { useRef, useState, useCallback, useEffect, useMemo } from "react"; import { useSocketStore } from "@src/stores/useSocketStore.ts"; From 55a4885ce952fde035f03a7209cde6a2f02353ca Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 12:49:24 +0900 Subject: [PATCH 09/11] =?UTF-8?q?design:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20padding=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/pageIconButton/pageIconModal.style.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts b/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts index ae892eeb..cab526b6 100644 --- a/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts +++ b/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts @@ -4,7 +4,7 @@ export const IconModal = css({ zIndex: 9000, position: "absolute", top: 14, - left: 14, + left: 5, }); export const IconModalContainer = css({ @@ -13,7 +13,7 @@ export const IconModalContainer = css({ borderRadius: "4px", width: "100%", maxHeight: "80vh", - padding: "28px 16px 12px 16px", + padding: "16px 16px 4px 16px", backgroundColor: "white", boxShadow: "lg", overflowY: "auto", @@ -21,9 +21,10 @@ export const IconModalContainer = css({ export const IconModalClose = css({ display: "flex", + zIndex: 1002, position: "absolute", - top: "4px", - right: "4px", + top: "2px", + right: "2px", justifyContent: "center", alignItems: "center", border: "none", @@ -55,7 +56,7 @@ export const IconButton = (isSelected: boolean) => border: "none", borderRadius: "4px", padding: "8px", - backgroundColor: isSelected ? "rgba(220, 215, 255, 0.65)" : "transparent", + backgroundColor: isSelected ? "rgba(220, 215, 255, 0.35)" : "transparent", transition: "all 0.1s ease-in-out", cursor: "pointer", _hover: { From f90607c7ce91776fe142b7069fe490099ca17522 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 13:17:28 +0900 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20remote=20=EC=97=B0=EC=82=B0?= =?UTF-8?q?=EC=8B=9C=20=EC=88=9C=EC=84=9C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/features/editor/Editor.tsx | 19 +++++++++++++++++++ .../editor/hooks/useMarkdownGrammer.ts | 3 +++ 2 files changed, 22 insertions(+) diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index 53def475..f888dc0b 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -200,7 +200,13 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData onRemoteBlockInsert: (operation) => { console.log(operation, "block : 입력 확인합니다이"); if (operation.pageId !== pageId) return; + const prevBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.node.prev)]; editorCRDT.current.remoteInsert(operation); + if (prevBlock.type === "ol") { + editorCRDT.current.LinkedList.updateAllOrderedListIndices(); + console.log("ol update"); + } setEditorState({ clock: editorCRDT.current.clock, linkedList: editorCRDT.current.LinkedList, @@ -210,7 +216,14 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData onRemoteBlockDelete: (operation) => { console.log(operation, "block : 삭제 확인합니다이"); if (operation.pageId !== pageId) return; + const targetBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.targetId)]; + const prevBlock = editorCRDT.current.LinkedList.nodeMap[JSON.stringify(targetBlock.prev)]; + const nextBlock = editorCRDT.current.LinkedList.nodeMap[JSON.stringify(targetBlock.next)]; editorCRDT.current.remoteDelete(operation); + if (prevBlock.type === "ol" && nextBlock.type === "ol") { + editorCRDT.current.LinkedList.updateAllOrderedListIndices(); + } setEditorState({ clock: editorCRDT.current.clock, linkedList: editorCRDT.current.LinkedList, @@ -248,7 +261,12 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData onRemoteBlockUpdate: (operation) => { console.log(operation, "block : 업데이트 확인합니다이"); if (operation.pageId !== pageId) return; + const prevBlock = + editorCRDT.current.LinkedList.nodeMap[JSON.stringify(operation.node.prev)]; editorCRDT.current.remoteUpdate(operation.node, operation.pageId); + if (prevBlock.type === "ol") { + editorCRDT.current.LinkedList.updateAllOrderedListIndices(); + } setEditorState({ clock: editorCRDT.current.clock, linkedList: editorCRDT.current.LinkedList, @@ -259,6 +277,7 @@ export const Editor = ({ onTitleChange, pageId, pageTitle, serializedEditorData console.log(operation, "block : 재정렬 확인합니다이"); if (operation.pageId !== pageId) return; editorCRDT.current.remoteReorder(operation); + editorCRDT.current.LinkedList.updateAllOrderedListIndices(); setEditorState({ clock: editorCRDT.current.clock, linkedList: editorCRDT.current.LinkedList, diff --git a/client/src/features/editor/hooks/useMarkdownGrammer.ts b/client/src/features/editor/hooks/useMarkdownGrammer.ts index 120929d2..8a308054 100644 --- a/client/src/features/editor/hooks/useMarkdownGrammer.ts +++ b/client/src/features/editor/hooks/useMarkdownGrammer.ts @@ -115,6 +115,9 @@ export const useMarkdownGrammer = ({ // 새 블록 생성 const operation = createNewBlock(currentIndex + 1); operation.node.crdt = new BlockCRDT(editorCRDT.client); + if (currentBlock.type === "ol") { + operation.node.listIndex = currentBlock.listIndex! + 1; + } operation.node.indent = currentBlock.indent; sendBlockInsertOperation({ node: operation.node, pageId }); From c7c2cb33efad7047d4148e17ccabb5ff1b5765e5 Mon Sep 17 00:00:00 2001 From: Ludovico7 Date: Wed, 27 Nov 2024 17:06:19 +0900 Subject: [PATCH 11/11] =?UTF-8?q?design:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=ED=95=B4=EC=84=9C=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=84=A4=EB=AA=85=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pageIconButton/PageIconButton.style.ts | 4 +- .../pageIconButton/PageIconModal.tsx | 11 +---- .../pageIconButton/pageIconModal.style.ts | 6 +-- .../sidebar/components/pageItem/PageItem.tsx | 3 +- client/src/constants/PageIconButton.config.ts | 40 +++++++++---------- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts b/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts index 3901bf9e..5299173c 100644 --- a/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts +++ b/client/src/components/sidebar/components/pageIconButton/PageIconButton.style.ts @@ -8,10 +8,10 @@ export const IconBox = css({ borderRadius: "4px", width: "24px", height: "24px", - + transition: "all 0.1s ease-in-out", cursor: "pointer", "&:hover": { - transform: "scale(1.1)", + transform: "translateY(-2px) scale(1.1)", }, }); diff --git a/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx b/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx index 962a76da..d36c0f09 100644 --- a/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx +++ b/client/src/components/sidebar/components/pageIconButton/PageIconModal.tsx @@ -26,11 +26,10 @@ export const PageIconModal = ({ onClose, onSelect, currentType }: PageIconModalP marginBottom: "12px", })} > - {/*

{group.title}

*/}
@@ -45,14 +44,6 @@ export const PageIconModal = ({ onClose, onSelect, currentType }: PageIconModalP className={IconButton(isSelected)} > - - {iconType} - ); })} diff --git a/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts b/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts index cab526b6..8c763481 100644 --- a/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts +++ b/client/src/components/sidebar/components/pageIconButton/pageIconModal.style.ts @@ -13,7 +13,7 @@ export const IconModalContainer = css({ borderRadius: "4px", width: "100%", maxHeight: "80vh", - padding: "16px 16px 4px 16px", + padding: "16px 16px 0px 16px", backgroundColor: "white", boxShadow: "lg", overflowY: "auto", @@ -23,8 +23,8 @@ export const IconModalClose = css({ display: "flex", zIndex: 1002, position: "absolute", - top: "2px", - right: "2px", + top: "-4px", + right: "-4px", justifyContent: "center", alignItems: "center", border: "none", diff --git a/client/src/components/sidebar/components/pageItem/PageItem.tsx b/client/src/components/sidebar/components/pageItem/PageItem.tsx index 97930591..c1f43d53 100644 --- a/client/src/components/sidebar/components/pageItem/PageItem.tsx +++ b/client/src/components/sidebar/components/pageItem/PageItem.tsx @@ -44,8 +44,9 @@ export const PageItem = ({ } }; - const handleCloseModal = () => { + const handleCloseModal = (e: React.MouseEvent) => { closeModal(); + e.stopPropagation(); }; const handleSelectIcon = (e: React.MouseEvent, type: PageIconType) => { diff --git a/client/src/constants/PageIconButton.config.ts b/client/src/constants/PageIconButton.config.ts index e2a88c9d..2a868b40 100644 --- a/client/src/constants/PageIconButton.config.ts +++ b/client/src/constants/PageIconButton.config.ts @@ -1,5 +1,7 @@ import { PageIconType } from "@noctaCrdt/Interfaces"; import { IconType } from "react-icons"; +import { CgGym } from "react-icons/cg"; +import { MdOutlinePlace } from "react-icons/md"; import { // 기본 문서 타입 RiFileTextLine, // docs: 일반 문서 @@ -14,16 +16,14 @@ import { // 개인 활동 RiBookMarkedLine, // diary: 일기/저널 RiQuillPenLine, // blog: 블로그 - RiYoutubeLine, // entertain: 엔터테인먼트 // 학습 관련 RiBookOpenLine, // study: 학습 RiSearchLine, // research: 연구/조사 - RiBookLine, // book: 독서 + RiBookmarkLine, // 협업 관련 RiGroupLine, // team: 팀 문서 - RiShareLine, // shared: 공유 문서 RiDiscussLine, // feedback: 피드백 RiAddFill, // plus: 추가 } from "react-icons/ri"; @@ -41,67 +41,67 @@ export const iconComponents: Record = { }, Note: { icon: RiStickyNoteLine, - color: "#FEA642", // YELLOW - 메모는 노란색 + color: "#FEA642", }, Wiki: { icon: RiBookReadLine, - color: "#A142FE", // PURPLE - 위키는 보라색 + color: "#A142FE", }, // 업무 관련 Project: { icon: RiProjectorLine, - color: "#1BBF44", // GREEN - 프로젝트는 초록색 + color: "#1BBF44", }, Meeting: { icon: RiTeamLine, - color: "#4E637C", // GRAY_700 - 회의는 진중한 느낌의 회색 + color: "#4E637C", }, Task: { icon: RiTaskLine, - color: "#F24150", // RED - 태스크는 주목성 높은 빨간색 + color: "#F24150", }, // 개인 활동 Diary: { icon: RiBookMarkedLine, - color: "#FF69B4", // 연한 분홍색 - 개인적/감성적인 느낌 + color: "#FF69B4", }, Blog: { icon: RiQuillPenLine, - color: "#99AFCA", // GRAY_300 - 글쓰기는 차분한 회색 + color: "#99AFCA", }, Entertain: { - icon: RiYoutubeLine, - color: "#F24150", // RED - 엔터테인먼트는 활동적인 빨간색 + icon: CgGym, + color: "#9ACD32", }, // 학습 관련 Study: { icon: RiBookOpenLine, - color: "#4285F4", // BLUE - 학습은 신뢰감 있는 파란색 + color: "#4285F4", }, Research: { icon: RiSearchLine, - color: "#2B4158", // GRAY_900 - 연구는 깊이 있는 진한 회색 + color: "#2B4158", }, Book: { - icon: RiBookLine, - color: "#8B4513", // BROWN - 독서는 책을 연상시키는 갈색 + icon: RiBookmarkLine, + color: "#8B4513", }, // 협업 관련 Team: { icon: RiGroupLine, - color: "#1BBF44", // GREEN - 팀워크는 긍정적인 초록색 + color: "#FF8C00", }, Shared: { - icon: RiShareLine, - color: "#4285F4", // BLUE - 공유는 소통을 의미하는 파란색 + icon: MdOutlinePlace, + color: "#4285F4", }, Feedback: { icon: RiDiscussLine, - color: "#A142FE", // PURPLE - 피드백은 창의적인 보라색 + color: "#A142FE", }, plus: {