From 3b616a10f1e882ed3965c8ba21e803056e32e027 Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Fri, 22 Nov 2024 23:09:46 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20=ED=95=9C=EA=B8=80=20enter=EC=8B=9C?= =?UTF-8?q?=202=EA=B0=9C=20=EB=B8=94=EB=A1=9D=EC=9D=B4=20=EC=83=9D?= =?UTF-8?q?=EA=B8=B0=EB=8A=94=20=EB=AC=B8=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 #171 --- client/src/features/editor/hooks/useMarkdownGrammer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/features/editor/hooks/useMarkdownGrammer.ts b/client/src/features/editor/hooks/useMarkdownGrammer.ts index 13604942..5d66022c 100644 --- a/client/src/features/editor/hooks/useMarkdownGrammer.ts +++ b/client/src/features/editor/hooks/useMarkdownGrammer.ts @@ -71,6 +71,7 @@ export const useMarkdownGrammer = ({ switch (e.key) { case "Enter": { + if (e.nativeEvent.isComposing) return; e.preventDefault(); const caretPosition = currentBlock.crdt.currentCaret; const currentContent = currentBlock.crdt.read(); From 3192f6156e2cabda0a91e496f6167ab4555c5060 Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Fri, 22 Nov 2024 23:10:05 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=ED=95=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=95=A9=EB=AC=B8=EC=9E=90=EB=8F=84=20crdt=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=EB=90=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #171 --- client/src/features/editor/Editor.tsx | 26 +++++++++++++++++++ .../editor/components/block/Block.tsx | 3 +++ 2 files changed, 29 insertions(+) diff --git a/client/src/features/editor/Editor.tsx b/client/src/features/editor/Editor.tsx index 42956749..0af75b70 100644 --- a/client/src/features/editor/Editor.tsx +++ b/client/src/features/editor/Editor.tsx @@ -120,6 +120,10 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr const handleBlockInput = useCallback( (e: React.FormEvent, block: CRDTBlock) => { if (!block) return; + if ((e.nativeEvent as InputEvent).isComposing) { + return; + } + let operationNode; const element = e.currentTarget; const newContent = element.textContent || ""; @@ -159,6 +163,27 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr [sendCharInsertOperation, sendCharDeleteOperation], ); + const handleCompositionEnd = (e: React.CompositionEvent, block: CRDTBlock) => { + const event = e.nativeEvent as CompositionEvent; + const characters = [...event.data]; + const selection = window.getSelection(); + const caretPosition = selection?.focusOffset || 0; + const startPosition = caretPosition - characters.length; + + characters.forEach((char, index) => { + const insertPosition = startPosition + index; + const charNode = block.crdt.localInsert(insertPosition, char, block.id, pageId); + + sendCharInsertOperation({ + node: charNode.node, + blockId: block.id, + pageId, + }); + }); + + block.crdt.currentCaret = caretPosition; + }; + const subscriptionRef = useRef(false); useEffect(() => { @@ -286,6 +311,7 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr block={block} isActive={block.id === editorState.currentBlock} onInput={handleBlockInput} + onCompositionEnd={handleCompositionEnd} onKeyDown={handleKeyDown} onClick={handleBlockClick} onAnimationSelect={handleAnimationSelect} diff --git a/client/src/features/editor/components/block/Block.tsx b/client/src/features/editor/components/block/Block.tsx index 3baa4827..897c8b57 100644 --- a/client/src/features/editor/components/block/Block.tsx +++ b/client/src/features/editor/components/block/Block.tsx @@ -16,6 +16,7 @@ interface BlockProps { block: CRDTBlock; isActive: boolean; onInput: (e: React.FormEvent, block: CRDTBlock) => void; + onCompositionEnd: (e: React.CompositionEvent, block: CRDTBlock) => void; onKeyDown: (e: React.KeyboardEvent) => void; onClick: (blockId: BlockId, e: React.MouseEvent) => void; onAnimationSelect: (blockId: BlockId, animation: AnimationType) => void; @@ -30,6 +31,7 @@ export const Block: React.FC = memo( block, isActive, onInput, + onCompositionEnd, onKeyDown, onClick, onAnimationSelect, @@ -127,6 +129,7 @@ export const Block: React.FC = memo( ref={blockRef} onKeyDown={onKeyDown} onInput={handleInput} + onCompositionEnd={(e) => onCompositionEnd(e, block)} onClick={(e) => onClick(block.id, e)} contentEditable suppressContentEditableWarning From 42cdca50590446eec0eac9de1ece1eec6cf86262 Mon Sep 17 00:00:00 2001 From: hyonun321 Date: Sat, 23 Nov 2024 14:15:20 +0900 Subject: [PATCH 3/6] =?UTF-8?q?design:=20=EB=B8=94=EB=A1=9D=EC=97=90=20?= =?UTF-8?q?=EB=A7=88=EC=9A=B0=EC=8A=A4=EB=A5=BC=20=ED=98=B8=EB=B2=84?= =?UTF-8?q?=ED=96=88=EC=9D=84=EB=95=8C=20menuBlock=EC=9A=94=EC=86=8C?= =?UTF-8?q?=EA=B0=80=20=EB=B3=B4=EC=9D=B4=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OptionModal이 켜진 상태에도 menuBlock요소가 보이도록 수정 #042 --- .../editor/components/MenuBlock/MenuBlock.style.ts | 4 +--- .../src/features/editor/components/MenuBlock/MenuBlock.tsx | 7 ++++++- client/src/features/editor/components/block/Block.style.ts | 3 +++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts b/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts index 1dfa9696..4e64099a 100644 --- a/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts +++ b/client/src/features/editor/components/MenuBlock/MenuBlock.style.ts @@ -9,11 +9,9 @@ export const menuBlockStyle = css({ width: "20px", height: "20px", marginLeft: "-20px", + opacity: 0, transition: "opacity 0.2s ease-in-out", cursor: "grab", - _groupHover: { - opacity: 1, - }, _active: { cursor: "grabbing", }, diff --git a/client/src/features/editor/components/MenuBlock/MenuBlock.tsx b/client/src/features/editor/components/MenuBlock/MenuBlock.tsx index 9ea2ecfe..da273c62 100644 --- a/client/src/features/editor/components/MenuBlock/MenuBlock.tsx +++ b/client/src/features/editor/components/MenuBlock/MenuBlock.tsx @@ -71,7 +71,12 @@ export const MenuBlock = ({ }; return ( -
+
drag handle
diff --git a/client/src/features/editor/components/block/Block.style.ts b/client/src/features/editor/components/block/Block.style.ts index 7a744530..e91d4efe 100644 --- a/client/src/features/editor/components/block/Block.style.ts +++ b/client/src/features/editor/components/block/Block.style.ts @@ -8,6 +8,9 @@ const baseBlockStyle = { width: "full", minHeight: "16px", backgroundColor: "transparent", + "&:hover .menu_block, .menu_block.option_modal_open": { + opacity: 1, + }, }; export const blockContainerStyle = cva({ From 06044280a235e860db5d2e162bf460c029211ddd Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sun, 24 Nov 2024 07:26:38 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=208=EB=B0=A9=ED=96=A5=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #178 --- client/src/features/page/Page.style.ts | 98 ++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/client/src/features/page/Page.style.ts b/client/src/features/page/Page.style.ts index 563c3748..8483df13 100644 --- a/client/src/features/page/Page.style.ts +++ b/client/src/features/page/Page.style.ts @@ -26,11 +26,97 @@ export const pageHeader = css({ }, }); -export const resizeHandle = css({ +const baseResizeHandle = css({ + zIndex: 1, position: "absolute", - right: "-10px", - bottom: "-10px", - width: "32px", - height: "32px", - cursor: "se-resize", }); + +export const resizeHandles = { + top: cx( + baseResizeHandle, + css({ + top: "-5px", + left: "5px", + right: "5px", + height: "10px", + cursor: "n-resize", + }), + ), + + bottom: cx( + baseResizeHandle, + css({ + left: "5px", + right: "5px", + bottom: "-5px", + height: "10px", + cursor: "s-resize", + }), + ), + + left: cx( + baseResizeHandle, + css({ + top: "5px", + left: "-5px", + bottom: "5px", + width: "10px", + cursor: "w-resize", + }), + ), + + right: cx( + baseResizeHandle, + css({ + top: "5px", + right: "-5px", + bottom: "5px", + width: "10px", + cursor: "e-resize", + }), + ), + + topLeft: cx( + baseResizeHandle, + css({ + top: "-10px", + left: "-10px", + width: "24px", + height: "24px", + cursor: "nw-resize", + }), + ), + + topRight: cx( + baseResizeHandle, + css({ + top: "-10px", + right: "-10px", + width: "24px", + height: "24px", + cursor: "ne-resize", + }), + ), + + bottomLeft: cx( + baseResizeHandle, + css({ + left: "-10px", + bottom: "-10px", + width: "24px", + height: "24px", + cursor: "sw-resize", + }), + ), + + bottomRight: cx( + baseResizeHandle, + css({ + right: "-10px", + bottom: "-10px", + width: "24px", + height: "24px", + cursor: "se-resize", + }), + ), +}; From bd7d1b6beebbc2eb291768ac9df91574533ff277 Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sun, 24 Nov 2024 07:26:49 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=208=20=EB=B0=A9=ED=96=A5=EC=97=90=20?= =?UTF-8?q?=EB=A6=AC=EC=82=AC=EC=9D=B4=EC=A7=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #178 --- client/src/features/page/Page.tsx | 29 +++-- client/src/features/page/hooks/usePage.ts | 122 ++++++++++++++++++++-- 2 files changed, 124 insertions(+), 27 deletions(-) diff --git a/client/src/features/page/Page.tsx b/client/src/features/page/Page.tsx index 23bf3d15..a376a5fd 100644 --- a/client/src/features/page/Page.tsx +++ b/client/src/features/page/Page.tsx @@ -2,12 +2,10 @@ import { serializedEditorDataProps } from "@noctaCrdt/Interfaces"; import { motion, AnimatePresence } from "framer-motion"; import { Editor } from "@features/editor/Editor"; import { Page as PageType } from "@src/types/page"; -import { pageAnimation, resizeHandleAnimation } from "./Page.animation"; -import { pageContainer, pageHeader, resizeHandle } from "./Page.style"; - +import { pageContainer, pageHeader, resizeHandles } from "./Page.style"; import { PageControlButton } from "./components/PageControlButton/PageControlButton"; import { PageTitle } from "./components/PageTitle/PageTitle"; -import { usePage } from "./hooks/usePage"; +import { DIRECTIONS, usePage } from "./hooks/usePage"; interface PageProps extends PageType { handlePageSelect: ({ pageId, isSidebar }: { pageId: string; isSidebar?: boolean }) => void; @@ -45,17 +43,12 @@ export const Page = ({ return ( - - - + {DIRECTIONS.map((direction) => ( + pageResize(e, direction)} + /> + ))} +
); }; diff --git a/client/src/features/page/hooks/usePage.ts b/client/src/features/page/hooks/usePage.ts index 61cbf7b5..f9cb9a40 100644 --- a/client/src/features/page/hooks/usePage.ts +++ b/client/src/features/page/hooks/usePage.ts @@ -5,6 +5,18 @@ import { useIsSidebarOpen } from "@stores/useSidebarStore"; import { Position, Size } from "@src/types/page"; const PADDING = SPACING.MEDIUM * 2; +export const DIRECTIONS = [ + "top", + "bottom", + "left", + "right", + "topLeft", + "topRight", + "bottomLeft", + "bottomRight", +] as const; + +type Direction = (typeof DIRECTIONS)[number]; export const usePage = ({ x, y }: Position) => { const [position, setPosition] = useState({ x, y }); @@ -68,28 +80,118 @@ export const usePage = ({ x, y }: Position) => { document.addEventListener("pointerup", handleDragEnd); }; - const pageResize = (e: React.MouseEvent) => { + const pageResize = (e: React.MouseEvent, direction: Direction) => { e.preventDefault(); const startX = e.clientX; const startY = e.clientY; const startWidth = size.width; const startHeight = size.height; + const startPosition = { x: position.x, y: position.y }; const resize = (e: MouseEvent) => { const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; - const newWidth = Math.max( - PAGE.MIN_WIDTH, - Math.min(startWidth + deltaX, window.innerWidth - position.x - getSidebarWidth() - PADDING), - ); - - const newHeight = Math.max( - PAGE.MIN_HEIGHT, - Math.min(startHeight + deltaY, window.innerHeight - position.y - PADDING), - ); + let newWidth = startWidth; + let newHeight = startHeight; + let newX = startPosition.x; + let newY = startPosition.y; + + switch (direction) { + case "right": { + // startWidth + deltaX 가 계속해서 변하는 값. 사용자가 마우스를 움직이면서 계속해서 변함 + newWidth = Math.min( + window.innerWidth - startPosition.x - getSidebarWidth() - PADDING, // 최대 넓이를 지정하고 싶을때 Math.min + Math.max(PAGE.MIN_WIDTH, startWidth + deltaX), //최소 넓이를 지정하고 싶을때 Math.max + ); + break; + } + + case "left": { + newWidth = Math.min( + startPosition.x + startWidth, + Math.max(PAGE.MIN_WIDTH, startWidth - deltaX), + ); + newX = Math.max(0, startPosition.x + startWidth - newWidth); + break; + } + + case "bottom": { + newHeight = Math.min( + window.innerHeight - startPosition.y - PADDING, + Math.max(PAGE.MIN_HEIGHT, startHeight + deltaY), + ); + break; + } + + case "top": { + newHeight = Math.min( + startPosition.y + startHeight, + Math.max(PAGE.MIN_HEIGHT, startHeight - deltaY), + ); + newY = Math.max(0, startPosition.y + startHeight - newHeight); + break; + } + + case "topLeft": { + newHeight = Math.min( + startPosition.y + startHeight, + Math.max(PAGE.MIN_HEIGHT, startHeight - deltaY), + ); + newY = Math.max(0, startPosition.y + startHeight - newHeight); + + newWidth = Math.min( + startPosition.x + startWidth, + Math.max(PAGE.MIN_WIDTH, startWidth - deltaX), + ); + newX = Math.max(0, startPosition.x + startWidth - newWidth); + break; + } + + case "topRight": { + newHeight = Math.min( + startPosition.y + startHeight, + Math.max(PAGE.MIN_HEIGHT, startHeight - deltaY), + ); + newY = Math.max(0, startPosition.y + startHeight - newHeight); + + newWidth = Math.min( + window.innerWidth - startPosition.x - getSidebarWidth() - PADDING, + Math.max(PAGE.MIN_WIDTH, startWidth + deltaX), + ); + break; + } + + case "bottomLeft": { + newHeight = Math.min( + window.innerHeight - startPosition.y - PADDING, + Math.max(PAGE.MIN_HEIGHT, startHeight + deltaY), + ); + + newWidth = Math.min( + startPosition.x + startWidth, + Math.max(PAGE.MIN_WIDTH, startWidth - deltaX), + ); + newX = Math.max(0, startPosition.x + startWidth - newWidth); + break; + } + + case "bottomRight": { + newHeight = Math.min( + window.innerHeight - startPosition.y - PADDING, + Math.max(PAGE.MIN_HEIGHT, startHeight + deltaY), + ); + + newWidth = Math.min( + window.innerWidth - startPosition.x - getSidebarWidth() - PADDING, + Math.max(PAGE.MIN_WIDTH, startWidth + deltaX), + ); + break; + } + } setSize({ width: newWidth, height: newHeight }); + setPosition({ x: newX, y: newY }); }; const stopResize = () => { From fc2cda977a75d3274d795dc27ddc7dc9c3b0e81e Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sun, 24 Nov 2024 07:32:56 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #178 --- client/src/features/page/hooks/usePage.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/features/page/hooks/usePage.ts b/client/src/features/page/hooks/usePage.ts index f9cb9a40..088d6831 100644 --- a/client/src/features/page/hooks/usePage.ts +++ b/client/src/features/page/hooks/usePage.ts @@ -99,10 +99,9 @@ export const usePage = ({ x, y }: Position) => { switch (direction) { case "right": { - // startWidth + deltaX 가 계속해서 변하는 값. 사용자가 마우스를 움직이면서 계속해서 변함 newWidth = Math.min( - window.innerWidth - startPosition.x - getSidebarWidth() - PADDING, // 최대 넓이를 지정하고 싶을때 Math.min - Math.max(PAGE.MIN_WIDTH, startWidth + deltaX), //최소 넓이를 지정하고 싶을때 Math.max + window.innerWidth - startPosition.x - getSidebarWidth() - PADDING, + Math.max(PAGE.MIN_WIDTH, startWidth + deltaX), ); break; }