From ed7a783fdbe1591b6c27b2793206f8f0e95a6dd6 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 05:29:13 +0900 Subject: [PATCH 01/25] =?UTF-8?q?chore:=20tiptap=20v2=EB=A1=9C=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 32 ++ pnpm-lock.yaml | 857 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 802 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index c3eb838..d3ed7bc 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,35 @@ "lint:fix": "eslint . --fix" }, "dependencies": { + "@tailwindcss/typography": "^0.5.19", "@tanstack/react-query": "^5.62.11", + "@tiptap/core": "2.4.0", + "@tiptap/extension-blockquote": "2.4.0", + "@tiptap/extension-bold": "2.4.0", + "@tiptap/extension-bubble-menu": "2.4.0", + "@tiptap/extension-bullet-list": "2.4.0", + "@tiptap/extension-code": "2.4.0", + "@tiptap/extension-code-block": "2.4.0", + "@tiptap/extension-document": "2.4.0", + "@tiptap/extension-dropcursor": "2.4.0", + "@tiptap/extension-floating-menu": "2.4.0", + "@tiptap/extension-gapcursor": "2.4.0", + "@tiptap/extension-hard-break": "2.4.0", + "@tiptap/extension-heading": "2.4.0", + "@tiptap/extension-history": "2.4.0", + "@tiptap/extension-horizontal-rule": "2.4.0", + "@tiptap/extension-italic": "2.4.0", + "@tiptap/extension-list-item": "2.4.0", + "@tiptap/extension-ordered-list": "2.4.0", + "@tiptap/extension-paragraph": "2.4.0", + "@tiptap/extension-placeholder": "2.4.0", + "@tiptap/extension-strike": "2.4.0", + "@tiptap/extension-task-item": "2.4.0", + "@tiptap/extension-task-list": "2.4.0", + "@tiptap/extension-text": "2.4.0", + "@tiptap/extension-typography": "2.4.0", + "@tiptap/pm": "2.4.0", + "@tiptap/react": "2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.468.0", @@ -19,7 +47,11 @@ "react": "19.2.3", "react-dom": "19.2.3", "tailwind-merge": "^2.6.0", +<<<<<<< Updated upstream "tw-animate-css": "^1.4.0", +======= + "tippy.js": "^6.3.7", +>>>>>>> Stashed changes "zustand": "^5.0.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b1651d..8f6ac86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,93 @@ importers: .: dependencies: + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@4.1.18) '@tanstack/react-query': specifier: ^5.62.11 version: 5.90.21(react@19.2.3) + '@tiptap/core': + specifier: 2.4.0 + version: 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/extension-blockquote': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-bold': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-bubble-menu': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-bullet-list': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-code': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-code-block': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-document': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-dropcursor': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-floating-menu': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-gapcursor': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-hard-break': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-heading': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-history': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-horizontal-rule': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-italic': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-list-item': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-ordered-list': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-paragraph': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-placeholder': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-strike': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-task-item': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-task-list': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-text': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/extension-typography': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0)) + '@tiptap/pm': + specifier: 2.4.0 + version: 2.4.0 + '@tiptap/react': + specifier: 2.4.0 + version: 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -38,6 +122,9 @@ importers: tw-animate-css: specifier: ^1.4.0 version: 1.4.0 + tippy.js: + specifier: ^6.3.7 + version: 6.3.7 zustand: specifier: ^5.0.2 version: 5.0.11(@types/react@19.2.14)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) @@ -59,7 +146,7 @@ importers: version: 9.39.2(jiti@2.6.1) eslint-config-next: specifier: 16.1.6 - version: 16.1.6(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 16.1.6(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.2(eslint@9.39.2(jiti@2.6.1)) @@ -1163,6 +1250,12 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@remirror/core-constants@2.0.2': + resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1261,6 +1354,11 @@ packages: '@tailwindcss/postcss@4.1.18': resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tanstack/query-core@5.90.20': resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} @@ -1269,6 +1367,151 @@ packages: peerDependencies: react: ^18 || ^19 + '@tiptap/core@2.4.0': + resolution: {integrity: sha512-YJSahk8pkxpCs8SflCZfTnJpE7IPyUWIylfgXM2DefjRQa5DZ+c6sNY0s/zbxKYFQ6AuHVX40r9pCfcqHChGxQ==} + peerDependencies: + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-blockquote@2.4.0': + resolution: {integrity: sha512-nJJy4KsPgQqWTTDOWzFRdjCfG5+QExfZj44dulgDFNh+E66xhamnbM70PklllXJgEcge7xmT5oKM0gKls5XgFw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-bold@2.4.0': + resolution: {integrity: sha512-csnW6hMDEHoRfxcPRLSqeJn+j35Lgtt1YRiOwn7DlS66sAECGRuoGfCvQSPij0TCDp4VCR9if5Sf8EymhnQumQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-bubble-menu@2.4.0': + resolution: {integrity: sha512-s99HmttUtpW3rScWq8rqk4+CGCwergNZbHLTkF6Rp6TSboMwfp+rwL5Q/JkcAG9KGLso1vGyXKbt1xHOvm8zMw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-bullet-list@2.4.0': + resolution: {integrity: sha512-9S5DLIvFRBoExvmZ+/ErpTvs4Wf1yOEs8WXlKYUCcZssK7brTFj99XDwpHFA29HKDwma5q9UHhr2OB2o0JYAdw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-code-block@2.4.0': + resolution: {integrity: sha512-QWGdv1D56TBGbbJSj2cIiXGJEKguPiAl9ONzJ/Ql1ZksiQsYwx0YHriXX6TOC//T4VIf6NSClHEtwtxWBQ/Csg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-code@2.4.0': + resolution: {integrity: sha512-wjhBukuiyJMq4cTcK3RBTzUPV24k5n1eEPlpmzku6ThwwkMdwynnMGMAmSF3fErh3AOyOUPoTTjgMYN2d10SJA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-document@2.4.0': + resolution: {integrity: sha512-3jRodQJZDGbXlRPERaloS+IERg/VwzpC1IO6YSJR9jVIsBO6xC29P3cKTQlg1XO7p6ZH/0ksK73VC5BzzTwoHg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-dropcursor@2.4.0': + resolution: {integrity: sha512-c46HoG2PEEpSZv5rmS5UX/lJ6/kP1iVO0Ax+6JrNfLEIiDULUoi20NqdjolEa38La2VhWvs+o20OviiTOKEE9g==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-floating-menu@2.4.0': + resolution: {integrity: sha512-vLb9v+htbHhXyty0oaXjT3VC8St4xuGSHWUB9GuAJAQ+NajIO6rBPbLUmm9qM0Eh2zico5mpSD1Qtn5FM6xYzg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-gapcursor@2.4.0': + resolution: {integrity: sha512-F4y/0J2lseohkFUw9P2OpKhrJ6dHz69ZScABUvcHxjznJLd6+0Zt7014Lw5PA8/m2d/w0fX8LZQ88pZr4quZPQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-hard-break@2.4.0': + resolution: {integrity: sha512-3+Z6zxevtHza5IsDBZ4lZqvNR3Kvdqwxq/QKCKu9UhJN1DUjsg/l1Jn2NilSQ3NYkBYh2yJjT8CMo9pQIu776g==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-heading@2.4.0': + resolution: {integrity: sha512-fYkyP/VMo7YHO76YVrUjd95Qeo0cubWn/Spavmwm1gLTHH/q7xMtbod2Z/F0wd6QHnc7+HGhO7XAjjKWDjldaw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-history@2.4.0': + resolution: {integrity: sha512-gr5qsKAXEVGr1Lyk1598F7drTaEtAxqZiuuSwTCzZzkiwgEQsWMWTWc9F8FlneCEaqe1aIYg6WKWlmYPaFwr0w==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-horizontal-rule@2.4.0': + resolution: {integrity: sha512-yDgxy+YxagcEsBbdWvbQiXYxsv3noS1VTuGwc9G7ZK9xPmBHJ5y0agOkB7HskwsZvJHoaSqNRsh7oZTkf0VR3g==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-italic@2.4.0': + resolution: {integrity: sha512-aaW/L9q+KNHHK+X73MPloHeIsT191n3VLd3xm6uUcFDnUNvzYJ/q65/1ZicdtCaOLvTutxdrEvhbkrVREX6a8g==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-list-item@2.4.0': + resolution: {integrity: sha512-reUVUx+2cI2NIAqMZhlJ9uK/+zvRzm1GTmlU2Wvzwc7AwLN4yemj6mBDsmBLEXAKPvitfLh6EkeHaruOGymQtg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-ordered-list@2.4.0': + resolution: {integrity: sha512-Zo0c9M0aowv+2+jExZiAvhCB83GZMjZsxywmuOrdUbq5EGYKb7q8hDyN3hkrktVHr9UPXdPAYTmLAHztTOHYRA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-paragraph@2.4.0': + resolution: {integrity: sha512-+yse0Ow67IRwcACd9K/CzBcxlpr9OFnmf0x9uqpaWt1eHck1sJnti6jrw5DVVkyEBHDh/cnkkV49gvctT/NyCw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-placeholder@2.4.0': + resolution: {integrity: sha512-SmWOjgWpmhFt0BPOnL65abCUH0wS5yksUJgtANn5bQoHF4HFSsyl7ETRmgf0ykxdjc7tzOg31FfpWVH4wzKSYg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-strike@2.4.0': + resolution: {integrity: sha512-pE1uN/fQPOMS3i+zxPYMmPmI3keubnR6ivwM+KdXWOMnBiHl9N4cNpJgq1n2eUUGKLurC2qrQHpnVyGAwBS6Vg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-task-item@2.4.0': + resolution: {integrity: sha512-x40vdHnmDiBbA2pjWR/92wVGb6jT13Nk2AhRUI/oP/r4ZGKpTypoB7heDnvLBgH0Y5a51dFqU+G1SFFL30u5uA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + + '@tiptap/extension-task-list@2.4.0': + resolution: {integrity: sha512-vmUB3wEJU81QbiHUygBlselQW8YIW8/85UTwANvWx8+KEWyM7EUF4utcm5R2UobIprIcWb4hyVkvW/5iou25gg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-text@2.4.0': + resolution: {integrity: sha512-LV0bvE+VowE8IgLca7pM8ll7quNH+AgEHRbSrsI3SHKDCYB9gTHMjWaAkgkUVaO1u0IfCrjnCLym/PqFKa+vvg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/extension-typography@2.4.0': + resolution: {integrity: sha512-RuGenfdPA6ggu5IVLdcIdhGCdRlwSvWp0lETySM3PILE6fxCrxTD54m3pxpY0Q1cg+F71YDUHdSmSipQTwQnsQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + + '@tiptap/pm@2.4.0': + resolution: {integrity: sha512-B1HMEqGS4MzIVXnpgRZDLm30mxDWj51LkBT/if1XD+hj5gm8B9Q0c84bhvODX6KIs+c6z+zsY9VkVu8w9Yfgxg==} + + '@tiptap/react@2.4.0': + resolution: {integrity: sha512-baxnIr6Dy+5iGagOEIKFeHzdl1ZRa6Cg+SJ3GDL/BVLpO6KiCM3Mm5ymB726UKP1w7icrBiQD2fGY3Bx8KaiSA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1281,6 +1524,15 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node@20.19.33': resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} @@ -1292,63 +1544,63 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@typescript-eslint/eslint-plugin@8.55.0': - resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} + '@typescript-eslint/eslint-plugin@8.56.0': + resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.55.0 - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/parser': ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.55.0': - resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} + '@typescript-eslint/parser@8.56.0': + resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.55.0': - resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} + '@typescript-eslint/project-service@8.56.0': + resolution: {integrity: sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.55.0': - resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} + '@typescript-eslint/scope-manager@8.56.0': + resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.55.0': - resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} + '@typescript-eslint/tsconfig-utils@8.56.0': + resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.55.0': - resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} + '@typescript-eslint/type-utils@8.56.0': + resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.55.0': - resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} + '@typescript-eslint/types@8.56.0': + resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.55.0': - resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} + '@typescript-eslint/typescript-estree@8.56.0': + resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.55.0': - resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} + '@typescript-eslint/utils@8.56.0': + resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.55.0': - resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} + '@typescript-eslint/visitor-keys@8.56.0': + resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1571,8 +1823,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001769: - resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001770: + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1601,10 +1853,18 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1676,6 +1936,10 @@ packages: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-abstract@1.24.1: resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} @@ -1808,6 +2072,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.0: + resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2242,6 +2510,9 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2264,10 +2535,17 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2323,6 +2601,10 @@ packages: sass: optional: true + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2362,6 +2644,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -2404,6 +2689,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -2485,6 +2774,68 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prosemirror-changeset@2.4.0: + resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.0: + resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.4: + resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} + + prosemirror-menu@1.3.0: + resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==} + + prosemirror-model@1.25.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-trailing-node@2.0.9: + resolution: {integrity: sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.11.0: + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} + + prosemirror-view@1.41.6: + resolution: {integrity: sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2567,14 +2918,18 @@ packages: engines: {node: '>= 0.4'} hasBin: true - resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + resolve@2.0.0-next.6: + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + engines: {node: '>= 0.4'} hasBin: true reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2719,6 +3074,9 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2758,11 +3116,11 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.55.0: - resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} + typescript-eslint@8.56.0: + resolution: {integrity: sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' typescript@5.9.3: @@ -2770,6 +3128,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -2814,6 +3175,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3981,6 +4348,10 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@popperjs/core@2.11.8': {} + + '@remirror/core-constants@2.0.2': {} + '@rtsao/scc@1.1.0': {} '@swc/helpers@0.5.15': @@ -4056,6 +4427,11 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.18 + '@tailwindcss/typography@0.5.19(tailwindcss@4.1.18)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.18 + '@tanstack/query-core@5.90.20': {} '@tanstack/react-query@5.90.21(react@19.2.3)': @@ -4063,6 +4439,147 @@ snapshots: '@tanstack/query-core': 5.90.20 react: 19.2.3 + '@tiptap/core@2.4.0(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-blockquote@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-bold@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-bubble-menu@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + tippy.js: 6.3.7 + + '@tiptap/extension-bullet-list@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-code-block@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-code@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-document@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-dropcursor@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-floating-menu@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + tippy.js: 6.3.7 + + '@tiptap/extension-gapcursor@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-hard-break@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-heading@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-history@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-horizontal-rule@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-italic@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-list-item@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-ordered-list@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-paragraph@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-placeholder@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-strike@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-task-item@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + + '@tiptap/extension-task-list@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-text@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/extension-typography@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + + '@tiptap/pm@2.4.0': + dependencies: + prosemirror-changeset: 2.4.0 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.0 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.4 + prosemirror-menu: 1.3.0 + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-trailing-node: 2.0.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6) + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + '@tiptap/react@2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) + '@tiptap/extension-bubble-menu': 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/extension-floating-menu': 2.4.0(@tiptap/core@2.4.0(@tiptap/pm@2.4.0))(@tiptap/pm@2.4.0) + '@tiptap/pm': 2.4.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -4074,6 +4591,15 @@ snapshots: '@types/json5@0.0.29': {} + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + '@types/node@20.19.33': dependencies: undici-types: 6.21.0 @@ -4086,14 +4612,14 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.0 + '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -4102,41 +4628,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/scope-manager': 8.56.0 + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.56.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) - '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.55.0': + '@typescript-eslint/scope-manager@8.56.0': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/visitor-keys': 8.56.0 - '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) @@ -4144,14 +4670,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.55.0': {} + '@typescript-eslint/types@8.56.0': {} - '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.56.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/visitor-keys': 8.55.0 + '@typescript-eslint/project-service': 8.56.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.0(typescript@5.9.3) + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/visitor-keys': 8.56.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.4 @@ -4161,21 +4687,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.0 + '@typescript-eslint/types': 8.56.0 + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.55.0': + '@typescript-eslint/visitor-keys@8.56.0': dependencies: - '@typescript-eslint/types': 8.55.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.56.0 + eslint-visitor-keys: 5.0.0 '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -4360,7 +4886,7 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 + caniuse-lite: 1.0.30001770 electron-to-chromium: 1.5.286 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -4384,7 +4910,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001770: {} chalk@4.1.2: dependencies: @@ -4409,12 +4935,16 @@ snapshots: convert-source-map@2.0.0: {} + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + cssesc@3.0.0: {} + csstype@3.2.3: {} damerau-levenshtein@1.0.8: {} @@ -4482,6 +5012,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@4.5.0: {} + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 @@ -4587,18 +5119,18 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.1.6(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.1.6(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.1.6 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) globals: 16.4.0 - typescript-eslint: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -4630,22 +5162,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4656,7 +5188,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4668,7 +5200,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4721,7 +5253,7 @@ snapshots: object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 - resolve: 2.0.0-next.5 + resolve: 2.0.0-next.6 semver: 6.3.1 string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 @@ -4735,6 +5267,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.0: {} + eslint@9.39.2(jiti@2.6.1): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) @@ -5171,6 +5705,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -5193,8 +5731,19 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-it@14.1.1: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + math-intrinsics@1.1.0: {} + mdurl@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -5225,7 +5774,7 @@ snapshots: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 + caniuse-lite: 1.0.30001770 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5244,6 +5793,13 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + node-releases@2.0.27: {} object-assign@4.1.1: {} @@ -5297,6 +5853,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -5329,6 +5887,11 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -5355,6 +5918,111 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + prosemirror-changeset@2.4.0: + dependencies: + prosemirror-transform: 1.11.0 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-gapcursor@1.4.0: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.6 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.4: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.1 + prosemirror-model: 1.25.4 + + prosemirror-menu@1.3.0: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-trailing-node@2.0.9(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6): + dependencies: + '@remirror/core-constants': 2.0.2 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.6 + + prosemirror-transform@1.11.0: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-view@1.41.6: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + punycode.js@2.3.1: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -5488,14 +6156,19 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.5: + resolve@2.0.0-next.6: dependencies: + es-errors: 1.3.0 is-core-module: 2.16.1 + node-exports-info: 1.6.0 + object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 reusify@1.1.0: {} + rope-sequence@1.3.4: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -5700,6 +6373,10 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -5756,12 +6433,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -5769,6 +6446,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -5831,6 +6510,10 @@ snapshots: dependencies: react: 19.2.3 + util-deprecate@1.0.2: {} + + w3c-keyname@2.2.8: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 From d2a8b6aeba2c7c35d1f2a780f8bde6fd5fe8dee4 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 05:29:59 +0900 Subject: [PATCH 02/25] =?UTF-8?q?style:=20=EC=97=90=EB=94=94=ED=84=B0=20?= =?UTF-8?q?=EC=A0=84=EC=97=AD=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 --- src/app/globals.css | 55 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index d3930fc..59c4e34 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,7 +1,7 @@ @import 'tailwindcss'; -@import "tw-animate-css"; - +@import 'tw-animate-css'; @custom-variant dark (&:is(.dark *)); +@plugin "@tailwindcss/typography"; @font-face { font-family: 'Pretendard Variable'; @@ -487,6 +487,55 @@ select:focus { background-clip: content-box; } +.ProseMirror { + color: #ffffff; +} + +.ProseMirror p { + color: #ffffff; +} + +/* 인용 */ +.ProseMirror blockquote { + font-style: italic; + border-left: 3px solid #d1d5db; + padding-left: 1rem; + margin: 1rem 0; + font-weight: normal; +} + +.ProseMirror blockquote p::before, +.ProseMirror blockquote p::after { + content: none !important; +} + +/* 인라인 코드 */ +.ProseMirror code:not(pre code) { + background-color: #3a3a3a; + color: #f3f4f6; /* gray-100 */ + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + font-weight: normal; +} + +.ProseMirror code::before, +.ProseMirror code::after { + content: none; +} + +/* 코드 블록 */ +.ProseMirror pre { + background-color: #3a3a3a; + border-radius: 0.5rem; + padding: 0.75rem 1rem; +} + +.ProseMirror pre code { + background-color: transparent; + color: #f3f4f6; /* gray-100 */ + padding: 0; +} + @layer base { * { @apply border-border outline-ring/50; @@ -494,4 +543,4 @@ select:focus { body { @apply bg-background text-foreground; } -} \ No newline at end of file +} From a814925857e35519be2a7bdad32c9d1a3cac2471 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 05:31:35 +0900 Subject: [PATCH 03/25] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=A0=84=EC=97=AD=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/usePostStore.ts | 116 +++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/stores/usePostStore.ts diff --git a/src/stores/usePostStore.ts b/src/stores/usePostStore.ts new file mode 100644 index 0000000..d7cdc5c --- /dev/null +++ b/src/stores/usePostStore.ts @@ -0,0 +1,116 @@ +import { create } from 'zustand'; + +interface FileItem { + file: File; + fileName: string; + fileUrl: string; + uploaded: boolean; +} + +interface PostState { + // 게시글 메타 정보 + board: string; + title: string; + cohort: number; + part: string; + category: string; + studyName: string; + week: number; + + // 에디터 본문 + content: string; + + // 파일 첨부 + files: FileItem[]; + + // 게시글 상태 + status: 'DRAFT' | 'PUBLISHED'; + + // Actions + setBoard: (board: string) => void; + setTitle: (title: string) => void; + setCohort: (cohort: number) => void; + setPart: (part: string) => void; + setCategory: (category: string) => void; + setStudyName: (studyName: string) => void; + setWeek: (week: number) => void; + setContent: (content: string) => void; + addFile: (file: FileItem) => void; + removeFile: (fileName: string) => void; + updateFileUrl: (fileName: string, fileUrl: string) => void; + setStatus: (status: 'DRAFT' | 'PUBLISHED') => void; + reset: () => void; + + // API payload 변환 + getPayload: () => { + title: string; + content: string; + category: string; + studyName: string; + week: number; + part: string; + cardinalNumber: number; + files: { fileName: string; fileUrl: string }[]; + }; +} + +const initialState = { + board: '', + title: '', + cohort: 0, + part: '', + category: '', + studyName: '', + week: 0, + content: '', + files: [], + status: 'DRAFT' as const, +}; + +export const usePostStore = create((set, get) => ({ + ...initialState, + + setBoard: (board) => set({ board }), + setTitle: (title) => set({ title }), + setCohort: (cohort) => set({ cohort }), + setPart: (part) => set({ part }), + setCategory: (category) => set({ category }), + setStudyName: (studyName) => set({ studyName }), + setWeek: (week) => set({ week }), + setContent: (content) => set({ content }), + + addFile: (file) => set((state) => ({ files: [...state.files, file] })), + + removeFile: (fileName) => + set((state) => ({ + files: state.files.filter((f) => f.fileName !== fileName), + })), + + updateFileUrl: (fileName, fileUrl) => + set((state) => ({ + files: state.files.map((f) => + f.fileName === fileName ? { ...f, fileUrl, uploaded: true } : f, + ), + })), + + setStatus: (status) => set({ status }), + + reset: () => set(initialState), + + // 제출 시 API payload 형태로 변환 + getPayload: () => { + const state = get(); + return { + title: state.title, + content: state.content, + category: state.category, + studyName: state.studyName, + week: state.week, + part: state.part, + cardinalNumber: state.cohort, + files: state.files + .filter((f) => f.uploaded) + .map(({ fileName, fileUrl }) => ({ fileName, fileUrl })), + }; + }, +})); From f4180b1bcb12a9b1a4fd8516879f5287b338f06d Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 05:37:36 +0900 Subject: [PATCH 04/25] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index d3ed7bc..a05807f 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,8 @@ "react": "19.2.3", "react-dom": "19.2.3", "tailwind-merge": "^2.6.0", -<<<<<<< Updated upstream "tw-animate-css": "^1.4.0", -======= "tippy.js": "^6.3.7", ->>>>>>> Stashed changes "zustand": "^5.0.2" }, "devDependencies": { From 17b93bdb12f17752ddfacbe8ff50415d7c433ba2 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 06:08:03 +0900 Subject: [PATCH 05/25] =?UTF-8?q?feat:=20=EC=97=90=EB=94=94=ED=84=B0=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/editor.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/types/editor.ts diff --git a/src/types/editor.ts b/src/types/editor.ts new file mode 100644 index 0000000..c83ef9a --- /dev/null +++ b/src/types/editor.ts @@ -0,0 +1,8 @@ +import { Editor as TiptapEditor } from '@tiptap/core'; + +export interface MenuItem { + label: string; + description: string; + icon: string; + command: (editor: TiptapEditor) => void; +} From cd8f84c2cfa3d9db68e8485cc81054efd417de4e Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 06:08:33 +0900 Subject: [PATCH 06/25] =?UTF-8?q?feat:=20=EC=8A=AC=EB=9E=98=EC=8B=9C=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=BB=A4=EB=A7=A8=EB=93=9C=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20=EC=A0=95=EC=9D=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/editor.ts | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/constants/editor.ts diff --git a/src/constants/editor.ts b/src/constants/editor.ts new file mode 100644 index 0000000..358d4d8 --- /dev/null +++ b/src/constants/editor.ts @@ -0,0 +1,68 @@ +import { Editor as TiptapEditor } from '@tiptap/core'; +import { MenuItem } from '@/types/editor'; + +export const STYLE_ITEMS: MenuItem[] = [ + { + label: 'Text', + description: '일반 텍스트', + icon: 'T', + command: (editor: TiptapEditor) => editor.chain().focus().clearNodes().setParagraph().run(), + }, + { + label: 'Heading 1', + description: '큰 제목', + icon: 'H1', + command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 1 }).run(), + }, + { + label: 'Heading 2', + description: '중간 제목', + icon: 'H2', + command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 2 }).run(), + }, + { + label: 'Heading 3', + description: '작은 제목', + icon: 'H3', + command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 3 }).run(), + }, + { + label: 'Bullet List', + description: '순서 없는 목록', + icon: '•', + command: (editor: TiptapEditor) => editor.chain().focus().toggleBulletList().run(), + }, + { + label: 'Numbered List', + description: '순서 있는 목록', + icon: '1.', + command: (editor: TiptapEditor) => editor.chain().focus().toggleOrderedList().run(), + }, + { + label: 'To-do List', + description: '체크리스트', + icon: '☑', + command: (editor: TiptapEditor) => editor.chain().focus().toggleTaskList().run(), + }, + { + label: 'Blockquote', + description: '인용', + icon: '"', + command: (editor: TiptapEditor) => editor.chain().focus().toggleBlockquote().run(), + }, + { + label: 'Code Block', + description: '코드 블록', + icon: '', + command: (editor: TiptapEditor) => editor.chain().focus().toggleCodeBlock().run(), + }, +]; + +export const INSERT_ITEMS: MenuItem[] = [ + { + label: 'Separator', + description: '구분선', + icon: '—', + command: (editor: TiptapEditor) => editor.chain().focus().setHorizontalRule().run(), + }, +]; From 18bcfbcc103ec38463a5eef7d9b6d86ef09e283f Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 06:09:12 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20=EC=8A=AC=EB=9E=98=EC=8B=9C=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/board/Editor/SlashMenu.tsx | 139 ++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/components/board/Editor/SlashMenu.tsx diff --git a/src/components/board/Editor/SlashMenu.tsx b/src/components/board/Editor/SlashMenu.tsx new file mode 100644 index 0000000..ffeb856 --- /dev/null +++ b/src/components/board/Editor/SlashMenu.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { Editor as TiptapEditor } from '@tiptap/core'; +import { useEffect, useMemo, useState, useCallback } from 'react'; +import { STYLE_ITEMS, INSERT_ITEMS } from '@/constants/editor'; +import { MenuItem } from '@/types/editor'; + +interface SlashMenuContentProps { + editor: TiptapEditor; + onClose: () => void; +} + +/** + * Slash Command 메뉴 UI + * + * 역할: + * - '/' 입력 후 나타나는 커맨드 목록 렌더링 + * - 키보드 탐색 (↑ ↓ Enter Escape) + * - 선택 시 slash 문자 제거 후 해당 command 실행 + */ + +export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { + const [selectedIndex, setSelectedIndex] = useState(0); + + /** + * 그룹 구조 정의 + */ + const GROUPS = useMemo( + () => [ + { title: 'Style', items: STYLE_ITEMS }, + { title: 'Insert', items: INSERT_ITEMS }, + ], + [], + ); + + const flatItems = useMemo(() => GROUPS.flatMap((group) => group.items), [GROUPS]); + + useEffect(() => { + if (flatItems.length === 0) { + setSelectedIndex(0); + return; + } + + if (selectedIndex >= flatItems.length) { + setSelectedIndex(0); + } + }, [flatItems.length, selectedIndex]); + + // 메뉴 선택 시 실행 + const handleSelect = useCallback( + (item: MenuItem) => { + const { $anchor } = editor.state.selection; + + const from = $anchor.pos - 1; + const to = $anchor.pos; + + editor.chain().focus().deleteRange({ from, to }).run(); + item.command(editor); + onClose(); + }, + [editor, onClose], + ); + + // 키보드 이벤트 핸들링 + useEffect(() => { + if (flatItems.length === 0) return; + + const dom = editor.view.dom; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'ArrowDown') { + e.preventDefault(); + setSelectedIndex((prev) => (prev + 1) % flatItems.length); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setSelectedIndex((prev) => (prev - 1 + flatItems.length) % flatItems.length); + } else if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + const item = flatItems[selectedIndex]; + if (item) handleSelect(item); + } else if (e.key === 'Escape') { + onClose(); + } + }; + + dom.addEventListener('keydown', handleKeyDown); + return () => dom.removeEventListener('keydown', handleKeyDown); + }, [editor, flatItems, selectedIndex, handleSelect, onClose]); + + if (flatItems.length === 0) return null; + + let runningIndex = 0; + const currentIndex = runningIndex++; + + return ( +
+
+ {GROUPS.map((group, groupIdx) => ( +
+
+

+ {group.title} +

+
+ + {group.items.map((item) => { + const currentIndex = runningIndex++; + const isSelected = currentIndex === selectedIndex; + + return ( + + ); + })} +
+ ))} + +
+
+
+ ); +} From d25acd49b3ff3121eb14666c1f262661138dc44c Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 06:09:56 +0900 Subject: [PATCH 08/25] =?UTF-8?q?feat:=20Tiptap=20=EC=97=90=EB=94=94?= =?UTF-8?q?=ED=84=B0=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=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 --- src/app/(private)/(main)/board/page.tsx | 12 +- src/components/board/Editor/index.tsx | 263 ++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 src/components/board/Editor/index.tsx diff --git a/src/app/(private)/(main)/board/page.tsx b/src/app/(private)/(main)/board/page.tsx index ebb42c5..675cad8 100644 --- a/src/app/(private)/(main)/board/page.tsx +++ b/src/app/(private)/(main)/board/page.tsx @@ -1,3 +1,13 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +const Editor = dynamic(() => import('@/components/board/Editor'), { ssr: false }); + export default function BoardPage() { - return
BoardPage
; + return ( +
+ +
+ ); } diff --git a/src/components/board/Editor/index.tsx b/src/components/board/Editor/index.tsx new file mode 100644 index 0000000..34283fa --- /dev/null +++ b/src/components/board/Editor/index.tsx @@ -0,0 +1,263 @@ +'use client'; + +import { useEditor, EditorContent, BubbleMenu, FloatingMenu } from '@tiptap/react'; +import Document from '@tiptap/extension-document'; +import Paragraph from '@tiptap/extension-paragraph'; +import Text from '@tiptap/extension-text'; +import Heading from '@tiptap/extension-heading'; +import Bold from '@tiptap/extension-bold'; +import Italic from '@tiptap/extension-italic'; +import Code from '@tiptap/extension-code'; +import CodeBlock from '@tiptap/extension-code-block'; +import Strike from '@tiptap/extension-strike'; +import Blockquote from '@tiptap/extension-blockquote'; +import BulletList from '@tiptap/extension-bullet-list'; +import OrderedList from '@tiptap/extension-ordered-list'; +import ListItem from '@tiptap/extension-list-item'; +import HorizontalRule from '@tiptap/extension-horizontal-rule'; +import HardBreak from '@tiptap/extension-hard-break'; +import History from '@tiptap/extension-history'; +import Dropcursor from '@tiptap/extension-dropcursor'; +import Gapcursor from '@tiptap/extension-gapcursor'; +import Placeholder from '@tiptap/extension-placeholder'; +import TaskList from '@tiptap/extension-task-list'; +import TaskItem from '@tiptap/extension-task-item'; +import Typography from '@tiptap/extension-typography'; +import { useState, useRef } from 'react'; +import { usePostStore } from '@/stores/usePostStore'; +import { SlashMenuContent } from './SlashMenu'; + +/** + * 주요 기능: + * Tiptap 기반 에디터 컴포넌트 + * + * - Slash Menu (/) + * - Bubble Menu (텍스트 선택 시) + * - 커스텀 키보드 단축키 (` 인라인 코드, Backspace 블록 정리) + */ + +export default function Editor() { + const setContent = usePostStore((state) => state.setContent); + const [showSlashMenu, setShowSlashMenu] = useState(false); + const containerRef = useRef(null); + + const editor = useEditor({ + extensions: [ + Document, + Paragraph, + Text, + Heading.configure({ levels: [1, 2, 3] }), + Bold, + Italic, + Code, + CodeBlock, + Strike, + Blockquote, + BulletList.configure({ keepMarks: true, keepAttributes: false }), + OrderedList.configure({ keepMarks: true, keepAttributes: false }), + ListItem, + HorizontalRule, + HardBreak, + History, + Dropcursor, + Gapcursor, + Typography, + Placeholder.configure({ placeholder: "명령어는 '/'를 입력하세요." }), + TaskList, + TaskItem.configure({ nested: true, onReadOnlyChecked: () => false }), + ], + + content: '', + + onUpdate: ({ editor }) => { + setContent(editor.getHTML()); + + const { $from } = editor.state.selection; + const currentLineText = $from.nodeBefore?.textContent ?? ''; + + setShowSlashMenu(currentLineText.endsWith('/')); + }, + editorProps: { + handleKeyDown: (view, event) => { + // 슬래시 메뉴 우선 처리 + if (showSlashMenu) { + if (event.key === 'Enter') { + event.preventDefault(); + return true; + } + + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + return true; + } + } + + const { state } = view; + const { $from } = state.selection; + + // 백틱 인라인 코드 단축키 + if (event.key === '`') { + const blockStart = $from.start(); + const textBefore = state.doc.textBetween(blockStart, $from.pos); + const openIndex = textBefore.lastIndexOf('`'); + + if (openIndex !== -1) { + const innerText = textBefore.slice(openIndex + 1); + + if (innerText.length > 0) { + event.preventDefault(); + const from = blockStart + openIndex; + const to = $from.pos; + const codeMark = state.schema.marks.code.create(); + const codeText = state.schema.text(innerText, [codeMark]); + const tr = state.tr.replaceWith(from, to, codeText); + tr.removeStoredMark(state.schema.marks.code); + + view.dispatch(tr); + return true; + } + } + } + + // Backspace UX 개선 (빈 리스트 아이템/헤딩 → 일반 단락으로 전환) + if (event.key === 'Backspace') { + if ($from.parentOffset === 0 && $from.parent.textContent === '') { + if ($from.parent.type.name === 'listItem') { + const depth = $from.depth; + const pos = $from.before(depth); + + view.dispatch(state.tr.setBlockType(pos, pos, state.schema.nodes.paragraph)); + return true; + } + if ($from.parent.type.name === 'heading') { + view.dispatch( + state.tr.setBlockType($from.pos, $from.pos, state.schema.nodes.paragraph), + ); + return true; + } + } + } + + return false; + }, + }, + }); + + if (!editor) return null; + + return ( +
+ {/* BubbleMenu */} + containerRef.current ?? document.body, + }} + className="flex items-center gap-0.5 rounded-lg border border-gray-200 bg-white p-1 shadow-md" + > + + + +
+ + + + + + {/* FloatingMenu (슬래시 메뉴) */} + containerRef.current ?? document.body, + }} + shouldShow={({ state }) => { + const { $from } = state.selection; + const isSlash = ($from.nodeBefore?.textContent ?? '').endsWith('/'); + setShowSlashMenu(isSlash); + return isSlash; + }} + > + {showSlashMenu && ( + setShowSlashMenu(false)} /> + )} + + + {/* 에디터 본문 */} +
+ +
+
+ ); +} From 99d012ef63708c7da80cb44022e8722bba2f90f7 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Fri, 20 Feb 2026 06:11:12 +0900 Subject: [PATCH 09/25] =?UTF-8?q?style:=20ProseMirror=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=97=AC=EB=B0=B1=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app/globals.css b/src/app/globals.css index 59c4e34..6ee6f41 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -536,6 +536,15 @@ select:focus { padding: 0; } +.ProseMirror ul[data-type='taskList'] { + padding-left: 0; +} + +.ProseMirror ul[data-type='taskList'] li p { + margin-top: 8px !important; + margin-bottom: 8px !important; +} + @layer base { * { @apply border-border outline-ring/50; From 9cc3f1125529a4e37f0ad250aadf4a41330a9d3d Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Sat, 21 Feb 2026 03:12:17 +0900 Subject: [PATCH 10/25] =?UTF-8?q?style:=20ProseMirror=20=ED=95=98=EB=93=9C?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EC=83=89=EC=83=81=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=ED=86=A0=ED=81=B0=EC=9C=BC=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 6ee6f41..c002d62 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -488,17 +488,13 @@ select:focus { } .ProseMirror { - color: #ffffff; -} - -.ProseMirror p { - color: #ffffff; + color: var(--text-normal); } /* 인용 */ .ProseMirror blockquote { font-style: italic; - border-left: 3px solid #d1d5db; + border-left: 3px solid var(--line); padding-left: 1rem; margin: 1rem 0; font-weight: normal; @@ -511,8 +507,8 @@ select:focus { /* 인라인 코드 */ .ProseMirror code:not(pre code) { - background-color: #3a3a3a; - color: #f3f4f6; /* gray-100 */ + background-color: var(--container-neutral-alternative); + color: var(--text-normal); padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-weight: normal; @@ -525,14 +521,14 @@ select:focus { /* 코드 블록 */ .ProseMirror pre { - background-color: #3a3a3a; + background-color: var(--container-neutral-alternative); border-radius: 0.5rem; padding: 0.75rem 1rem; } .ProseMirror pre code { background-color: transparent; - color: #f3f4f6; /* gray-100 */ + color: var(--text-normal); padding: 0; } From c3f21e5902ce515a4f2c6a84f201edbaa279d417 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Sat, 21 Feb 2026 03:19:34 +0900 Subject: [PATCH 11/25] =?UTF-8?q?feat:=20SlashMenu=20=ED=82=A4=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=ED=83=90=EC=83=89=20=EC=9E=90=EB=8F=99=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=ED=86=A0=ED=81=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useRef + scrollIntoView로 키보드 탐색 시 선택 항목 자동 스크롤 - data-index 속성으로 선택된 버튼 요소 탐색 - 하드코딩 gray-* 색상을 디자인 토큰으로 교체 --- src/components/board/Editor/SlashMenu.tsx | 34 ++++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/board/Editor/SlashMenu.tsx b/src/components/board/Editor/SlashMenu.tsx index ffeb856..dbc53c2 100644 --- a/src/components/board/Editor/SlashMenu.tsx +++ b/src/components/board/Editor/SlashMenu.tsx @@ -1,7 +1,7 @@ 'use client'; import { Editor as TiptapEditor } from '@tiptap/core'; -import { useEffect, useMemo, useState, useCallback } from 'react'; +import { useEffect, useMemo, useState, useCallback, useRef } from 'react'; import { STYLE_ITEMS, INSERT_ITEMS } from '@/constants/editor'; import { MenuItem } from '@/types/editor'; @@ -21,6 +21,7 @@ interface SlashMenuContentProps { export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { const [selectedIndex, setSelectedIndex] = useState(0); + const scrollContainerRef = useRef(null); /** * 그룹 구조 정의 @@ -46,6 +47,15 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { } }, [flatItems.length, selectedIndex]); + // 선택된 아이템이 스크롤 영역 밖에 있을 때 자동 스크롤 + useEffect(() => { + const container = scrollContainerRef.current; + if (!container) return; + + const selectedEl = container.querySelector(`[data-index="${selectedIndex}"]`); + selectedEl?.scrollIntoView({ block: 'nearest' }); + }, [selectedIndex]); + // 메뉴 선택 시 실행 const handleSelect = useCallback( (item: MenuItem) => { @@ -90,16 +100,16 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { if (flatItems.length === 0) return null; + // runningIndex는 그룹을 넘어서도 연속된 flat index를 부여하기 위해 사용 let runningIndex = 0; - const currentIndex = runningIndex++; return ( -
-
+
+
{GROUPS.map((group, groupIdx) => (
-
-

+

+

{group.title}

@@ -111,20 +121,24 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { return ( ); From 08e7b187bc460109880f54326a42ec2cda5aad9b Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Sat, 21 Feb 2026 03:24:43 +0900 Subject: [PATCH 12/25] =?UTF-8?q?feat:=20Editor=20=EC=8A=AC=EB=9E=98?= =?UTF-8?q?=EC=8B=9C=20=EB=A9=94=EB=89=B4=20stale=20closure=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=BB=A4=EC=84=9C=20=EC=9D=B4=ED=83=88?= =?UTF-8?q?=20=EA=B0=90=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showSlashMenuRef로 handleKeyDown stale closure 방지 - onSelectionUpdate로 커서 이동 시 슬래시 메뉴 자동 닫기 - Backspace 시 listItem 처리 제거 (Tiptap 자체 처리) - 툴바 버튼 디자인 토큰 적용 --- src/components/board/Editor/index.tsx | 87 +++++++++++++++------------ 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/src/components/board/Editor/index.tsx b/src/components/board/Editor/index.tsx index 34283fa..3e62b24 100644 --- a/src/components/board/Editor/index.tsx +++ b/src/components/board/Editor/index.tsx @@ -23,7 +23,7 @@ import Placeholder from '@tiptap/extension-placeholder'; import TaskList from '@tiptap/extension-task-list'; import TaskItem from '@tiptap/extension-task-item'; import Typography from '@tiptap/extension-typography'; -import { useState, useRef } from 'react'; +import { useState, useRef, useCallback } from 'react'; import { usePostStore } from '@/stores/usePostStore'; import { SlashMenuContent } from './SlashMenu'; @@ -39,8 +39,20 @@ import { SlashMenuContent } from './SlashMenu'; export default function Editor() { const setContent = usePostStore((state) => state.setContent); const [showSlashMenu, setShowSlashMenu] = useState(false); + // ref로 최신 상태 유지 → useEditor 내부 handleKeyDown stale closure 방지 + const showSlashMenuRef = useRef(false); const containerRef = useRef(null); + const closeSlashMenu = useCallback(() => { + showSlashMenuRef.current = false; + setShowSlashMenu(false); + }, []); + + const updateSlashMenuState = useCallback((isSlash: boolean) => { + showSlashMenuRef.current = isSlash; + setShowSlashMenu(isSlash); + }, []); + const editor = useEditor({ extensions: [ Document, @@ -71,22 +83,21 @@ export default function Editor() { onUpdate: ({ editor }) => { setContent(editor.getHTML()); - const { $from } = editor.state.selection; - const currentLineText = $from.nodeBefore?.textContent ?? ''; + updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); + }, - setShowSlashMenu(currentLineText.endsWith('/')); + // 커서 이동만으로 '/' 뒤를 벗어났을 때도 메뉴를 닫기 위해 추적 + onSelectionUpdate: ({ editor }) => { + const { $from } = editor.state.selection; + updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); }, + editorProps: { handleKeyDown: (view, event) => { - // 슬래시 메뉴 우선 처리 - if (showSlashMenu) { - if (event.key === 'Enter') { - event.preventDefault(); - return true; - } - - if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { + // 슬래시 메뉴 우선 처리 (ref로 stale closure 없이 최신 값 참조) + if (showSlashMenuRef.current) { + if (event.key === 'Enter' || event.key === 'ArrowUp' || event.key === 'ArrowDown') { event.preventDefault(); return true; } @@ -119,16 +130,10 @@ export default function Editor() { } } - // Backspace UX 개선 (빈 리스트 아이템/헤딩 → 일반 단락으로 전환) + // Backspace UX 개선 (빈 헤딩 → 일반 단락으로 전환) + // listItem은 Tiptap ListItem extension이 자체 처리 if (event.key === 'Backspace') { if ($from.parentOffset === 0 && $from.parent.textContent === '') { - if ($from.parent.type.name === 'listItem') { - const depth = $from.depth; - const pos = $from.before(depth); - - view.dispatch(state.tr.setBlockType(pos, pos, state.schema.nodes.paragraph)); - return true; - } if ($from.parent.type.name === 'heading') { view.dispatch( state.tr.setBlockType($from.pos, $from.pos, state.schema.nodes.paragraph), @@ -154,77 +159,89 @@ export default function Editor() { duration: 100, appendTo: () => containerRef.current ?? document.body, }} - className="flex items-center gap-0.5 rounded-lg border border-gray-200 bg-white p-1 shadow-md" + className="border-line bg-container-neutral flex items-center gap-0.5 rounded-lg border p-1 shadow-md" > -
+
@@ -181,11 +189,7 @@ export default function Editor() { e.preventDefault(); editor.chain().focus().toggleItalic().run(); }} - className={`rounded px-2 py-1 text-sm italic transition-colors ${ - editor.isActive('italic') - ? 'bg-button-primary text-text-inverse' - : 'text-text-normal hover:bg-container-neutral-interaction' - }`} + className={cn(bubbleButtonVariants({ active: editor.isActive('italic') }), 'italic')} > I @@ -195,26 +199,18 @@ export default function Editor() { e.preventDefault(); editor.chain().focus().toggleCode().run(); }} - className={`rounded px-2 py-1 font-mono text-sm transition-colors ${ - editor.isActive('code') - ? 'bg-button-primary text-text-inverse' - : 'text-text-normal hover:bg-container-neutral-interaction' - }`} + className={cn(bubbleButtonVariants({ active: editor.isActive('code') }), 'font-mono')} > {'<>'} -
+
@@ -224,11 +220,7 @@ export default function Editor() { e.preventDefault(); editor.chain().focus().toggleHeading({ level: 2 }).run(); }} - className={`rounded px-2 py-1 text-sm transition-colors ${ - editor.isActive('heading', { level: 2 }) - ? 'bg-button-primary text-text-inverse' - : 'text-text-normal hover:bg-container-neutral-interaction' - }`} + className={cn(bubbleButtonVariants({ active: editor.isActive('heading', { level: 2 }) }))} > H2 @@ -238,11 +230,7 @@ export default function Editor() { e.preventDefault(); editor.chain().focus().toggleHeading({ level: 3 }).run(); }} - className={`rounded px-2 py-1 text-sm transition-colors ${ - editor.isActive('heading', { level: 3 }) - ? 'bg-button-primary text-text-inverse' - : 'text-text-normal hover:bg-container-neutral-interaction' - }`} + className={cn(bubbleButtonVariants({ active: editor.isActive('heading', { level: 3 }) }))} > H3 From 83d5d23cea6c9353e9f2fd6a65d081a929cbc873 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Sat, 21 Feb 2026 17:01:00 +0900 Subject: [PATCH 16/25] =?UTF-8?q?style:=20EditorContent=20=EB=B9=84?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=ED=83=80=EC=9D=B4=ED=8F=AC/=EC=BB=AC?= =?UTF-8?q?=EB=9F=AC/=EA=B0=84=EA=B2=A9=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 12 ++++++++++++ src/components/board/Editor/index.tsx | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 78e62fe..b22fa38 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -491,6 +491,18 @@ select:focus { color: var(--text-normal); } +.ProseMirror h1 { + @apply typo-h1; +} + +.ProseMirror h2 { + @apply typo-h2; +} + +.ProseMirror h3 { + @apply typo-h3; +} + /* 인용 */ .ProseMirror blockquote { font-style: italic; diff --git a/src/components/board/Editor/index.tsx b/src/components/board/Editor/index.tsx index 0aff43d..a40095a 100644 --- a/src/components/board/Editor/index.tsx +++ b/src/components/board/Editor/index.tsx @@ -253,10 +253,10 @@ export default function Editor() { {/* 에디터 본문 */} -
+
From 28dfed1c54614d0532514be5aac3c921475a4de7 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Sat, 21 Feb 2026 17:14:55 +0900 Subject: [PATCH 17/25] =?UTF-8?q?refactor:=20SlashMenu=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EB=AC=B8=EC=9E=90=EC=97=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Lucide=20React=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/board/Editor/SlashMenu.tsx | 5 ++-- src/constants/editor.ts | 32 ++++++++++++++++------- src/types/editor.ts | 3 ++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/components/board/Editor/SlashMenu.tsx b/src/components/board/Editor/SlashMenu.tsx index dbc53c2..9ef7145 100644 --- a/src/components/board/Editor/SlashMenu.tsx +++ b/src/components/board/Editor/SlashMenu.tsx @@ -117,6 +117,7 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { {group.items.map((item) => { const currentIndex = runningIndex++; const isSelected = currentIndex === selectedIndex; + const Icon = item.icon; return ( + + +
+ + + + + ); +} diff --git a/src/components/board/Editor/extensions.ts b/src/components/board/Editor/extensions.ts new file mode 100644 index 0000000..9c1f34c --- /dev/null +++ b/src/components/board/Editor/extensions.ts @@ -0,0 +1,47 @@ +import Document from '@tiptap/extension-document'; +import Paragraph from '@tiptap/extension-paragraph'; +import Text from '@tiptap/extension-text'; +import Heading from '@tiptap/extension-heading'; +import Bold from '@tiptap/extension-bold'; +import Italic from '@tiptap/extension-italic'; +import Code from '@tiptap/extension-code'; +import CodeBlock from '@tiptap/extension-code-block'; +import Strike from '@tiptap/extension-strike'; +import Blockquote from '@tiptap/extension-blockquote'; +import BulletList from '@tiptap/extension-bullet-list'; +import OrderedList from '@tiptap/extension-ordered-list'; +import ListItem from '@tiptap/extension-list-item'; +import HorizontalRule from '@tiptap/extension-horizontal-rule'; +import HardBreak from '@tiptap/extension-hard-break'; +import History from '@tiptap/extension-history'; +import Dropcursor from '@tiptap/extension-dropcursor'; +import Gapcursor from '@tiptap/extension-gapcursor'; +import Placeholder from '@tiptap/extension-placeholder'; +import TaskList from '@tiptap/extension-task-list'; +import TaskItem from '@tiptap/extension-task-item'; +import Typography from '@tiptap/extension-typography'; + +export const editorExtensions = [ + Document, + Paragraph, + Text, + Heading.configure({ levels: [1, 2, 3] }), + Bold, + Italic, + Code, + CodeBlock, + Strike, + Blockquote, + BulletList.configure({ keepMarks: true, keepAttributes: false }), + OrderedList.configure({ keepMarks: true, keepAttributes: false }), + ListItem, + HorizontalRule, + HardBreak, + History, + Dropcursor, + Gapcursor, + Typography, + Placeholder.configure({ placeholder: "명령어는 '/'를 입력하세요." }), + TaskList, + TaskItem.configure({ nested: true, onReadOnlyChecked: () => false }), +]; diff --git a/src/components/board/Editor/index.tsx b/src/components/board/Editor/index.tsx index 5a21fe0..0f54728 100644 --- a/src/components/board/Editor/index.tsx +++ b/src/components/board/Editor/index.tsx @@ -1,32 +1,8 @@ 'use client'; -import { useEditor, EditorContent, BubbleMenu, FloatingMenu } from '@tiptap/react'; -import Document from '@tiptap/extension-document'; -import Paragraph from '@tiptap/extension-paragraph'; -import Text from '@tiptap/extension-text'; -import Heading from '@tiptap/extension-heading'; -import Bold from '@tiptap/extension-bold'; -import Italic from '@tiptap/extension-italic'; -import Code from '@tiptap/extension-code'; -import CodeBlock from '@tiptap/extension-code-block'; -import Strike from '@tiptap/extension-strike'; -import Blockquote from '@tiptap/extension-blockquote'; -import BulletList from '@tiptap/extension-bullet-list'; -import OrderedList from '@tiptap/extension-ordered-list'; -import ListItem from '@tiptap/extension-list-item'; -import HorizontalRule from '@tiptap/extension-horizontal-rule'; -import HardBreak from '@tiptap/extension-hard-break'; -import History from '@tiptap/extension-history'; -import Dropcursor from '@tiptap/extension-dropcursor'; -import Gapcursor from '@tiptap/extension-gapcursor'; -import Placeholder from '@tiptap/extension-placeholder'; -import TaskList from '@tiptap/extension-task-list'; -import TaskItem from '@tiptap/extension-task-item'; -import Typography from '@tiptap/extension-typography'; -import { useState, useRef, useCallback } from 'react'; -import { cva } from 'class-variance-authority'; -import { cn } from '@/lib/cn'; -import { usePostStore } from '@/stores/usePostStore'; +import { EditorContent, FloatingMenu } from '@tiptap/react'; +import { usePostEditor } from './usePostEditor'; +import { BubbleMenuBar } from './EditorBubbleMenu'; import { SlashMenuContent } from './SlashMenu'; /** @@ -38,205 +14,15 @@ import { SlashMenuContent } from './SlashMenu'; * - 커스텀 키보드 단축키 (` 인라인 코드, Backspace 블록 정리) */ -const bubbleButtonVariants = cva('rounded px-200 py-100 typo-button2 transition-colors', { - variants: { - active: { - true: 'bg-button-primary text-text-inverse', - false: 'text-text-normal hover:bg-container-neutral-interaction', - }, - }, - defaultVariants: { active: false }, -}); - export default function Editor() { - const setContent = usePostStore((state) => state.setContent); - const [showSlashMenu, setShowSlashMenu] = useState(false); - // ref로 최신 상태 유지 → useEditor 내부 handleKeyDown stale closure 방지 - const showSlashMenuRef = useRef(false); - const containerRef = useRef(null); - - const closeSlashMenu = useCallback(() => { - showSlashMenuRef.current = false; - setShowSlashMenu(false); - }, []); - - const updateSlashMenuState = useCallback((isSlash: boolean) => { - showSlashMenuRef.current = isSlash; - setShowSlashMenu(isSlash); - }, []); - - const editor = useEditor({ - extensions: [ - Document, - Paragraph, - Text, - Heading.configure({ levels: [1, 2, 3] }), - Bold, - Italic, - Code, - CodeBlock, - Strike, - Blockquote, - BulletList.configure({ keepMarks: true, keepAttributes: false }), - OrderedList.configure({ keepMarks: true, keepAttributes: false }), - ListItem, - HorizontalRule, - HardBreak, - History, - Dropcursor, - Gapcursor, - Typography, - Placeholder.configure({ placeholder: "명령어는 '/'를 입력하세요." }), - TaskList, - TaskItem.configure({ nested: true, onReadOnlyChecked: () => false }), - ], - - content: '', - - onUpdate: ({ editor }) => { - setContent(editor.getHTML()); - const { $from } = editor.state.selection; - updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); - }, - - // 커서 이동만으로 '/' 뒤를 벗어났을 때도 메뉴를 닫기 위해 추적 - onSelectionUpdate: ({ editor }) => { - const { $from } = editor.state.selection; - updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); - }, - - editorProps: { - handleKeyDown: (view, event) => { - // 슬래시 메뉴 우선 처리 (ref로 stale closure 없이 최신 값 참조) - if (showSlashMenuRef.current) { - if (event.key === 'Enter' || event.key === 'ArrowUp' || event.key === 'ArrowDown') { - event.preventDefault(); - return true; - } - } - - const { state } = view; - const { $from } = state.selection; - - // 백틱 인라인 코드 단축키 - if (event.key === '`') { - const blockStart = $from.start(); - const textBefore = state.doc.textBetween(blockStart, $from.pos); - const openIndex = textBefore.lastIndexOf('`'); - - if (openIndex !== -1) { - const innerText = textBefore.slice(openIndex + 1); - - if (innerText.length > 0) { - event.preventDefault(); - const from = blockStart + openIndex; - const to = $from.pos; - const codeMark = state.schema.marks.code.create(); - const codeText = state.schema.text(innerText, [codeMark]); - const tr = state.tr.replaceWith(from, to, codeText); - tr.removeStoredMark(state.schema.marks.code); - - view.dispatch(tr); - return true; - } - } - } - - // Backspace UX 개선 (빈 헤딩 → 일반 단락으로 전환) - // listItem은 Tiptap ListItem extension이 자체 처리 - if (event.key === 'Backspace') { - if ($from.parentOffset === 0 && $from.parent.textContent === '') { - if ($from.parent.type.name === 'heading') { - view.dispatch( - state.tr.setBlockType($from.pos, $from.pos, state.schema.nodes.paragraph), - ); - return true; - } - } - } - - return false; - }, - }, - }); + const { editor, showSlashMenu, closeSlashMenu, containerRef } = usePostEditor(); if (!editor) return null; return (
- {/* BubbleMenu */} - containerRef.current ?? document.body, - }} - className="border-line bg-container-neutral flex items-center gap-100 rounded-lg border p-100 shadow-md" - > - - - -
- - - - + - {/* FloatingMenu (슬래시 메뉴) */} } - {/* 에디터 본문 */}
diff --git a/src/components/board/Editor/usePostEditor.ts b/src/components/board/Editor/usePostEditor.ts new file mode 100644 index 0000000..af51749 --- /dev/null +++ b/src/components/board/Editor/usePostEditor.ts @@ -0,0 +1,96 @@ +'use client'; + +import { useEditor } from '@tiptap/react'; +import { useState, useRef, useCallback } from 'react'; +import { usePostStore } from '@/stores/usePostStore'; +import { editorExtensions } from './extensions'; + +export function usePostEditor() { + const setContent = usePostStore((state) => state.setContent); + const [showSlashMenu, setShowSlashMenu] = useState(false); + // ref로 최신 상태 유지 → useEditor 내부 handleKeyDown stale closure 방지 + const showSlashMenuRef = useRef(false); + const containerRef = useRef(null); + + const closeSlashMenu = useCallback(() => { + showSlashMenuRef.current = false; + setShowSlashMenu(false); + }, []); + + const updateSlashMenuState = useCallback((isSlash: boolean) => { + showSlashMenuRef.current = isSlash; + setShowSlashMenu(isSlash); + }, []); + + const editor = useEditor({ + extensions: editorExtensions, + content: '', + + onUpdate: ({ editor }) => { + setContent(editor.getHTML()); + const { $from } = editor.state.selection; + updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); + }, + + // 커서 이동만으로 '/' 뒤를 벗어났을 때도 메뉴를 닫기 위해 추적 + onSelectionUpdate: ({ editor }) => { + const { $from } = editor.state.selection; + updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); + }, + + editorProps: { + handleKeyDown: (view, event) => { + // 슬래시 메뉴 우선 처리 (ref로 stale closure 없이 최신 값 참조) + if (showSlashMenuRef.current) { + if (event.key === 'Enter' || event.key === 'ArrowUp' || event.key === 'ArrowDown') { + event.preventDefault(); + return true; + } + } + + const { state } = view; + const { $from } = state.selection; + + // 백틱 인라인 코드 단축키 + if (event.key === '`') { + const blockStart = $from.start(); + const textBefore = state.doc.textBetween(blockStart, $from.pos); + const openIndex = textBefore.lastIndexOf('`'); + + if (openIndex !== -1) { + const innerText = textBefore.slice(openIndex + 1); + + if (innerText.length > 0) { + event.preventDefault(); + const from = blockStart + openIndex; + const to = $from.pos; + const codeMark = state.schema.marks.code.create(); + const codeText = state.schema.text(innerText, [codeMark]); + const tr = state.tr.replaceWith(from, to, codeText); + tr.removeStoredMark(state.schema.marks.code); + + view.dispatch(tr); + return true; + } + } + } + + // Backspace UX 개선 (빈 헤딩 → 일반 단락으로 전환) + if (event.key === 'Backspace') { + if ($from.parentOffset === 0 && $from.parent.textContent === '') { + if ($from.parent.type.name === 'heading') { + view.dispatch( + state.tr.setBlockType($from.pos, $from.pos, state.schema.nodes.paragraph), + ); + return true; + } + } + } + + return false; + }, + }, + }); + + return { editor, showSlashMenu, closeSlashMenu, containerRef }; +} From edf30533d010eaee5e81a991bbf2623eeef58161 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Mon, 23 Feb 2026 20:40:37 +0900 Subject: [PATCH 21/25] =?UTF-8?q?style:=20=EC=97=90=EB=94=94=ED=84=B0=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A0=9C=EA=B1=B0=20(=EC=A7=81=EC=A0=91?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98=20x,=20tiptap=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index d917fd9..0ba5d08 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -503,21 +503,6 @@ select:focus { @apply typo-h3; } -/* 리스트 기본 스타일 */ -.ProseMirror ul { - list-style-type: disc; - padding-left: var(--padding-400); -} - -.ProseMirror ol { - list-style-type: decimal; - padding-left: var(--padding-400); -} - -.ProseMirror li + li { - margin-top: var(--spacing-300); -} - /* 인용 */ .ProseMirror blockquote { font-style: italic; @@ -559,15 +544,6 @@ select:focus { padding: 0; } -.ProseMirror ul[data-type='taskList'] { - padding-left: 0; -} - -.ProseMirror ul[data-type='taskList'] li p { - margin-top: var(--margin-200) !important; - margin-bottom: var(--margin-200) !important; -} - @layer base { * { @apply border-border outline-ring/50; From 4f23d30d4fcac446c64d5b719b43ebd8c7d3896c Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Mon, 23 Feb 2026 21:08:20 +0900 Subject: [PATCH 22/25] =?UTF-8?q?feat:=20=EB=93=A4=EC=97=AC=EC=93=B0?= =?UTF-8?q?=EA=B8=B0(tab)=20=EC=A7=80=EC=9B=90=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/Editor/IndentExtension.ts | 83 +++++++++++++++++++ src/components/board/Editor/extensions.ts | 2 + 2 files changed, 85 insertions(+) create mode 100644 src/components/board/Editor/IndentExtension.ts diff --git a/src/components/board/Editor/IndentExtension.ts b/src/components/board/Editor/IndentExtension.ts new file mode 100644 index 0000000..d00d8f8 --- /dev/null +++ b/src/components/board/Editor/IndentExtension.ts @@ -0,0 +1,83 @@ +import { Extension } from '@tiptap/core'; + +// Tab 들여쓰기 한 단계당 증가 크기 (rem) +const INDENT_STEP_REM = 2; +const MAX_INDENT_LEVEL = 4; + +/** + * paragraph / heading 블록의 Tab 들여쓰기를 지원하는 Extension + * + * - Tab (들여쓰기) + * - Shift-Tab (들여쓰기 한 단계 감소) + * - Backspace (커서가 블록 시작점에 있을 때 들여쓰기 한 단계 감소) + */ + +export const IndentExtension = Extension.create({ + name: 'indent', + + addGlobalAttributes() { + return [ + { + types: ['paragraph', 'heading'], + attributes: { + indent: { + default: 0, + parseHTML: (el) => parseInt(el.dataset.indent ?? '0'), + // 렌더링 시 단계에 비례한 margin-left 적용 + renderHTML: ({ indent }) => { + if (!indent) return {}; + return { + 'data-indent': indent, + style: `margin-left: ${indent * INDENT_STEP_REM}rem`, + }; + }, + }, + }, + }, + ]; + }, + + addKeyboardShortcuts() { + // 커서가 listItem 노드 안에 있는지 여부 탐색 + const isInsideList = () => { + const { $from } = this.editor.state.selection; + for (let depth = $from.depth; depth > 0; depth--) { + if ($from.node(depth).type.name === 'listItem') return true; + } + return false; + }; + + return { + Tab: () => { + if (isInsideList()) return false; + + const { $from } = this.editor.state.selection; + const next = Math.min(($from.parent.attrs.indent ?? 0) + 1, MAX_INDENT_LEVEL); + return this.editor.commands.updateAttributes($from.parent.type.name, { indent: next }); + }, + + 'Shift-Tab': () => { + if (isInsideList()) return false; + + const { $from } = this.editor.state.selection; + const next = Math.max(($from.parent.attrs.indent ?? 0) - 1, 0); + return this.editor.commands.updateAttributes($from.parent.type.name, { indent: next }); + }, + + Backspace: () => { + if (isInsideList()) return false; + + const { $from } = this.editor.state.selection; + // 커서가 블록 시작점이 아니면 일반 backspace 처리 + if ($from.parentOffset !== 0) return false; + + const currentIndent = $from.parent.attrs.indent ?? 0; + if (currentIndent === 0) return false; + + return this.editor.commands.updateAttributes($from.parent.type.name, { + indent: currentIndent - 1, + }); + }, + }; + }, +}); diff --git a/src/components/board/Editor/extensions.ts b/src/components/board/Editor/extensions.ts index 9c1f34c..fb48955 100644 --- a/src/components/board/Editor/extensions.ts +++ b/src/components/board/Editor/extensions.ts @@ -1,3 +1,4 @@ +import { IndentExtension } from './IndentExtension'; import Document from '@tiptap/extension-document'; import Paragraph from '@tiptap/extension-paragraph'; import Text from '@tiptap/extension-text'; @@ -44,4 +45,5 @@ export const editorExtensions = [ Placeholder.configure({ placeholder: "명령어는 '/'를 입력하세요." }), TaskList, TaskItem.configure({ nested: true, onReadOnlyChecked: () => false }), + IndentExtension, ]; From 689ffc4d3828b69740a31a71538d044163f23a36 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Tue, 24 Feb 2026 22:04:06 +0900 Subject: [PATCH 23/25] =?UTF-8?q?chore:=20react=20compiler=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20=EB=B0=8F=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 2 +- package.json | 3 ++- pnpm-lock.yaml | 31 +++++++++++++++++++++---------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/next.config.ts b/next.config.ts index 5e891cf..314bae6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { - /* config options here */ + reactCompiler: true, }; export default nextConfig; diff --git a/package.json b/package.json index 3d694f7..8f6334b 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "react": "19.2.3", "react-dom": "19.2.3", "tailwind-merge": "^2.6.0", - "tw-animate-css": "^1.4.0", "tippy.js": "^6.3.7", + "tw-animate-css": "^1.4.0", "zustand": "^5.0.2" }, "devDependencies": { @@ -58,6 +58,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "babel-plugin-react-compiler": "^1.0.0", "eslint": "^9", "eslint-config-next": "16.1.6", "eslint-config-prettier": "^9.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f6ac86..e81f9b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,7 +106,7 @@ importers: version: 0.468.0(react@19.2.3) next: specifier: 16.1.6 - version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.1.6(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) radix-ui: specifier: ^1.4.3 version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -119,12 +119,12 @@ importers: tailwind-merge: specifier: ^2.6.0 version: 2.6.1 - tw-animate-css: - specifier: ^1.4.0 - version: 1.4.0 tippy.js: specifier: ^6.3.7 version: 6.3.7 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 zustand: specifier: ^5.0.2 version: 5.0.11(@types/react@19.2.14)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) @@ -141,6 +141,9 @@ importers: '@types/react-dom': specifier: ^19 version: 19.2.3(@types/react@19.2.14) + babel-plugin-react-compiler: + specifier: ^1.0.0 + version: 1.0.0 eslint: specifier: ^9 version: 9.39.2(jiti@2.6.1) @@ -560,6 +563,9 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1250,9 +1256,6 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@remirror/core-constants@2.0.2': resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} @@ -1785,6 +1788,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -3601,6 +3607,8 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@popperjs/core@2.11.8': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -4348,8 +4356,6 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@popperjs/core@2.11.8': {} - '@remirror/core-constants@2.0.2': {} '@rtsao/scc@1.1.0': {} @@ -4866,6 +4872,10 @@ snapshots: axobject-query@4.1.0: {} + babel-plugin-react-compiler@1.0.0: + dependencies: + '@babel/types': 7.29.0 + balanced-match@1.0.2: {} baseline-browser-mapping@2.9.19: {} @@ -5769,7 +5779,7 @@ snapshots: natural-compare@1.4.0: {} - next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@16.1.6(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 @@ -5788,6 +5798,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.1.6 '@next/swc-win32-arm64-msvc': 16.1.6 '@next/swc-win32-x64-msvc': 16.1.6 + babel-plugin-react-compiler: 1.0.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' From 9bb31f79dee8b6f0e63b0387af4308bbf2f57941 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Tue, 24 Feb 2026 22:16:32 +0900 Subject: [PATCH 24/25] =?UTF-8?q?refactor:=20React=20Compiler=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20useMemo/useCallback=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 --- src/components/board/Editor/SlashMenu.tsx | 49 ++++++++------------ src/components/board/Editor/usePostEditor.ts | 10 ++-- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/components/board/Editor/SlashMenu.tsx b/src/components/board/Editor/SlashMenu.tsx index 9ef7145..64ad6bf 100644 --- a/src/components/board/Editor/SlashMenu.tsx +++ b/src/components/board/Editor/SlashMenu.tsx @@ -1,10 +1,17 @@ 'use client'; import { Editor as TiptapEditor } from '@tiptap/core'; -import { useEffect, useMemo, useState, useCallback, useRef } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { STYLE_ITEMS, INSERT_ITEMS } from '@/constants/editor'; import { MenuItem } from '@/types/editor'; +const GROUPS = [ + { title: 'Style', items: STYLE_ITEMS }, + { title: 'Insert', items: INSERT_ITEMS }, +]; + +const flatItems = GROUPS.flatMap((group) => group.items); + interface SlashMenuContentProps { editor: TiptapEditor; onClose: () => void; @@ -23,19 +30,6 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { const [selectedIndex, setSelectedIndex] = useState(0); const scrollContainerRef = useRef(null); - /** - * 그룹 구조 정의 - */ - const GROUPS = useMemo( - () => [ - { title: 'Style', items: STYLE_ITEMS }, - { title: 'Insert', items: INSERT_ITEMS }, - ], - [], - ); - - const flatItems = useMemo(() => GROUPS.flatMap((group) => group.items), [GROUPS]); - useEffect(() => { if (flatItems.length === 0) { setSelectedIndex(0); @@ -45,7 +39,7 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { if (selectedIndex >= flatItems.length) { setSelectedIndex(0); } - }, [flatItems.length, selectedIndex]); + }, [selectedIndex]); // 선택된 아이템이 스크롤 영역 밖에 있을 때 자동 스크롤 useEffect(() => { @@ -57,19 +51,16 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { }, [selectedIndex]); // 메뉴 선택 시 실행 - const handleSelect = useCallback( - (item: MenuItem) => { - const { $anchor } = editor.state.selection; - - const from = $anchor.pos - 1; - const to = $anchor.pos; - - editor.chain().focus().deleteRange({ from, to }).run(); - item.command(editor); - onClose(); - }, - [editor, onClose], - ); + const handleSelect = (item: MenuItem) => { + const { $anchor } = editor.state.selection; + + const from = $anchor.pos - 1; + const to = $anchor.pos; + + editor.chain().focus().deleteRange({ from, to }).run(); + item.command(editor); + onClose(); + }; // 키보드 이벤트 핸들링 useEffect(() => { @@ -96,7 +87,7 @@ export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { dom.addEventListener('keydown', handleKeyDown); return () => dom.removeEventListener('keydown', handleKeyDown); - }, [editor, flatItems, selectedIndex, handleSelect, onClose]); + }, [editor, selectedIndex, handleSelect, onClose]); if (flatItems.length === 0) return null; diff --git a/src/components/board/Editor/usePostEditor.ts b/src/components/board/Editor/usePostEditor.ts index af51749..cdaef27 100644 --- a/src/components/board/Editor/usePostEditor.ts +++ b/src/components/board/Editor/usePostEditor.ts @@ -1,7 +1,7 @@ 'use client'; import { useEditor } from '@tiptap/react'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef } from 'react'; import { usePostStore } from '@/stores/usePostStore'; import { editorExtensions } from './extensions'; @@ -12,15 +12,15 @@ export function usePostEditor() { const showSlashMenuRef = useRef(false); const containerRef = useRef(null); - const closeSlashMenu = useCallback(() => { + const closeSlashMenu = () => { showSlashMenuRef.current = false; setShowSlashMenu(false); - }, []); + }; - const updateSlashMenuState = useCallback((isSlash: boolean) => { + const updateSlashMenuState = (isSlash: boolean) => { showSlashMenuRef.current = isSlash; setShowSlashMenu(isSlash); - }, []); + }; const editor = useEditor({ extensions: editorExtensions, From f758a9ada6a09ec22c497cd7ac4b521f3e594831 Mon Sep 17 00:00:00 2001 From: nabbang6 Date: Wed, 25 Feb 2026 21:53:48 +0900 Subject: [PATCH 25/25] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=20=EB=9E=98?= =?UTF-8?q?=EB=B9=97=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20-=20?= =?UTF-8?q?=EB=B2=84=EB=B8=94=20=EB=A9=94=EB=89=B4=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=97=90=20aria-label=20=EC=B6=94=EA=B0=80=20(B/I/<>/H1~H3)=20?= =?UTF-8?q?=20=20-=20parseInt=20radix=20=EB=88=84=EB=9D=BD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(IndentExtension)=20=20=20-=20gap-3,=20my-3=20>=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4=20=20=20-=20handleSelect,=20closeSlashMenu?= =?UTF-8?q?=EC=97=90=20useCallback=20=EC=A0=81=EC=9A=A9=ED=95=B4=20?= =?UTF-8?q?=ED=82=A4=EB=B3=B4=EB=93=9C=20=EB=A6=AC=EC=8A=A4=EB=84=88=20?= =?UTF-8?q?=EB=A7=A4=20=EB=A0=8C=EB=8D=94=20=EC=9E=AC=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=20=20=20-=20onSelectionUpdate=EB=A5=BC=20?= =?UTF-8?q?=EB=8B=AB=EA=B8=B0=20=EC=A0=84=EC=9A=A9=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=B4=20'/'=20=EB=92=A4=20=EC=BB=A4?= =?UTF-8?q?=EC=84=9C=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=20=EC=9D=98=EB=8F=84=EC=B9=98=20=EC=95=8A=EA=B2=8C=20=EC=97=B4?= =?UTF-8?q?=EB=A6=AC=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 --- .../board/Editor/EditorBubbleMenu.tsx | 6 +++++ .../board/Editor/IndentExtension.ts | 2 +- src/components/board/Editor/SlashMenu.tsx | 25 +++++++++++-------- src/components/board/Editor/index.tsx | 2 +- src/components/board/Editor/usePostEditor.ts | 11 +++++--- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/components/board/Editor/EditorBubbleMenu.tsx b/src/components/board/Editor/EditorBubbleMenu.tsx index 0061e1b..a4fb2fc 100644 --- a/src/components/board/Editor/EditorBubbleMenu.tsx +++ b/src/components/board/Editor/EditorBubbleMenu.tsx @@ -33,6 +33,7 @@ export function BubbleMenuBar({ editor, containerRef }: BubbleMenuBarProps) { >
diff --git a/src/components/board/Editor/usePostEditor.ts b/src/components/board/Editor/usePostEditor.ts index cdaef27..6dfdd83 100644 --- a/src/components/board/Editor/usePostEditor.ts +++ b/src/components/board/Editor/usePostEditor.ts @@ -1,7 +1,7 @@ 'use client'; import { useEditor } from '@tiptap/react'; -import { useState, useRef } from 'react'; +import { useState, useRef, useCallback } from 'react'; import { usePostStore } from '@/stores/usePostStore'; import { editorExtensions } from './extensions'; @@ -12,10 +12,10 @@ export function usePostEditor() { const showSlashMenuRef = useRef(false); const containerRef = useRef(null); - const closeSlashMenu = () => { + const closeSlashMenu = useCallback(() => { showSlashMenuRef.current = false; setShowSlashMenu(false); - }; + }, []); const updateSlashMenuState = (isSlash: boolean) => { showSlashMenuRef.current = isSlash; @@ -34,8 +34,11 @@ export function usePostEditor() { // 커서 이동만으로 '/' 뒤를 벗어났을 때도 메뉴를 닫기 위해 추적 onSelectionUpdate: ({ editor }) => { + if (!showSlashMenuRef.current) return; const { $from } = editor.state.selection; - updateSlashMenuState(($from.nodeBefore?.textContent ?? '').endsWith('/')); + if (!($from.nodeBefore?.textContent ?? '').endsWith('/')) { + closeSlashMenu(); + } }, editorProps: {