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 706a6b1..8f6334b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,35 @@ "format:check": "prettier --check ." }, "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", @@ -21,6 +49,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "tailwind-merge": "^2.6.0", + "tippy.js": "^6.3.7", "tw-animate-css": "^1.4.0", "zustand": "^5.0.2" }, @@ -29,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 4b1651d..e81f9b5 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 @@ -22,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) @@ -35,6 +119,9 @@ importers: tailwind-merge: specifier: ^2.6.0 version: 2.6.1 + tippy.js: + specifier: ^6.3.7 + version: 6.3.7 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -54,12 +141,15 @@ 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) 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)) @@ -473,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==} @@ -1163,6 +1256,9 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@remirror/core-constants@2.0.2': + resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1261,6 +1357,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 +1370,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 +1527,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 +1547,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': @@ -1533,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==} @@ -1571,8 +1829,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 +1859,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 +1942,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 +2078,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 +2516,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 +2541,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 +2607,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 +2650,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 +2695,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 +2780,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 +2924,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 +3080,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 +3122,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 +3134,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 +3181,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'} @@ -3234,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': {} @@ -3981,6 +4356,8 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@remirror/core-constants@2.0.2': {} + '@rtsao/scc@1.1.0': {} '@swc/helpers@0.5.15': @@ -4056,6 +4433,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 +4445,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 +4597,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 +4618,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 +4634,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 +4676,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 +4693,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 @@ -4340,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: {} @@ -4360,7 +4896,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 +4920,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001770: {} chalk@4.1.2: dependencies: @@ -4409,12 +4945,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 +5022,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 +5129,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 +5172,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 +5198,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 +5210,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 +5263,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 +5277,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 +5715,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 +5741,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: @@ -5220,12 +5779,12 @@ 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 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) @@ -5239,11 +5798,19 @@ 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' - 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 +5864,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 +5898,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 +5929,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 +6167,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 +6384,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 +6444,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 +6457,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -5831,6 +6521,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 diff --git a/src/app/(private)/(main)/board/page.tsx b/src/app/(private)/(main)/board/page.tsx index ebb42c5..1868544 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/app/globals.css b/src/app/globals.css index d3930fc..0ba5d08 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,63 @@ select:focus { background-clip: content-box; } +.ProseMirror { + color: var(--text-normal); +} + +.ProseMirror h1 { + @apply typo-h1; +} + +.ProseMirror h2 { + @apply typo-h2; +} + +.ProseMirror h3 { + @apply typo-h3; +} + +/* 인용 */ +.ProseMirror blockquote { + font-style: italic; + border-left: 3px solid var(--line); + padding-left: var(--padding-400); + margin: var(--margin-300) 0; + font-weight: normal; +} + +.ProseMirror blockquote p::before, +.ProseMirror blockquote p::after { + content: none !important; +} + +/* 인라인 코드 */ +.ProseMirror code:not(pre code) { + background-color: var(--container-neutral-alternative); + color: var(--text-normal); + padding: var(--padding-100) var(--padding-200); + border-radius: var(--radius-sm); + font-weight: normal; +} + +.ProseMirror code::before, +.ProseMirror code::after { + content: none; +} + +/* 코드 블록 */ +.ProseMirror pre { + background-color: var(--container-neutral-alternative); + border-radius: var(--radius-sm); + padding: var(--padding-300) var(--padding-400); +} + +.ProseMirror pre code { + background-color: transparent; + color: var(--text-normal); + padding: 0; +} + @layer base { * { @apply border-border outline-ring/50; @@ -494,4 +551,4 @@ select:focus { body { @apply bg-background text-foreground; } -} \ No newline at end of file +} diff --git a/src/components/board/Editor/EditorBubbleMenu.tsx b/src/components/board/Editor/EditorBubbleMenu.tsx new file mode 100644 index 0000000..a4fb2fc --- /dev/null +++ b/src/components/board/Editor/EditorBubbleMenu.tsx @@ -0,0 +1,103 @@ +'use client'; + +import { BubbleMenu } from '@tiptap/react'; +import { Editor as TiptapEditor } from '@tiptap/core'; +import { cva } from 'class-variance-authority'; +import { cn } from '@/lib/cn'; +import { RefObject } from 'react'; + +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 }, +}); + +interface BubbleMenuBarProps { + editor: TiptapEditor; + containerRef: RefObject; +} + +export function BubbleMenuBar({ editor, containerRef }: BubbleMenuBarProps) { + return ( + containerRef.current ?? document.body, + }} + className="border-line bg-container-neutral flex items-center gap-100 rounded-lg border p-100 shadow-md" + > + + + +
+ + + + + ); +} diff --git a/src/components/board/Editor/IndentExtension.ts b/src/components/board/Editor/IndentExtension.ts new file mode 100644 index 0000000..e372e29 --- /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', 10), + // 렌더링 시 단계에 비례한 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/SlashMenu.tsx b/src/components/board/Editor/SlashMenu.tsx new file mode 100644 index 0000000..9016d98 --- /dev/null +++ b/src/components/board/Editor/SlashMenu.tsx @@ -0,0 +1,148 @@ +'use client'; + +import { Editor as TiptapEditor } from '@tiptap/core'; +import { useEffect, useState, useRef, useCallback } 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; +} + +/** + * Slash Command 메뉴 UI + * + * 역할: + * - '/' 입력 후 나타나는 커맨드 목록 렌더링 + * - 키보드 탐색 (↑ ↓ Enter Escape) + * - 선택 시 slash 문자 제거 후 해당 command 실행 + */ + +export function SlashMenuContent({ editor, onClose }: SlashMenuContentProps) { + const [selectedIndex, setSelectedIndex] = useState(0); + const scrollContainerRef = useRef(null); + + useEffect(() => { + if (flatItems.length === 0) { + setSelectedIndex(0); + return; + } + + if (selectedIndex >= flatItems.length) { + setSelectedIndex(0); + } + }, [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) => { + 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, selectedIndex, handleSelect, onClose]); + + if (flatItems.length === 0) return null; + + // runningIndex는 그룹을 넘어서도 연속된 flat index를 부여하기 위해 사용 + let runningIndex = 0; + + return ( +
+
+ {GROUPS.map((group, groupIdx) => ( +
+
+

+ {group.title} +

+
+ + {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..fb48955 --- /dev/null +++ b/src/components/board/Editor/extensions.ts @@ -0,0 +1,49 @@ +import { IndentExtension } from './IndentExtension'; +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 }), + IndentExtension, +]; diff --git a/src/components/board/Editor/index.tsx b/src/components/board/Editor/index.tsx new file mode 100644 index 0000000..abc2527 --- /dev/null +++ b/src/components/board/Editor/index.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { EditorContent, FloatingMenu } from '@tiptap/react'; +import { usePostEditor } from './usePostEditor'; +import { BubbleMenuBar } from './EditorBubbleMenu'; +import { SlashMenuContent } from './SlashMenu'; + +/** + * 주요 기능: + * Tiptap 기반 에디터 컴포넌트 + * + * - Slash Menu (/) + * - Bubble Menu (텍스트 선택 시) + * - 커스텀 키보드 단축키 (` 인라인 코드, Backspace 블록 정리) + */ + +export default function Editor() { + const { editor, showSlashMenu, closeSlashMenu, containerRef } = usePostEditor(); + + if (!editor) return null; + + return ( +
+ + + containerRef.current ?? document.body, + }} + shouldShow={({ state }) => { + const { $from } = state.selection; + return ($from.nodeBefore?.textContent ?? '').endsWith('/'); + }} + > + {showSlashMenu && } + + +
+ +
+
+ ); +} diff --git a/src/components/board/Editor/usePostEditor.ts b/src/components/board/Editor/usePostEditor.ts new file mode 100644 index 0000000..6dfdd83 --- /dev/null +++ b/src/components/board/Editor/usePostEditor.ts @@ -0,0 +1,99 @@ +'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 = (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 }) => { + if (!showSlashMenuRef.current) return; + const { $from } = editor.state.selection; + if (!($from.nodeBefore?.textContent ?? '').endsWith('/')) { + closeSlashMenu(); + } + }, + + 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 }; +} diff --git a/src/constants/editor.ts b/src/constants/editor.ts new file mode 100644 index 0000000..783ba38 --- /dev/null +++ b/src/constants/editor.ts @@ -0,0 +1,80 @@ +import { Editor as TiptapEditor } from '@tiptap/core'; +import { + Type, + Heading1, + Heading2, + Heading3, + List, + ListOrdered, + ListTodo, + Quote, + Code2, + Minus, +} from 'lucide-react'; +import { MenuItem } from '@/types/editor'; + +export const STYLE_ITEMS: MenuItem[] = [ + { + label: 'Text', + description: '일반 텍스트', + icon: Type, + command: (editor: TiptapEditor) => editor.chain().focus().clearNodes().setParagraph().run(), + }, + { + label: 'Heading 1', + description: '큰 제목', + icon: Heading1, + command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 1 }).run(), + }, + { + label: 'Heading 2', + description: '중간 제목', + icon: Heading2, + command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 2 }).run(), + }, + { + label: 'Heading 3', + description: '작은 제목', + icon: Heading3, + command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 3 }).run(), + }, + { + label: 'Bullet List', + description: '순서 없는 목록', + icon: List, + command: (editor: TiptapEditor) => editor.chain().focus().toggleBulletList().run(), + }, + { + label: 'Numbered List', + description: '순서 있는 목록', + icon: ListOrdered, + command: (editor: TiptapEditor) => editor.chain().focus().toggleOrderedList().run(), + }, + { + label: 'To-do List', + description: '체크리스트', + icon: ListTodo, + command: (editor: TiptapEditor) => editor.chain().focus().toggleTaskList().run(), + }, + { + label: 'Blockquote', + description: '인용', + icon: Quote, + command: (editor: TiptapEditor) => editor.chain().focus().toggleBlockquote().run(), + }, + { + label: 'Code Block', + description: '코드 블록', + icon: Code2, + command: (editor: TiptapEditor) => editor.chain().focus().toggleCodeBlock().run(), + }, +]; + +export const INSERT_ITEMS: MenuItem[] = [ + { + label: 'Separator', + description: '구분선', + icon: Minus, + command: (editor: TiptapEditor) => editor.chain().focus().setHorizontalRule().run(), + }, +]; diff --git a/src/stores/usePostStore.ts b/src/stores/usePostStore.ts new file mode 100644 index 0000000..2963d78 --- /dev/null +++ b/src/stores/usePostStore.ts @@ -0,0 +1,82 @@ +import { create } from 'zustand'; +import { combine, devtools } from 'zustand/middleware'; + +export interface FileItem { + file: File; + fileName: string; + fileUrl: string; + uploaded: boolean; +} + +const initialState = { + board: '', + title: '', + cardinalNumber: 0, + part: '', + category: '', + studyName: '', + week: 0, + content: '', + files: [] as FileItem[], + status: 'DRAFT' as 'DRAFT' | 'PUBLISHED', +}; + +export type PostState = typeof initialState; + +export const usePostStore = create( + devtools( + combine(initialState, (set, get) => ({ + setBoard: (board: string) => set({ board }, false, 'setBoard'), + setTitle: (title: string) => set({ title }, false, 'setTitle'), + setCardinalNumber: (cardinalNumber: number) => set({ cardinalNumber }, false, 'setCardinalNumber'), + setPart: (part: string) => set({ part }, false, 'setPart'), + setCategory: (category: string) => set({ category }, false, 'setCategory'), + setStudyName: (studyName: string) => set({ studyName }, false, 'setStudyName'), + setWeek: (week: number) => set({ week }, false, 'setWeek'), + setContent: (content: string) => set({ content }, false, 'setContent'), + + addFile: (file: FileItem) => + set((state) => ({ files: [...state.files, file] }), false, 'addFile'), + + removeFile: (fileName: string) => + set( + (state) => ({ files: state.files.filter((f) => f.fileName !== fileName) }), + false, + 'removeFile', + ), + + updateFileUrl: (fileName: string, fileUrl: string) => + set( + (state) => ({ + files: state.files.map((f) => + f.fileName === fileName ? { ...f, fileUrl, uploaded: true } : f, + ), + }), + false, + 'updateFileUrl', + ), + + setStatus: (status: 'DRAFT' | 'PUBLISHED') => + set({ status }, false, 'setStatus'), + + reset: () => set(initialState, false, 'reset'), + + 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.cardinalNumber, + files: state.files + .filter((f) => f.uploaded) + .map(({ fileName, fileUrl }) => ({ fileName, fileUrl })), + }; + }, + })), + { name: 'PostStore' }, + ), +); diff --git a/src/types/editor.ts b/src/types/editor.ts new file mode 100644 index 0000000..ba86e27 --- /dev/null +++ b/src/types/editor.ts @@ -0,0 +1,9 @@ +import { Editor as TiptapEditor } from '@tiptap/core'; +import { LucideIcon } from 'lucide-react'; + +export interface MenuItem { + label: string; + description: string; + icon: LucideIcon; + command: (editor: TiptapEditor) => void; +}