Skip to content

Commit

Permalink
Merge pull request #95 from boostcampwm-2024/Feature/#094_기초_마크다운_에디터_구현
Browse files Browse the repository at this point in the history
Feature/#94 기초 마크다운 에디터 구현
  • Loading branch information
hyonun321 authored Nov 12, 2024
2 parents 69f8c8a + 88d416c commit 8bb2d3d
Show file tree
Hide file tree
Showing 42 changed files with 1,570 additions and 8 deletions.
1 change: 1 addition & 0 deletions @noctaCrdt/dist/Crdt.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//# sourceMappingURL=Crdt.d.ts.map
1 change: 1 addition & 0 deletions @noctaCrdt/dist/Crdt.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions @noctaCrdt/dist/Crdt.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/Crdt.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/Interfaces.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//# sourceMappingURL=Interfaces.d.ts.map
1 change: 1 addition & 0 deletions @noctaCrdt/dist/Interfaces.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions @noctaCrdt/dist/Interfaces.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/Interfaces.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/LinkedList.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//# sourceMappingURL=LinkedList.d.ts.map
1 change: 1 addition & 0 deletions @noctaCrdt/dist/LinkedList.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions @noctaCrdt/dist/LinkedList.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/LinkedList.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/Node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//# sourceMappingURL=Node.d.ts.map
1 change: 1 addition & 0 deletions @noctaCrdt/dist/Node.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions @noctaCrdt/dist/Node.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/Node.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions @noctaCrdt/dist/tsconfig.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"program":{"fileNames":["../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/.pnpm/[email protected]/node_modules/typescript/lib/lib.es2021.full.d.ts","../crdt.ts","../interfaces.ts","../linkedlist.ts","../node.ts"],"fileInfos":[{"version":"f33e5332b24c3773e930e212cbb8b6867c8ba3ec4492064ea78e55a524d57450","affectsGlobalScope":true},"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","26f2f787e82c4222710f3b676b4d83eb5ad0a72fa7b746f03449e7a026ce5073","9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569",{"version":"21e41a76098aa7a191028256e52a726baafd45a925ea5cf0222eb430c96c1d83","affectsGlobalScope":true},{"version":"35299ae4a62086698444a5aaee27fc7aa377c68cbb90b441c9ace246ffd05c97","affectsGlobalScope":true},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true},{"version":"138fb588d26538783b78d1e3b2c2cc12d55840b97bf5e08bca7f7a174fbe2f17","affectsGlobalScope":true},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true},{"version":"4443e68b35f3332f753eacc66a04ac1d2053b8b035a0e0ac1d455392b5e243b3","affectsGlobalScope":true},{"version":"bc47685641087c015972a3f072480889f0d6c65515f12bd85222f49a98952ed7","affectsGlobalScope":true},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true},{"version":"bb42a7797d996412ecdc5b2787720de477103a0b2e53058569069a0e2bae6c7e","affectsGlobalScope":true},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true},{"version":"61c37c1de663cf4171e1192466e52c7a382afa58da01b1dc75058f032ddf0839","affectsGlobalScope":true},{"version":"b541a838a13f9234aba650a825393ffc2292dc0fc87681a5d81ef0c96d281e7a","affectsGlobalScope":true},{"version":"e0275cd0e42990dc3a16f0b7c8bca3efe87f1c8ad404f80c6db1c7c0b828c59f","affectsGlobalScope":true},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true},{"version":"49ed889be54031e1044af0ad2c603d627b8bda8b50c1a68435fe85583901d072","affectsGlobalScope":true},{"version":"e93d098658ce4f0c8a0779e6cab91d0259efb88a318137f686ad76f8410ca270","affectsGlobalScope":true},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true},{"version":"ec0104fee478075cb5171e5f4e3f23add8e02d845ae0165bfa3f1099241fa2aa","affectsGlobalScope":true},{"version":"2b72d528b2e2fe3c57889ca7baef5e13a56c957b946906d03767c642f386bbc3","affectsGlobalScope":true},{"version":"acae90d417bee324b1372813b5a00829d31c7eb670d299cd7f8f9a648ac05688","affectsGlobalScope":true},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true},{"version":"51e547984877a62227042850456de71a5c45e7fe86b7c975c6e68896c86fa23b","affectsGlobalScope":true},{"version":"62a4966981264d1f04c44eb0f4b5bdc3d81c1a54725608861e44755aa24ad6a5","affectsGlobalScope":true},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true},"c1e8d979afc15d66e2bd5a58c732d5a2ba3ccaae41ac7d5a2c539e6de66a8e51","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],"root":[[54,57]],"options":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationMap":true,"emitDecoratorMetadata":true,"experimentalDecorators":true,"module":99,"outDir":"./","removeComments":true,"rootDir":"..","skipLibCheck":true,"sourceMap":true,"strict":true,"target":8},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[54,55,56,57,51,52,9,10,14,13,2,15,16,17,18,19,20,21,22,3,4,23,27,24,25,26,28,29,30,5,31,32,33,34,6,38,35,36,37,39,7,40,45,46,41,42,43,44,8,53,50,47,48,49,1,12,11],"latestChangedDtsFile":"./Node.d.ts"},"version":"5.3.3"}
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.14",
"prettier": "^3.0.0",
"uuid": "^11.0.3",
"vite": "^5.4.10",
"vite-tsconfig-paths": "^5.1.0"
}
Expand Down
81 changes: 81 additions & 0 deletions client/src/components/block/Block.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { cva } from "@styled-system/css";

// 기본 블록 스타일
const baseBlockStyle = {
textStyle: "display-medium16",
outline: "none",
borderRadius: "radii.xs",
width: "full",
minHeight: "spacing.lg",
margin: "spacing.sm 0",
padding: "spacing.sm",
color: "gray.900",
backgroundColor: "transparent",
"&:empty::before": {
content: "attr(data-placeholder)", // data-placeholder 속성의 값을 표시
color: "gray.300",
position: "absolute",
pointerEvents: "none", // 텍스트 선택이나 클릭 방지
},
};

// 블록 타입별 스타일 variants
export const blockContainerStyle = cva({
base: baseBlockStyle,
variants: {
type: {
p: {
textStyle: "display-medium16",
fontWeight: "bold",
},
h1: {
textStyle: "display-medium24",
fontWeight: "bold",
},
h2: {
textStyle: "display-medium20",
fontWeight: "bold",
},
h3: {
textStyle: "display-medium16",
fontWeight: "bold",
},
ul: {
display: "block",
listStyleType: "disc",
listStylePosition: "outside",
},
ol: {
display: "block",
listStyleType: "decimal",
listStylePosition: "outside",
},
li: {
textStyle: "display-medium16",
display: "list-item",
outline: "none",
margin: "0",
padding: "0 0 0 16px",
},
blockquote: {
borderLeft: "4px solid token(colors.gray.300)",
paddingLeft: "spacing.md",
color: "gray.500",
fontStyle: "italic",
},
checkbox: {},
},
isActive: {
true: {
opacity: 0.9,
},
false: {
backgroundColor: "transparent",
},
},
},
defaultVariants: {
type: "p",
isActive: false,
},
});
78 changes: 78 additions & 0 deletions client/src/components/block/Block.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { memo } from "react";
import { EditorNode } from "../../types/markdown";
import { blockContainerStyle } from "./Block.style";

interface BlockProps {
node: EditorNode;
isActive: boolean;
contentRef?: React.RefObject<HTMLDivElement>;
onKeyDown: (e: React.KeyboardEvent) => void;
onCompositionStart: () => void;
onCompositionEnd: () => void;
onInput: (e: React.KeyboardEvent) => void;
onClick: (nodeId: string) => void;
currentNodeId: string;
}

export const Block: React.FC<BlockProps> = memo(
({
node,
isActive,
contentRef,
onKeyDown,
onCompositionStart,
onCompositionEnd,
onInput,
onClick,
}: BlockProps) => {
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
onClick(node.id);
};

const getPlaceholder = (type: string) => {
switch (type) {
case "p":
return "텍스트를 입력하세요 ...";
case "h1":
return "제목 1";
case "h2":
return "제목 2";
case "h3":
return "제목 3";
case "li":
return "리스트 항목";
case "blockquote":
return "인용구를 입력하세요";
default:
return "텍스트를 입력하세요";
}
};

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

const commonProps = {
"data-node-id": node.id,
"data-depth": node.depth,
"data-placeholder": getPlaceholder(node.type),
onKeyDown,
onInput,
onCompositionStart,
onCompositionEnd,
onClick: handleClick,
ref: isActive ? contentRef : undefined,
contentEditable: true,
suppressContentEditableWarning: true,
dangerouslySetInnerHTML: { __html: node.content },
className: blockContainerStyle({
type: node.type,
isActive,
}),
};

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

// 메모이제이션을 위한 displayName 설정
Block.displayName = "Block";
50 changes: 50 additions & 0 deletions client/src/features/editor/Editor.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { css } from "@styled-system/css";

export const editorContainer = css({
width: "full",
height: "full", // 부모 컴포넌트의 header(60px)를 제외한 높이
margin: "spacing.lg", // 16px margin
padding: "24px", // 24px padding
overflowY: "auto", // 내용이 많을 경우 스크롤
_focus: {
outline: "none",
},
});

export const editorTitleContainer = css({
width: "full",
marginBottom: "30px",
padding: "spacing.sm",
});

export const editorTitle = css({
textStyle: "display-medium28",
outline: "none",
border: "none",
width: "full",
color: "gray.700",
"&::placeholder": {
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",
},
});
Loading

0 comments on commit 8bb2d3d

Please sign in to comment.