Skip to content

Commit

Permalink
feat: 체크박스 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Ludovico7 committed Nov 12, 2024
1 parent 6f4ab02 commit 6b1fb9b
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 19 deletions.
8 changes: 4 additions & 4 deletions client/src/components/block/Block.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,27 +43,27 @@ export const blockContainerStyle = cva({
ul: {
display: "block",
listStyleType: "disc",
listStylePosition: "inside",
listStylePosition: "outside",
},
ol: {
display: "block",
listStyleType: "decimal",
listStylePosition: "inside",
listStylePosition: "outside",
},
li: {
textStyle: "display-medium16",
display: "list-item",
outline: "none",
margin: "0",
padding: "0 0 0 spacing.md",
padding: "0 0 0 16px",
},
blockquote: {
borderLeft: "4px solid token(colors.gray.300)",
paddingLeft: "spacing.md",
color: "gray.500",
fontStyle: "italic",
},
input: {},
checkbox: {},
},
isActive: {
true: {
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/block/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const Block: React.FC<BlockProps> = memo(
}
};

const nodeType = node.type === "checkbox" ? "p" : node.type;

const commonProps = {
"data-node-id": node.id,
"data-depth": node.depth,
Expand All @@ -68,7 +70,7 @@ export const Block: React.FC<BlockProps> = memo(
}),
};

return React.createElement(node.type, commonProps);
return React.createElement(nodeType, commonProps);
},
);

Expand Down
21 changes: 21 additions & 0 deletions client/src/features/editor/Editor.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,24 @@ export const editorTitle = css({
color: "gray.300",
},
});

export const checkboxContainer = css({
display: "flex",
gap: "spacing.sm",
flexDirection: "row",
alignItems: "center",
});

export const checkbox = css({
border: "1px solid",
borderColor: "gray.300",
borderRadius: "4px",
width: "16px",
height: "16px",
margin: "0 8px 0 0",
cursor: "pointer",
"&:checked": {
borderColor: "blue.500",
backgroundColor: "blue.500",
},
});
38 changes: 36 additions & 2 deletions client/src/features/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { useCaretManager } from "@hooks/useCaretManager";
import { useKeyboardHandlers } from "@hooks/useMarkdownGrammer";
import { LinkedListBlock } from "@utils/linkedLIstBlock";
import { checkMarkdownPattern } from "@utils/markdownPatterns";
import { editorContainer, editorTitleContainer, editorTitle } from "./Editor.style";
import {
editorContainer,
editorTitleContainer,
editorTitle,
checkboxContainer,
checkbox,
} from "./Editor.style";

interface EditorProps {
onTitleChange: (title: string) => void;
Expand Down Expand Up @@ -81,7 +87,7 @@ export const Editor = ({ onTitleChange }: EditorProps) => {
const renderNodes = () => {
const nodes = editorList.traverseNodes();
return nodes.map((node) => {
if (node.type === "li") return null;
if (node.type === "li") return;
if (node.type === "ul" || node.type === "ol") {
const children = [];
let child = node.firstChild;
Expand Down Expand Up @@ -110,6 +116,34 @@ export const Editor = ({ onTitleChange }: EditorProps) => {
);
}

if (node.type === "checkbox") {
return (
<div key={node.id} className={checkboxContainer}>
<input
type="checkbox"
checked={node.attributes?.checked || false}
onChange={() => {}}
onClick={(e) => e.stopPropagation()}
className={checkbox}
/>
{node.firstChild && (
<Block
key={node.firstChild.id}
node={node.firstChild}
isActive={node.firstChild === editorState.currentNode}
contentRef={node.firstChild === editorState.currentNode ? contentRef : undefined}
currentNodeId={editorState.currentNode?.id || ""}
onKeyDown={handleKeyDown}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onInput={handleInput}
onClick={handleBlockClick}
/>
)}
</div>
);
}

return (
<Block
key={node.id}
Expand Down
20 changes: 18 additions & 2 deletions client/src/hooks/useMarkdownGrammer/handlers/arrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ export const useArrowKeyHandler = ({
let targetNode: EditorNode | null = null;
switch (e.key) {
case "ArrowUp":
if (currentNode.prevNode?.type === "ul" || currentNode.prevNode?.type === "ol") {
if (currentNode.parentNode?.type === "checkbox") {
if (currentNode.parentNode.prevNode) {
if (currentNode.parentNode.prevNode.type === "checkbox") {
targetNode = currentNode.parentNode.prevNode.firstChild;
} else {
targetNode = currentNode.parentNode.prevNode;
}
}
} else if (currentNode.prevNode?.type === "ul" || currentNode.prevNode?.type === "ol") {
targetNode = editorList.getLastChild(currentNode.prevNode!);
} else if (currentNode.type === "li") {
if (currentNode.prevSibling?.id) {
Expand All @@ -31,7 +39,15 @@ export const useArrowKeyHandler = ({
}
break;
case "ArrowDown":
if (currentNode.nextNode?.type === "ul" || currentNode.nextNode?.type === "ol") {
if (currentNode.parentNode?.type === "checkbox") {
if (currentNode.parentNode.nextNode) {
if (currentNode.parentNode.nextNode.type === "checkbox") {
targetNode = currentNode.parentNode.nextNode.firstChild;
} else {
targetNode = currentNode.parentNode.nextNode;
}
}
} else if (currentNode.nextNode?.type === "ul" || currentNode.nextNode?.type === "ol") {
targetNode = currentNode.nextNode!.firstChild;
} else if (currentNode.type === "li") {
if (currentNode.nextSibling?.id) {
Expand Down
69 changes: 66 additions & 3 deletions client/src/hooks/useMarkdownGrammer/handlers/backSpace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,60 @@ export const useBackspaceKeyHandler = ({
const { currentNode } = editorState;
if (!currentNode || currentNode.content.length > 0) return;
if (currentNode === editorState.rootNode && currentNode.type === "p") return;

e.preventDefault();
if (currentNode.type === "li") {
if (currentNode.parentNode?.type === "checkbox") {
const { parentNode } = currentNode;
const wasRoot = parentNode === editorState.rootNode;

let focusNode;
if (parentNode.prevNode?.type === "checkbox") {
// undefined가 될 수 있는 상황 체크
const prevFirstChild = parentNode.prevNode.firstChild;
if (!prevFirstChild) return;
focusNode = prevFirstChild;
} else {
focusNode = editorList.createNode("p", "", parentNode.prevNode, parentNode.nextNode);

// 포인터 관계 설정
if (parentNode.prevNode) {
parentNode.prevNode.nextNode = focusNode;
}
if (parentNode.nextNode) {
parentNode.nextNode.prevNode = focusNode;
}

if (wasRoot) {
editorList.root = focusNode;
}
}

// 관계 정리 순서 변경
// 1. 기존 연결 관계 정리
if (parentNode.nextNode) {
parentNode.nextNode.prevNode = parentNode.prevNode;
}
if (parentNode.prevNode) {
parentNode.prevNode.nextNode = parentNode.nextNode;
}

// 2. 부모-자식 관계 정리
currentNode.parentNode = null;
parentNode.firstChild = null;

// 3. 마지막으로 현재 노드의 관계 정리
parentNode.prevNode = null;
parentNode.nextNode = null;

// 노드 제거
editorList.removeNode(currentNode);
editorList.removeNode(parentNode);

setEditorState((prev) => ({
...prev,
rootNode: wasRoot ? focusNode : prev.rootNode,
currentNode: focusNode,
}));
} else if (currentNode.type === "li") {
const { parentNode } = currentNode;
if (!parentNode) return;

Expand Down Expand Up @@ -142,11 +193,23 @@ export const useBackspaceKeyHandler = ({
setEditorState((prev) => ({ ...prev }));
} else {
let focusNode;
if (currentNode.prevNode?.type === "ul" || currentNode.prevNode?.type === "ol") {
if (currentNode.prevNode?.type === "checkbox") {
// 이전 노드가 체크박스면 그 체크박스의 content 노드로 포커스
focusNode = currentNode.prevNode.firstChild;
} else if (currentNode.prevNode?.type === "ul" || currentNode.prevNode?.type === "ol") {
focusNode = editorList.getLastChild(currentNode.prevNode);
} else {
focusNode = currentNode.prevNode || currentNode.parentNode;
}

// 현재 노드 제거 전에 연결 관계 정리
if (currentNode.prevNode) {
currentNode.prevNode.nextNode = currentNode.nextNode;
}
if (currentNode.nextNode) {
currentNode.nextNode.prevNode = currentNode.prevNode;
}

editorList.removeNode(currentNode);

if (focusNode === editorState.rootNode) {
Expand Down
69 changes: 67 additions & 2 deletions client/src/hooks/useMarkdownGrammer/handlers/enter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,65 @@ export const useEnterKeyHandler = ({
const beforeText = content.slice(0, caretPosition);
const afterText = content.slice(caretPosition);

if (currentNode.type === "li") {
if (currentNode.parentNode?.type === "checkbox") {
const { parentNode } = currentNode;
if (content.length === 0) {
// 빈 체크박스에서 엔터 -> 일반 블록으로 전환
const newNode = editorList.createNode("p", "", parentNode.prevNode, parentNode.nextNode);

if (parentNode.prevNode) {
parentNode.prevNode.nextNode = newNode;
}
if (parentNode.nextNode) {
parentNode.nextNode.prevNode = newNode;
}

// root 노드 업데이트
if (parentNode === editorState.rootNode) {
editorList.root = newNode;
}

editorList.removeNode(currentNode);

setEditorState((prev) => ({
...prev,
rootNode: parentNode === prev.rootNode ? newNode : prev.rootNode,
currentNode: newNode,
}));
} else {
// 텍스트가 있는 체크박스에서 엔터 -> 새로운 체크박스 블록 생성
currentNode.content = beforeText;

// 새로운 체크박스 컨테이너와 컨텐츠 생성
const newContainer = editorList.createNode(
"checkbox",
"",
parentNode,
parentNode.nextNode,
);
const newContent = editorList.createNode("p", afterText, null, null, currentNode.depth);

// 새 체크박스의 관계 설정
newContainer.firstChild = newContent;
newContent.parentNode = newContainer;

// 기존 노드들과의 연결 설정
if (parentNode.nextNode) {
parentNode.nextNode.prevNode = newContainer;
}
parentNode.nextNode = newContainer;

// 기본 체크박스 속성 설정
newContainer.attributes = {
checked: false,
};

setEditorState((prev) => ({
...prev,
currentNode: newContent,
}));
}
} else if (currentNode.type === "li") {
const { parentNode } = currentNode;
if (!parentNode) return;

Expand Down Expand Up @@ -140,11 +198,18 @@ export const useEnterKeyHandler = ({
} else {
// 현재 텍스트의 길이가 0이면 일반 블록으로 변경
if (content.length === 0) {
/*
currentNode.type = "p";
currentNode.content = "";
*/
const newNode = editorList.createNode("p", "", currentNode, currentNode.nextNode);
if (currentNode.nextNode) {
currentNode.nextNode.prevNode = newNode;
}
currentNode.nextNode = newNode;
setEditorState((prev) => ({
...prev,
currentNode,
currentNode: newNode,
}));
} else {
// 일반 블록은 항상 p 태그로 새 블록 생성
Expand Down
40 changes: 39 additions & 1 deletion client/src/hooks/useMarkdownGrammer/handlers/space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,46 @@ export const useSpaceKeyHandler = ({
e.preventDefault();
const { currentNode } = editorState;
if (!currentNode) return;
if (newElement.type === "checkbox") {
const { prevNode, nextNode } = currentNode;
const wasRoot = currentNode.prevNode === null;

// checkbox-container 노드 생성
const containerNode = editorList.createNode(
"checkbox",
"",
prevNode,
nextNode,
currentNode.depth,
);

// content 노드 생성
const contentNode = editorList.createNode("p", "", null, null, currentNode.depth + 1);

// 노드 간 관계 설정
containerNode.firstChild = contentNode;
contentNode.parentNode = containerNode;

// checkbox 속성 설정
containerNode.attributes = {
checked: false,
...newElement.attributes,
};

// root 노드 업데이트
if (wasRoot) {
editorList.root = containerNode;
} else {
if (prevNode) prevNode.nextNode = containerNode;
if (nextNode) nextNode.prevNode = containerNode;
}

if (newElement.type === "ul" || newElement.type === "ol") {
setEditorState((prev) => ({
...prev,
rootNode: wasRoot ? containerNode : prev.rootNode,
currentNode: contentNode,
}));
} else if (newElement.type === "ul" || newElement.type === "ol") {
// 기존 노드의 위치 관계 저장
const { prevNode, nextNode } = currentNode;
const wasRoot = currentNode.prevNode === null;
Expand Down
2 changes: 1 addition & 1 deletion client/src/types/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type ElementType = "p" | "h1" | "h2" | "h3" | "ul" | "ol" | "li" | "input" | "blockquote";
export type ElementType = "p" | "h1" | "h2" | "h3" | "ul" | "ol" | "li" | "checkbox" | "blockquote";

export interface ListProperties {
index?: number;
Expand Down
Loading

0 comments on commit 6b1fb9b

Please sign in to comment.