๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Frontend Dev/Output

[Mini Project] ํ”„๋ก ํŠธ์—”๋“œ ๊ณผ์ œํ…Œ์ŠคํŠธ๋กœ ๋งŒ๋‚œ Note-App ๊ตฌํ˜„ ๊ณผ์ • ์ •๋ฆฌ (3)

๋ฐ˜์‘ํ˜•

[Mini Project] Note-App

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป Github Repository https://github.com/sw2377/note-app

 

 

์ž‘์—… ๊ธฐ๊ฐ„ : 2024. 01. 09 ~ 2024. 01. 16

ํ”„๋ก ํŠธ์—”๋“œ ์ฑ„์šฉ ๊ณผ์ œ๋กœ ์ˆ˜ํ–‰ํ•œ Note-App ๊ตฌํ˜„ ๊ณผ์ • (๊ตฌํ˜„ ์ค‘ ์—๋Ÿฌ์‚ฌํ•ญ & ๊ณ ๋ฏผํ•œ ๋ถ€๋ถ„)์„ ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

 

๐Ÿ”— ์‹œ๋ฆฌ์ฆˆ ์ด์–ด๋ณด๊ธฐ

Note-App ๊ตฌํ˜„ ๊ณผ์ • ์ •๋ฆฌ (1)

Note-App ๊ตฌํ˜„ ๊ณผ์ • ์ •๋ฆฌ (2)

ํ˜„์žฌ๊ธ€ : Note-App ๊ตฌํ˜„ ๊ณผ์ • ์ •๋ฆฌ (3)

 

๐Ÿ“– ๋ชฉ์ฐจ

1. ์™ธ๋ถ€ ํƒ€์ž…์ •์˜ ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ
2. ๋…ธํŠธ ์‚ญ์ œ์‹œ ๋น„๋™๊ธฐ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ (async / await)
3. ๋ฐ˜๋ณต๋˜๋˜ handleCreateNote ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋งŒ๋“ค๊ธฐ
4. TypeScript์˜ Date ํƒ€์ž…
5. vercel ๋ฐฐํฌ

+ ๊ทธ๋ ‡๊ฒŒ ๋งˆ์ง€๋ง‰ ๋‚ , ๊ณผ์ œ ์ œ์ถœ์ผ์ด ๋˜์—ˆ๋‹ค.

 

1. ์™ธ๋ถ€ ํƒ€์ž…์ •์˜ ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ

LexicalNode.d.ts

์—ฌ๊ธฐ์„œ LexicalNode.d.ts์— __text์˜ ํƒ€์ž…์ด ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์•„ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋‹ค. textNode๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์™ธ๋ถ€ ํƒ€์ž… ์ •์˜ ํŒŒ์ผ์„ ๋”ฐ๋กœ ๋งŒ๋“ค๋ฉด ๋  ๊ฒƒ ๊ฐ™์•„ ํƒ€์ž…์ •์˜ ํŒŒ์ผ์„ ๋งŒ๋“ค์—ˆ๋‹ค. ํƒ€์ž…์ •์˜ ํŒŒ์ผ์˜ ์ƒ์„ฑ์€ chatGPT์˜ ๋„์›€์„ ๋ฐ›์•˜๋‹ค.

 

// lexical-extensions.d.ts

// ์›๋ž˜์˜ LexicalNode ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
import { LexicalNode as OriginalLexicalNode } from "lexical";

// LexicalNode ํด๋ž˜์Šค๋ฅผ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.
declare module "lexical" {
  interface LexicalNode {
    __text?: string;
    // ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋‹ค๋ฅธ ์†์„ฑ์ด๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  }
}

// ์›๋ž˜์˜ LexicalNode๋ฅผ ํ™•์žฅํ•˜์—ฌ ์ƒˆ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
export declare class ExtendedLexicalNode extends OriginalLexicalNode {
  // ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ถ”๊ฐ€ ์†์„ฑ์ด๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ์—ฌ๊ธฐ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
}

 

chatGPT์˜ ๋„์›€์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๊ณ , ์˜ค๋ฅ˜์—†์ด ๋™์ž‘ํ•˜๊ธฐ๋Š” ํ•˜๋Š”๋ฐ ์ด ๋ฐฉ๋ฒ•์ด ๋งž๋Š” ๋ฐฉ๋ฒ•์ธ์ง€๋Š” ๋” ์ฐพ์•„๋ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

 

2. ๋…ธํŠธ ์‚ญ์ œ์‹œ ๋น„๋™๊ธฐ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ (async / await)

const handleRemoveNote = async (targetId: number) => {
    if (window.confirm("๋…ธํŠธ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) {
      await removeNote(targetId);
      navigate("/");
    }
};

 

์—ฌ๊ธฐ์„œ navigate๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ํ•ด์ฃผ์ง€ ์•Š์œผ๋‹ˆ ์ž‘๋™์ด ๋˜์ง€ ์•Š์•˜๋‹ค.

์‚ฌ์‹ค ์ด ๋ถ€๋ถ„์€ ์™œ์ธ์ง€ ์ดํ•ด๊ฐ€ ์ž˜ ๋˜์ง€ ์•Š์•„์„œ ์ฐพ์•„๋ดค์—ˆ๋Š”๋ฐ, ์•„์ง ๋ช…ํ™•ํžˆ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ๋” ์ฐพ์•„๋ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค. 

 

3. ๋ฐ˜๋ณต๋˜๋˜ handleCreateNote ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋งŒ๋“ค๊ธฐ

๋…ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” handleCreateNote๊ฐ€ Header์™€ NoteContainer์—์„œ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— custom hook useCreateNote๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ˜๋ณต๋˜๋˜ ์ฝ”๋“œ๋ฅผ ํ•ฉ์ณ ์ฝ”๋“œ๋Ÿ‰์„ ์ค„์ด๊ณ , ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์˜€๋‹ค. (์‚ฌ์‹ค ๋‹ค๋ฅธ ๊ณณ์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ผ์€ ์—†์„ ๊ฒƒ ๊ฐ™๊ธฐ๋Š” ํ•˜์ง€๋งŒ)

๋…ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋…ธํŠธ๋ถ์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜ ๋˜ํ•œ ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋บ„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•˜๋‹ค. ์ข€ ๋” ์ƒ๊ฐํ•ด๋ณด๋ฉด ๋…ธํŠธ์™€ ๋…ธํŠธ๋ถ์„ ์ƒ์„ฑํ•˜๋Š” ํ•˜๋‚˜์˜ custom hook์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์œผ๋ ค๋‚˜..? ์ด ๋ถ€๋ถ„์€ ์ข€ ๋” ์ƒ๊ฐํ•ด๋ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

// useCreateNote.ts
// Custom Hook
import { useStore } from "../store/notebooks";

const useCreateNote = () => {
  const { createNote } = useStore();

  const handleCreateNote = (notebook: string) => {
    const initialContent = JSON.stringify({
      root: {
        children: [
          {
            children: [],
            direction: null,
            format: "",
            indent: 0,
            type: "paragraph",
            version: 1,
          },
        ],
        direction: null,
        format: "",
        indent: 0,
        type: "root",
        version: 1,
      },
    });

    const note = {
      id: Date.now(),
      title: "New Note",
      content: initialContent,
      date: new Date().toLocaleString(),
    };
    createNote(notebook, note);
    return note.id;
  };

  return { handleCreateNote };
};

export default useCreateNote;
// ๊ธฐ์กด Header ์ปดํฌ๋„ŒํŠธ์˜ handleCreateNote
// Before
import { useParams, useNavigate } from "react-router-dom";
import { useStore } from "../store/notebooks";
import ActionButton from "../componenets/common/ActionButton";

const Header = () => {
  const { createNote } = useStore();
  const navigate = useNavigate();
  const { notebook } = useParams();

  const handleCreateNote = () => {
    if (notebook) {
      const initialContent = JSON.stringify({
        root: {
        // ...initialContent
        },
      });

      const note = {
        id: Date.now(),
        title: "New Note",
        content: initialContent,
        date: new Date().toLocaleString(),
      };
      createNote(notebook, note);

      navigate(`${notebook}/${note.id}`);
    } else {
      window.alert("Create a Notebook first, please.");
    }
  };

  return (
    <HeaderWrapper>
      <ButtonArea>
        <ActionButton handleClick={handleCreateNote}>New Note</ActionButton>
      </ButtonArea>
    </HeaderWrapper>
  );
};
export default Header;
// custom hook์ธ useCreateNote๋ฅผ ์‚ฌ์šฉํ•œ Header ์ปดํฌ๋„ŒํŠธ์˜ handleCreateNoteClick
// After
import { useParams, useNavigate } from "react-router-dom";
import useCreateNote from "../hooks/useCreateNote";
import ActionButton from "../componenets/common/ActionButton";

const Header = () => {
  const navigate = useNavigate();
  const { notebook } = useParams();

  const { handleCreateNote } = useCreateNote();

  const handleCreateNoteClick = () => {
    if (notebook) {
      const createdNoteId = handleCreateNote(notebook);
      navigate(`${notebook}/${createdNoteId}`);
    } else {
      window.alert("Create a Notebook first, please.");
    }
  };

  return (
    <HeaderWrapper>
      <ButtonArea>
        <ActionButton handleClick={handleCreateNoteClick}>New Note</ActionButton>
      </ButtonArea>
    </HeaderWrapper>
  );
};

 

custom hook์„ ๋งŒ๋“ค๋ฉด์„œ chatGPT์˜ ๋„์›€์„ ๋งŽ์ด ๋ฐ›์•˜๋Š”๋ฐ, chatGPT์˜ ์‚ฌ์šฉ๋นˆ๋„๊ฐ€ ๋Š˜์–ด๊ฐ€๋ฉด์„œ ๊ฑฑ์ •์ด ๋˜๋ฉด์„œ๋„ chatGPT๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋“ค์— ๋†€๋ž๊ธฐ๋„ ํ•˜๋ฉด์„œ๋„ ๋™์‹œ์— ์•ž์œผ๋กœ ์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐœ๋ฐœ์„ ๊ณต๋ถ€ํ•ด์•ผ ํ•˜๋Š”์ง€ ๊ณ ๋ฏผ์ด ๋˜๋˜ ์‹œ์ ์ด์—ˆ๋‹ค.

 

4. TypeScript์˜ Date ํƒ€์ž…

export interface NoteType {
  id: number;
  title: string;
  content: string;
  date: string | Date;
}

 

์ด ๋ถ€๋ถ„์€ ์ด์ „ ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ๋„ ํ—ท๊ฐˆ๋ ธ๋˜ ๋ถ€๋ถ„์ด์—ˆ๋‹ค. date๋ฅผ Date ํƒ€์ž…์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, date์— Date ํƒ€์ž…์„ ๋„ฃ์œผ๋‹ˆ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ date์— ํƒ€์ž…์—๋Ÿฌ๊ฐ€ ๋‚ฌ์—ˆ๋‹ค. Type 'string | Date' is not assignable to type 'ReactNode'.

ReactNode ํƒ€์ž…์—๋Š” ReactElement, string, number, boolean, null, undefined ๋“ฑ์ด ์žˆ์ง€๋งŒ Date ๊ฐ์ฒด๋Š” ReactNode์˜ ํƒ€์ž…์œผ๋กœ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค. ๋”ฐ๋ผ์„œ ํƒ€์ž…์ด Date๊ฐ€ ๋œ๋‹ค๋ฉด, ์ด๋ฅผ string ๋“ฑ์˜ ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

<NoteListItemDate>{note.date.toLocaleString()}</NoteListItemDate>

 

5. vercel ๋ฐฐํฌ

๋ฐฐํฌ๋Š” ์š”๊ตฌ์‚ฌํ•ญ์— ์—†๊ธฐ๋Š” ํ–ˆ์ง€๋งŒ ๋ฐฐํฌ์ฃผ์†Œ๊ฐ€ ์žˆ์œผ๋ฉด ๊ตฌํ˜„ ์‚ฌํ•ญ์„ ํ™•์ธํ•˜๊ธฐ ํ›จ์”ฌ ํŽธํ•  ๊ฒƒ ๊ฐ™์•„ ๋„ฃ๊ธฐ๋กœ ํ–ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” github pages๋กœ ๋ฐฐํฌ๋ฅผ ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ด ๋ถ€๋ถ„ ๋˜ํ•œ ์„ธํŒ…์ด ํ•„์š”ํ•ด์„œ ์ข€ ๋” ํŽธ๋ฆฌํ•œ vercel๋กœ ๋ฐฐํฌ๋ฅผ ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

vercel๋กœ ๋ฐฐํฌ๋ฅผ ํ•˜๋ฉด main branch์— push๋ฅผ ํ•  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ฐฐํฌ๊ฐ€ ๋˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด ๊ต‰์žฅํžˆ ํŽธ๋ฆฌํ•˜๋‹ค.

 

{
  "rewrites":  [
    {"source": "/(.*)", "destination": "/"}
  ]
}

 

vercel๋กœ ๋ฐฐํฌ ํ›„ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜๋‹ˆ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๊ฒ€์ƒ‰์„ ํ•ด๋ณด๋‹ˆ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ๊ฒช์€ ๋ฌธ์ œ์˜€๊ณ , ํ•ด๊ฒฐ๋ฐฉ๋ฒ•๋„ ์ž˜ ์•ˆ๋‚ด๋˜์–ด ์žˆ์—ˆ๋‹ค. ์ž์„ธํ•œ ์ด์œ ๋Š” ์ฐพ์•„๋ด์•ผ ์•Œ๊ฒ ์ง€๋งŒ, ํด๋” ์ตœ์ƒ๋‹จ์— ์œ„ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋‹ˆ ๋” ์ด์ƒ ์—๋Ÿฌ๊ฐ€ ๋‚˜์ง€ ์•Š์•˜๋‹ค ๐Ÿ˜Š


๊ทธ๋ ‡๊ฒŒ ๋งˆ์ง€๋ง‰ ๋‚ , ๊ณผ์ œ ์ œ์ถœ์ผ์ด ๋˜์—ˆ๋‹ค.

 ์˜์™ธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€, ์‚ญ์ œ, ์ˆ˜์ •ํ•˜๋Š” ๋“ฑ์˜ CRUD ์ž‘์—…๋ณด๋‹ค lexical editor๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋˜์ง€, ๋ ˆ์ด์•„์›ƒ์„ ์ง ๋‹ค๋˜์ง€ ๋“ฑ์˜ ๋‹ค๋ฅธ ์ž‘์—…๋“ค์ด ์‹œ๊ฐ„์ด ํ›จ์”ฌ ๋งŽ์ด ๊ฑธ๋ ธ๊ณ , ์–ด๋ ค์› ๋‹ค. ํ•˜์ง€๋งŒ ์˜ค๋žœ๋งŒ์— ์ด๋Ÿฐ ๊ณผ์ œ๋ฅผ ๋ฐ›์•„ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“œ๋Š” ์ž‘์—… ์ž์ฒด๊ฐ€ ๋„ˆ๋ฌด ์žฌ๋ฏธ์žˆ์—ˆ๊ณ , ๋งŽ์€๊ฑธ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด ์˜๋ฏธ์žˆ์—ˆ๋‹ค. ์ œํ•œ๋œ ์‹œ๊ฐ„ ์•ˆ์— ์š”๊ตฌ๋œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ  ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“œ๋Š” ์งœ๋ฆฟํ•จ ๋˜ํ•œ ์žˆ์—ˆ๋‹ค. ์˜์™ธ๋กœ ์ด๋Ÿฐ ๋งˆ๊ฐ๊ธฐํ•œ์ด ๋‚˜์—๊ฒŒ ์ƒ๋‹นํžˆ ๋†’์€ ์ง‘์ค‘๋ ฅ๊ณผ ๋™๊ธฐ๋ถ€์—ฌ๊ฐ€ ๋˜๊ธฐ๋„ ํ–ˆ๋‹ค.

 ๋‚˜๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ชจ๋ฅด๋Š” ๋ถ€๋ถ„, ์ƒˆ๋กญ๊ฒŒ ๋ฐฐ์šด ๋ถ€๋ถ„๋“ค์„ ๋‹ค ๊ธฐ๋กํ•ด๋†“๋Š” ํŽธ์ด๋‹ค. ๋ฌผ๋ก  ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ค‘์—๋Š” ์ •์‹ ์ด ์—†์–ด ๋งˆ๊ตฌ์žก์ด๋กœ ๊ฐ„๋‹จํžˆ ๋ฉ”๋ชจ ์ •๋„๋งŒ ๋‚จ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๊ตฌ๋‚จ๋ฐฉ์ด ๋˜์–ด์žˆ์„ ๋•Œ๊ฐ€ ๋งŽ๋‹ค. ๊ณผ์ œ๋ฅผ ์ œ์ถœํ•˜๊ณ  ๋งˆ๊ตฌ์žก์ด๋กœ ๋ฉ”๋ชจ๋งŒ ๋‚จ๊ฒจ๋†จ๋˜ ๋ถ€๋ถ„๋“ค์„ ์ •๋ฆฌํ•˜๋ฉฐ ๋ฌด์—‡์„ ํ–ˆ๋Š”์ง€ ๋‹ค์‹œ ๊ธฐ์–ตํ•˜๊ณ  ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ์–ด ์ด ์‹œ๊ฐ„์€ ํ•ญ์ƒ ์˜๋ฏธ์žˆ๋Š” ์‹œ๊ฐ„์ด๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค. ํ˜„์—…์—์„œ ์ผํ•˜๊ฒŒ ๋˜๋ฉด, ์ด๋ ‡๊ฒŒํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š์œผ๋ ค๋‚˜. ๊ฒฝํ—˜ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค, ์ƒˆ๋กœ์šด ์ผ์„.

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ •๋ฆฌํ•˜๋ฉฐ ๋งŒ์•ฝ ๋ฆฌํŒฉํ† ๋ง ๋˜๋Š” ๊ธฐ๋Šฅ ๊ฐœ์„  ๋˜๋Š” ์ถ”๊ฐ€๋ฅผ ํ•œ๋‹ค๋ฉด ๋ฌด์—‡์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ ์ƒ๊ฐ๋„ ํ•ด๋ณด์•˜๋‹ค.

 

  • ์—๋Ÿฌ์ฒ˜๋ฆฌ (์ƒ๊ฐํ•ด๋ณด๋‹ˆ ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ๋”ฐ๋กœ ํ•œ ๋ถ€๋ถ„์ด ์—†๋Š” ๊ฒƒ ๊ฐ™๋‹ค.)
  • ์ตœ์ ํ™”์™€ ํ…Œ์ŠคํŠธ์ฝ”๋“œ ์ž‘์„ฑ (์ตœ์ ํ™”์™€ ํ…Œ์ŠคํŠธ์ฝ”๋“œ ์ž‘์„ฑ์€ ํ•™์Šต ๋ชฉ์ ์œผ๋กœ ํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.)
  • svg๋กœ ์ด๋ฏธ์ง€ ์‚ฝ์ž… (์ค‘๊ฐ„์— ํ•˜๋‹ค๊ฐ€ ์„ธํŒ…์ด ์ž˜ ์•ˆ๋˜์„œ ์ผ๋‹จ ๋†“์•˜๋‹ค…)
  • ๋…ธํŠธ๋ถ ์ƒ์„ฑ ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋งŒ๋“ค๊ธฐ
  • Modal์˜ ์œ„์น˜๋„ ๋‹ค์‹œ ๊ณ ๋ฏผํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.
  • ํ˜„์žฌ notebook, note์˜ id๋ฅผ Date.now()์˜ random number๋กœ ์ƒ์„ฑํ•˜์˜€๋Š”๋ฐ, uuid ๋“ฑ ์ข€ ๋” ๊ณ ์œ ํ•˜๊ณ  ์œ ๋‹ˆํฌํ•œ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.
๋ฐ˜์‘ํ˜•