From 74578ad26b03eba8a24651a612ee0a92beb1c24f Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Tue, 3 Mar 2026 19:15:28 +0900
Subject: [PATCH 01/15] =?UTF-8?q?#154=20[FEAT]=20tiptap=20=ED=8C=A8?=
=?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 25 +--
pnpm-lock.yaml | 438 ++++++++++++++++++++++++++-----------------------
2 files changed, 242 insertions(+), 221 deletions(-)
diff --git a/package.json b/package.json
index d03f80e..baa1a62 100644
--- a/package.json
+++ b/package.json
@@ -13,18 +13,19 @@
"@next/third-parties": "^16.1.5",
"@tailwindcss/typography": "^0.5.19",
"@tanstack/react-query": "^5.90.5",
- "@tiptap/core": "^3.7.2",
- "@tiptap/extension-document": "^3.7.2",
- "@tiptap/extension-heading": "^3.7.2",
- "@tiptap/extension-link": "^3.7.2",
- "@tiptap/extension-list": "^3.7.2",
- "@tiptap/extension-paragraph": "^3.7.2",
- "@tiptap/extension-placeholder": "^3.7.2",
- "@tiptap/extension-text": "^3.7.2",
- "@tiptap/extension-underline": "^3.7.2",
- "@tiptap/pm": "^3.7.2",
- "@tiptap/react": "^3.7.2",
- "@tiptap/starter-kit": "^3.7.2",
+ "@tiptap/core": "^3.20.0",
+ "@tiptap/extension-document": "^3.20.0",
+ "@tiptap/extension-heading": "^3.20.0",
+ "@tiptap/extension-link": "^3.20.0",
+ "@tiptap/extension-list": "^3.20.0",
+ "@tiptap/extension-paragraph": "^3.20.0",
+ "@tiptap/extension-placeholder": "^3.20.0",
+ "@tiptap/extension-table": "^3.20.0",
+ "@tiptap/extension-text": "^3.20.0",
+ "@tiptap/extension-underline": "^3.20.0",
+ "@tiptap/pm": "^3.20.0",
+ "@tiptap/react": "^3.20.0",
+ "@tiptap/starter-kit": "^3.20.0",
"axios": "^1.12.2",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index df99127..4259d03 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -21,41 +21,44 @@ importers:
specifier: ^5.90.5
version: 5.90.5(react@19.1.0)
'@tiptap/core':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/pm@3.7.2)
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/pm@3.20.0)
'@tiptap/extension-document':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
'@tiptap/extension-heading':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
'@tiptap/extension-link':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
'@tiptap/extension-list':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
'@tiptap/extension-paragraph':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
'@tiptap/extension-placeholder':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-table':
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
'@tiptap/extension-text':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
'@tiptap/extension-underline':
- specifier: ^3.7.2
- version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
+ specifier: ^3.20.0
+ version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
'@tiptap/pm':
- specifier: ^3.7.2
- version: 3.7.2
+ specifier: ^3.20.0
+ version: 3.20.0
'@tiptap/react':
- specifier: ^3.7.2
- version: 3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ specifier: ^3.20.0
+ version: 3.20.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tiptap/starter-kit':
- specifier: ^3.7.2
- version: 3.7.2
+ specifier: ^3.20.0
+ version: 3.20.0
axios:
specifier: ^1.12.2
version: 1.12.2
@@ -1368,159 +1371,165 @@ packages:
peerDependencies:
react: ^18 || ^19
- '@tiptap/core@3.7.2':
- resolution: {integrity: sha512-fJwNpTx0aq4UU0HNkxPvPYfNBcTHQ/q5xBUdOB5Mgu6clwGES38jVsNNSudB8g53APUmJIS+2fJbkxl3V+0jww==}
+ '@tiptap/core@3.20.0':
+ resolution: {integrity: sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ==}
peerDependencies:
- '@tiptap/pm': ^3.7.2
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-blockquote@3.7.2':
- resolution: {integrity: sha512-8rNDh1E1ratex9KicvNNnjJGtF313Kpf5hXHOUcIm8FQwvA/0Tu6jq7r6VgESMyo95R3EmzRpnCYQef+zDm6OQ==}
+ '@tiptap/extension-blockquote@3.20.0':
+ resolution: {integrity: sha512-LQzn6aGtL4WXz2+rYshl/7/VnP2qJTpD7fWL96GXAzhqviPEY1bJES7poqJb3MU/gzl8VJUVzVzU1VoVfUKlbA==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-bold@3.7.2':
- resolution: {integrity: sha512-bwCn9lQEXnEi7LfIx3G/oaH4I0ZapAgrHzLCNJH/tNgRKVWym1H1Oa8PlkiFDbalWOdUkbgeAUqUaIB13k408Q==}
+ '@tiptap/extension-bold@3.20.0':
+ resolution: {integrity: sha512-sQklEWiyf58yDjiHtm5vmkVjfIc/cBuSusmCsQ0q9vGYnEF1iOHKhGpvnCeEXNeqF3fiJQRlquzt/6ymle3Iwg==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-bubble-menu@3.7.2':
- resolution: {integrity: sha512-rCJu/X7sZEYWkOwLO342JP06f4giVBECPzr/SzG/fQdAidPW96eilPk3L82w5j24kS9odTlxSLlFlIf6UZ2b9w==}
+ '@tiptap/extension-bubble-menu@3.20.0':
+ resolution: {integrity: sha512-MDosUfs8Tj+nwg8RC+wTMWGkLJORXmbR6YZgbiX4hrc7G90Gopdd6kj6ht5/T8t7dLLaX7N0+DEHdUEPGED7dw==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-bullet-list@3.7.2':
- resolution: {integrity: sha512-OHYYXKjmxisLQws0tW8Dz14PcyIJmaed7eypZvIm/R3hxa/7lJY/2EM/Ti5g/w1U8WPBEH1hX3icRtiulserKw==}
+ '@tiptap/extension-bullet-list@3.20.0':
+ resolution: {integrity: sha512-OcKMeopBbqWzhSi6o8nNz0aayogg1sfOAhto3NxJu3Ya32dwBFqmHXSYM6uW4jOphNvVPyjiq9aNRh3qTdd1dw==}
peerDependencies:
- '@tiptap/extension-list': ^3.7.2
+ '@tiptap/extension-list': ^3.20.0
- '@tiptap/extension-code-block@3.7.2':
- resolution: {integrity: sha512-TfixutvvbGCrSSCsfDK/PBm6A5FIzcPTSVDrmmsiAfqldj/Woy1T42dads+wv9SjKG06GlWDwYtDGAk2Uun8NA==}
+ '@tiptap/extension-code-block@3.20.0':
+ resolution: {integrity: sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-code@3.7.2':
- resolution: {integrity: sha512-J8FaCiKJJnHvQiPcbfbUtc5RNmGx/Gui/K5CDMPc17jhCiQ9JhR9idRPREV24Z2t7GujWX7LG6ZDDR82pSns+g==}
+ '@tiptap/extension-code@3.20.0':
+ resolution: {integrity: sha512-TYDWFeSQ9umiyrqsT6VecbuhL8XIHkUhO+gEk0sVvH67ZLwjFDhAIIgWIr1/dbIGPcvMZM19E7xUUhAdIaXaOQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-document@3.7.2':
- resolution: {integrity: sha512-OrHl402v2FWCUKR1Xi5MTNBAkKYQh7mtpw/WlJDFnk5z1qHLqz4UIcbGilDYzVPrNUZPhA1p3c+V5UUVUFzUfg==}
+ '@tiptap/extension-document@3.20.0':
+ resolution: {integrity: sha512-oJfLIG3vAtZo/wg29WiBcyWt22KUgddpP8wqtCE+kY5Dw8znLR9ehNmVWlSWJA5OJUMO0ntAHx4bBT+I2MBd5w==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-dropcursor@3.7.2':
- resolution: {integrity: sha512-79y6M9pJYwqcqBHIWoomfptJp0QB/TP3Y+2NOL09sMNeSdUgmz5pCVObA4H48YMkoB0EcUtux2IUOM66e4nsJA==}
+ '@tiptap/extension-dropcursor@3.20.0':
+ resolution: {integrity: sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ==}
peerDependencies:
- '@tiptap/extensions': ^3.7.2
+ '@tiptap/extensions': ^3.20.0
- '@tiptap/extension-floating-menu@3.7.2':
- resolution: {integrity: sha512-g19ratrXlplYDS29VLQa1y/IM/ro0UFhSS4fQokiQKkazwnA1ZVnebjw8ERYg5lkMm/hiImqstpgdO0LtoivvQ==}
+ '@tiptap/extension-floating-menu@3.20.0':
+ resolution: {integrity: sha512-rYs4Bv5pVjqZ/2vvR6oe7ammZapkAwN51As/WDbemvYDjfOGRqK58qGauUjYZiDzPOEIzI2mxGwsZ4eJhPW4Ig==}
peerDependencies:
'@floating-ui/dom': ^1.0.0
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-gapcursor@3.7.2':
- resolution: {integrity: sha512-vCLo2dL2SfeWjh/gJKDiu0/fz6OF7obGTJvHg/yStkoUqlAEiwKoyHP/NXeTGYJMzZzUi0kY9DtTEJdGFvphuQ==}
+ '@tiptap/extension-gapcursor@3.20.0':
+ resolution: {integrity: sha512-P/LasfvG9/qFq43ZAlNbAnPnXC+/RJf49buTrhtFvI9Zg0+Lbpjx1oh6oMHB19T88Y28KtrckfFZ8aTSUWDq6w==}
peerDependencies:
- '@tiptap/extensions': ^3.7.2
+ '@tiptap/extensions': ^3.20.0
- '@tiptap/extension-hard-break@3.7.2':
- resolution: {integrity: sha512-nNDo+5S1yRQ3JkBM+gwpEEVZ/Kw9qWoG/cpShyGYDHo1/y8MgO+VI0kSb/LuBTw7g+jmNXdf+ZaRRI/pXsUihg==}
+ '@tiptap/extension-hard-break@3.20.0':
+ resolution: {integrity: sha512-rqvhMOw4f+XQmEthncbvDjgLH6fz8L9splnKZC7OeS0eX8b0qd7+xI1u5kyxF3KA2Z0BnigES++jjWuecqV6mA==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-heading@3.7.2':
- resolution: {integrity: sha512-eH/G66FIRlTQz4MhEmlNNNQgVTxhoqlkyFzgeG5aipIplYOdYa5Y6Wl0NF4xqr1jAHGLAK6LaYS4FXp3TE7LyA==}
+ '@tiptap/extension-heading@3.20.0':
+ resolution: {integrity: sha512-JgJhurnCe3eN6a0lEsNQM/46R1bcwzwWWZEFDSb1P9dR8+t1/5v7cMZWsSInpD7R4/74iJn0+M5hcXLwCmBmYA==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-horizontal-rule@3.7.2':
- resolution: {integrity: sha512-pN+1hJAVVP3uqtpZ5Rm7z5XUB/NGprK6wExJ04xG117E4rTVcaEb1FnMILY3J3A5XbdC3vHX+cblR8mOl1PAMw==}
+ '@tiptap/extension-horizontal-rule@3.20.0':
+ resolution: {integrity: sha512-6uvcutFMv+9wPZgptDkbRDjAm3YVxlibmkhWD5GuaWwS9L/yUtobpI3GycujRSUZ8D3q6Q9J7LqpmQtQRTalWA==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-italic@3.7.2':
- resolution: {integrity: sha512-1tfF37LvKgA5hg09UBgOjdMLNRb1C6keIOBF0r5oHKeWPYOf4z3j5IU9PsFUoOn53XRMb1aiD/TNbGPyoT3Fyw==}
+ '@tiptap/extension-italic@3.20.0':
+ resolution: {integrity: sha512-/DhnKQF8yN8RxtuL8abZ28wd5281EaGoE2Oha35zXSOF1vNYnbyt8Ymkv/7u1BcWEWTvRPgaju0YCGXisPRLYw==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-link@3.7.2':
- resolution: {integrity: sha512-9K54PxBiDSWAMfICqkb8jcQ6cL7vDAtjTk0zqBw4d+XuaUy0FC9QUdbx7r1Pkbf36K1/ApbvM9a7qpOirWk8Xw==}
+ '@tiptap/extension-link@3.20.0':
+ resolution: {integrity: sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-list-item@3.7.2':
- resolution: {integrity: sha512-962TFsx4eF5NMyLVhGFGF/btt5j3MipPhDiUsxzBgnlW8o5OonVepb9cDrqpEDQ2/wLvheWnCKuvmG7umasldQ==}
+ '@tiptap/extension-list-item@3.20.0':
+ resolution: {integrity: sha512-qEtjaaGPuqaFB4VpLrGDoIe9RHnckxPfu6d3rc22ap6TAHCDyRv05CEyJogqccnFceG/v5WN4znUBER8RWnWHA==}
peerDependencies:
- '@tiptap/extension-list': ^3.7.2
+ '@tiptap/extension-list': ^3.20.0
- '@tiptap/extension-list-keymap@3.7.2':
- resolution: {integrity: sha512-1du9eo+NPIkuRT258yUn9bovhip556aJo/yDtRbswEVNScP1E8y/kFRWvw0HD7/YWcNqok1ZteoSwShWnKAXRQ==}
+ '@tiptap/extension-list-keymap@3.20.0':
+ resolution: {integrity: sha512-Z4GvKy04Ms4cLFN+CY6wXswd36xYsT2p/YL0V89LYFMZTerOeTjFYlndzn6svqL8NV1PRT5Diw4WTTxJSmcJPA==}
peerDependencies:
- '@tiptap/extension-list': ^3.7.2
+ '@tiptap/extension-list': ^3.20.0
- '@tiptap/extension-list@3.7.2':
- resolution: {integrity: sha512-/tYHmEkOGcVweAc9ZgnAXkzua5aJfu7TjZcKTq5fmDt6x9MY1eY1+egS7D9hVR2sUSAC10VgXmYdYPDsKF3p2g==}
+ '@tiptap/extension-list@3.20.0':
+ resolution: {integrity: sha512-+V0/gsVWAv+7vcY0MAe6D52LYTIicMSHw00wz3ISZgprSb2yQhJ4+4gurOnUrQ4Du3AnRQvxPROaofwxIQ66WQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-ordered-list@3.7.2':
- resolution: {integrity: sha512-Tu61/JXh1RRd3Kb+s7A7jmpnB+w1pqGSRfMXBtYHDHDIGyXu255ru7soX44lJfHGq/zYcTFSHGSsi8o23QONJg==}
+ '@tiptap/extension-ordered-list@3.20.0':
+ resolution: {integrity: sha512-jVKnJvrizLk7etwBMfyoj6H2GE4M+PD4k7Bwp6Bh1ohBWtfIA1TlngdS842Mx5i1VB2e3UWIwr8ZH46gl6cwMA==}
peerDependencies:
- '@tiptap/extension-list': ^3.7.2
+ '@tiptap/extension-list': ^3.20.0
- '@tiptap/extension-paragraph@3.7.2':
- resolution: {integrity: sha512-HmDuAixTcvP4A/v6OLkh/C6nB86i7/DRNswBf/Udak8TgWUIcSUK0iActxxm5+B3MZTSf3U87JzyI6IeuElLIQ==}
+ '@tiptap/extension-paragraph@3.20.0':
+ resolution: {integrity: sha512-mM99zK4+RnEXIMCv6akfNATAs0Iija6FgyFA9J9NZ6N4o8y9QiNLLa6HjLpAC+W+VoCgQIekyoF/Q9ftxmAYDQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-placeholder@3.7.2':
- resolution: {integrity: sha512-YUr1rlxkgEBQDsMLpU8ruA4Uet37kXvwwFwIbDgaFd4NpfAD0fvX2zmPhHIBzsdH3e4V6eNp6IkmoYCWvugAAA==}
+ '@tiptap/extension-placeholder@3.20.0':
+ resolution: {integrity: sha512-ZhYD3L5m16ydSe2z8vqz+RdtAG/iOQaFHHedFct70tKRoLqi2ajF5kgpemu8DwpaRTcyiCN4G99J/+MqehKNjQ==}
peerDependencies:
- '@tiptap/extensions': ^3.7.2
+ '@tiptap/extensions': ^3.20.0
- '@tiptap/extension-strike@3.7.2':
- resolution: {integrity: sha512-I1G+4vZbCBTpAMmyVwaO8cLBJgXEf1DyEzc0B+HhTJiBa9qA9OKgRQEGFgisxu1kggjbzB6+d0+taHfjsZC1SQ==}
+ '@tiptap/extension-strike@3.20.0':
+ resolution: {integrity: sha512-0vcTZRRAiDfon3VM1mHBr9EFmTkkUXMhm0Xtdtn0bGe+sIqufyi+hUYTEw93EQOD9XNsPkrud6jzQNYpX2H3AQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extension-text@3.7.2':
- resolution: {integrity: sha512-sKaeGYNP1+bAe2rvmzWLW5qH9DsSFOJlOUEOFchR0OX0rC7bbGS6/KuyAq0w6UkL+cMJnDyAbv3KeD2WEA192w==}
+ '@tiptap/extension-table@3.20.0':
+ resolution: {integrity: sha512-vaaMtQ2KnSSr8WVwgWf7NYNzPwrHx/6T0ekA5CxV8qNUEpXIaLXa5+tE7tJHWEdNR2KY3gUJ46D3lfOkxyFrBQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
- '@tiptap/extension-underline@3.7.2':
- resolution: {integrity: sha512-GDpUZllTD7uIdHjTzYJ6i4jUgCeviW40SCpLVVv1xH0gj1t1xu0Rnxmk+bXkF2XNe8jPXkMCgYNr6DR6eO8roQ==}
+ '@tiptap/extension-text@3.20.0':
+ resolution: {integrity: sha512-tf8bE8tSaOEWabCzPm71xwiUhyMFKqY9jkP5af3Kr1/F45jzZFIQAYZooHI/+zCHRrgJ99MQHKHe1ZNvODrKHQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/extensions@3.7.2':
- resolution: {integrity: sha512-FaToSdU9fhQk2swkaXrAQNgdaE0dwLbUHcvilW5F4xTpQfZ3J535u5U2TUYf+f9KKSV5fTmD4QGNY9qxY7ihTg==}
+ '@tiptap/extension-underline@3.20.0':
+ resolution: {integrity: sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
- '@tiptap/pm@3.7.2':
- resolution: {integrity: sha512-i2fvXDapwo/TWfHM6STYEbkYyF3qyfN6KEBKPrleX/Z80G5bLxom0gB79TsjLNxTLi6mdf0vTHgAcXMG1avc2g==}
+ '@tiptap/extensions@3.20.0':
+ resolution: {integrity: sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg==}
+ peerDependencies:
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
+
+ '@tiptap/pm@3.20.0':
+ resolution: {integrity: sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A==}
- '@tiptap/react@3.7.2':
- resolution: {integrity: sha512-tka4ioSmsGI4TyGZ7jAUoIw8t8DVjr1It0B38vZVLqg8M/ZFgR1NkF50TJ6qAkhy8Uz12AO50so0v79tV2pmEA==}
+ '@tiptap/react@3.20.0':
+ resolution: {integrity: sha512-jFLNzkmn18zqefJwPje0PPd9VhZ7Oy28YHiSvSc7YpBnQIbuN/HIxZ2lrOsKyEHta0WjRZjfU5X1pGxlbcGwOA==}
peerDependencies:
- '@tiptap/core': ^3.7.2
- '@tiptap/pm': ^3.7.2
+ '@tiptap/core': ^3.20.0
+ '@tiptap/pm': ^3.20.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
'@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
- '@tiptap/starter-kit@3.7.2':
- resolution: {integrity: sha512-43GwI+2Mtc/ci7J4eJOE02wZxp5KIsDTMMb0peNksPcEGaURGdDhav9zbAW24NRjRxU7Auk/zaQu9O8+ZE0v0A==}
+ '@tiptap/starter-kit@3.20.0':
+ resolution: {integrity: sha512-W4+1re35pDNY/7rpXVg+OKo/Fa4Gfrn08Bq3E3fzlJw6gjE3tYU8dY9x9vC2rK9pd9NOp7Af11qCFDaWpohXkw==}
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
@@ -2272,6 +2281,10 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+ fast-equals@5.4.0:
+ resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==}
+ engines: {node: '>=6.0.0'}
+
fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
engines: {node: '>=8.6.0'}
@@ -4974,123 +4987,128 @@ snapshots:
'@tanstack/query-core': 5.90.5
react: 19.1.0
- '@tiptap/core@3.7.2(@tiptap/pm@3.7.2)':
+ '@tiptap/core@3.20.0(@tiptap/pm@3.20.0)':
dependencies:
- '@tiptap/pm': 3.7.2
+ '@tiptap/pm': 3.20.0
- '@tiptap/extension-blockquote@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-blockquote@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-bold@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-bold@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-bubble-menu@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-bubble-menu@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
'@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
optional: true
- '@tiptap/extension-bullet-list@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-bullet-list@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-code-block@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-code-block@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
- '@tiptap/extension-code@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-code@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-document@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-document@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-dropcursor@3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-dropcursor@3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-floating-menu@3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-floating-menu@3.20.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
'@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
optional: true
- '@tiptap/extension-gapcursor@3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-gapcursor@3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-hard-break@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-hard-break@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-heading@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-heading@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-horizontal-rule@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-horizontal-rule@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
- '@tiptap/extension-italic@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-italic@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-link@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-link@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
linkifyjs: 4.3.2
- '@tiptap/extension-list-item@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-list-item@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-list-keymap@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-list-keymap@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
- '@tiptap/extension-ordered-list@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-ordered-list@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-paragraph@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-paragraph@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-placeholder@3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-placeholder@3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
- '@tiptap/extension-strike@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-strike@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extension-text@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-table@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
- '@tiptap/extension-underline@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))':
+ '@tiptap/extension-text@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)':
+ '@tiptap/extension-underline@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
- '@tiptap/pm@3.7.2':
+ '@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)':
+ dependencies:
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
+
+ '@tiptap/pm@3.20.0':
dependencies:
prosemirror-changeset: 2.3.1
prosemirror-collab: 1.3.1
@@ -5111,49 +5129,49 @@ snapshots:
prosemirror-transform: 1.10.4
prosemirror-view: 1.41.3
- '@tiptap/react@3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ '@tiptap/react@3.20.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
'@types/react': 19.1.16
'@types/react-dom': 19.1.9(@types/react@19.1.16)
'@types/use-sync-external-store': 0.0.6
- fast-deep-equal: 3.1.3
+ fast-equals: 5.4.0
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
use-sync-external-store: 1.6.0(react@19.1.0)
optionalDependencies:
- '@tiptap/extension-bubble-menu': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
- '@tiptap/extension-floating-menu': 3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
+ '@tiptap/extension-bubble-menu': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
+ '@tiptap/extension-floating-menu': 3.20.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
transitivePeerDependencies:
- '@floating-ui/dom'
- '@tiptap/starter-kit@3.7.2':
- dependencies:
- '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2)
- '@tiptap/extension-blockquote': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-bold': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-bullet-list': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
- '@tiptap/extension-code': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-code-block': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
- '@tiptap/extension-document': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-dropcursor': 3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
- '@tiptap/extension-gapcursor': 3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
- '@tiptap/extension-hard-break': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-heading': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-horizontal-rule': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
- '@tiptap/extension-italic': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-link': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
- '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
- '@tiptap/extension-list-item': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
- '@tiptap/extension-list-keymap': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
- '@tiptap/extension-ordered-list': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))
- '@tiptap/extension-paragraph': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-strike': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-text': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extension-underline': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))
- '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)
- '@tiptap/pm': 3.7.2
+ '@tiptap/starter-kit@3.20.0':
+ dependencies:
+ '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
+ '@tiptap/extension-blockquote': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-bold': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-bullet-list': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-code': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-code-block': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
+ '@tiptap/extension-document': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-dropcursor': 3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-gapcursor': 3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-hard-break': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-heading': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-horizontal-rule': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
+ '@tiptap/extension-italic': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-link': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
+ '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
+ '@tiptap/extension-list-item': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-list-keymap': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-ordered-list': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))
+ '@tiptap/extension-paragraph': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-strike': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-text': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extension-underline': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
+ '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)
+ '@tiptap/pm': 3.20.0
'@trysound/sax@0.2.0': {}
@@ -6074,6 +6092,8 @@ snapshots:
fast-deep-equal@3.1.3: {}
+ fast-equals@5.4.0: {}
+
fast-glob@3.3.1:
dependencies:
'@nodelib/fs.stat': 2.0.5
From cc94701b81ce114aec03b93688ceb777c136dfc9 Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Tue, 3 Mar 2026 19:20:52 +0900
Subject: [PATCH 02/15] =?UTF-8?q?#154=20[FEAT]=20=ED=91=9C=20=EB=B2=84?=
=?UTF-8?q?=ED=8A=BC=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/recruit/editor/Toolbar.tsx | 25 +++++++++++++++++
.../recruit/editor/editorExtensions.ts | 27 ++++++++++++++++++-
2 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/src/components/recruit/editor/Toolbar.tsx b/src/components/recruit/editor/Toolbar.tsx
index 20c0aa3..d67fb8d 100644
--- a/src/components/recruit/editor/Toolbar.tsx
+++ b/src/components/recruit/editor/Toolbar.tsx
@@ -173,6 +173,31 @@ const Toolbar = ({ editor, onLinkButtonClick }: ToolbarProps) => {
className="h-6 w-6"
/>
+
);
};
diff --git a/src/components/recruit/editor/editorExtensions.ts b/src/components/recruit/editor/editorExtensions.ts
index 5320f62..3fd3ccf 100644
--- a/src/components/recruit/editor/editorExtensions.ts
+++ b/src/components/recruit/editor/editorExtensions.ts
@@ -1,6 +1,7 @@
import StarterKit from '@tiptap/starter-kit';
import Link from '@tiptap/extension-link';
import Placeholder from '@tiptap/extension-placeholder';
+import { TableKit } from '@tiptap/extension-table';
export const getEditorExtensions = () => [
StarterKit.configure({
@@ -8,6 +9,24 @@ export const getEditorExtensions = () => [
bulletList: { HTMLAttributes: { class: 'list-disc ml-4' } },
orderedList: { HTMLAttributes: { class: 'list-decimal ml-4' } },
}),
+ TableKit.configure({
+ table: {
+ resizable: true,
+ HTMLAttributes: {
+ class: 'border-collapse w-full my-4',
+ },
+ },
+ tableCell: {
+ HTMLAttributes: {
+ class: 'border border-gray-300 px-3 py-2 text-left',
+ },
+ },
+ tableHeader: {
+ HTMLAttributes: {
+ class: 'border border-gray-300 px-3 py-2 bg-gray-100 font-semibold text-left',
+ },
+ },
+ }),
Link.configure({
autolink: true,
linkOnPaste: true,
@@ -20,7 +39,13 @@ export const getEditorExtensions = () => [
validate: (href) => /^(https?:\/\/|mailto:|tel:|www\.)/i.test(href),
}),
Placeholder.configure({
- placeholder: ({ node }) => {
+ placeholder: ({ node, pos, editor }) => {
+ const resolvedPos = editor.state.doc.resolve(pos);
+ const parent = resolvedPos.parent;
+ if (parent.type.name === 'tableCell' || parent.type.name === 'tableHeader') {
+ return '';
+ }
+
if (node.type.name === 'paragraph') {
return [
'*최소 50자 이상부터 작성 가능해요',
From b8683f50da5a9140429bc25ae2ae1d3ca9343a3e Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Tue, 3 Mar 2026 19:22:22 +0900
Subject: [PATCH 03/15] =?UTF-8?q?#154=20[FEAT]=20=ED=91=9C=20=EA=B4=80?=
=?UTF-8?q?=EB=A0=A8=20=EB=A9=94=EB=89=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../recruit/editor/table/TableBubbleMenu.tsx | 249 ++++++++++++++++++
.../recruit/editor/table/TableHandleMenu.tsx | 110 ++++++++
2 files changed, 359 insertions(+)
create mode 100644 src/components/recruit/editor/table/TableBubbleMenu.tsx
create mode 100644 src/components/recruit/editor/table/TableHandleMenu.tsx
diff --git a/src/components/recruit/editor/table/TableBubbleMenu.tsx b/src/components/recruit/editor/table/TableBubbleMenu.tsx
new file mode 100644
index 0000000..5d681a5
--- /dev/null
+++ b/src/components/recruit/editor/table/TableBubbleMenu.tsx
@@ -0,0 +1,249 @@
+'use client';
+
+import React from 'react';
+import { Editor } from '@tiptap/react';
+import { BubbleMenu } from '@tiptap/react/menus';
+import { CellSelection } from '@tiptap/pm/tables';
+
+interface Props {
+ editor: Editor;
+}
+
+const Btn = ({
+ onClick,
+ disabled = false,
+ danger = false,
+ active = false,
+ children,
+}: {
+ onClick: () => void;
+ disabled?: boolean;
+ danger?: boolean;
+ active?: boolean;
+ children: React.ReactNode;
+}) => (
+
+);
+
+const Sep = () => ;
+
+// 선택된 셀들이 속한 행이 모두 헤더인지 확인
+const isSelectionInHeaderRow = (editor: Editor): boolean => {
+ const { selection } = editor.state;
+ if (!(selection instanceof CellSelection)) return false;
+
+ const rows = new Set();
+ selection.forEachCell((_, pos) => {
+ const $pos = editor.state.doc.resolve(pos);
+ // tableRow의 pos
+ rows.add($pos.before($pos.depth - 1));
+ });
+
+ let isHeader = true;
+ selection.forEachCell((node) => {
+ if (node.type.name !== 'tableHeader') isHeader = false;
+ });
+ return isHeader;
+};
+
+export const TableBubbleMenu = ({ editor }: Props) => {
+ const canMerge = editor.can().mergeCells();
+ const canSplit = editor.can().splitCell();
+ const isHeader = isSelectionInHeaderRow(editor);
+
+ const handleToggleHeader = () => {
+ editor.chain().focus().toggleHeaderRow().run();
+ };
+
+ return (
+ {
+ const { selection } = editor.state;
+ return selection instanceof CellSelection;
+ }}
+ >
+
+ {/* 헤더 토글 */}
+
+ {isHeader ? '헤더 해제' : '헤더 설정'}
+
+
+
+ {/* 셀 병합 / 분리 */}
+ editor.chain().focus().mergeCells().run()} disabled={!canMerge}>
+ 병합
+
+ editor.chain().focus().splitCell().run()} disabled={!canSplit}>
+ 분리
+
+
+
+ {/* 행 조작 */}
+ editor.chain().focus().addRowBefore().run()}>
+ 위에 행
+
+ editor.chain().focus().addRowAfter().run()}>
+ 아래 행
+
+ editor.chain().focus().deleteRow().run()} danger>
+ 행 삭제
+
+
+
+ {/* 열 조작 */}
+ editor.chain().focus().addColumnBefore().run()}>
+ 왼쪽 열
+
+ editor.chain().focus().addColumnAfter().run()}>
+ 오른쪽 열
+
+ editor.chain().focus().deleteColumn().run()} danger>
+ 열 삭제
+
+
+
+ {/* 표 삭제 */}
+ editor.chain().focus().deleteTable().run()} danger>
+ 표 삭제
+
+
+
+ );
+};
+
+// 아이콘
+
+const Ic = ({ d, extra }: { d?: string; extra?: React.ReactNode }) => (
+
+);
+
+const IcHeader = () => (
+
+
+
+
+ >
+ }
+ />
+);
+const IcMerge = () => (
+
+
+
+
+
+
+ >
+ }
+ />
+);
+const IcSplit = () => (
+
+
+
+
+ >
+ }
+ />
+);
+const IcRowBefore = () => (
+
+
+
+ >
+ }
+ />
+);
+const IcRowAfter = () => (
+
+
+
+ >
+ }
+ />
+);
+const IcColBefore = () => (
+
+
+
+ >
+ }
+ />
+);
+const IcColAfter = () => (
+
+
+
+ >
+ }
+ />
+);
+const IcDel = () => ;
+const IcTable = () => (
+
+
+
+
+
+ >
+ }
+ />
+);
diff --git a/src/components/recruit/editor/table/TableHandleMenu.tsx b/src/components/recruit/editor/table/TableHandleMenu.tsx
new file mode 100644
index 0000000..6fa5490
--- /dev/null
+++ b/src/components/recruit/editor/table/TableHandleMenu.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+import React, { useEffect, useRef } from 'react';
+import { Editor } from '@tiptap/react';
+
+export type HandleType = 'row' | 'col';
+
+export interface HandleMenuState {
+ type: HandleType;
+ index: number;
+ x: number;
+ y: number;
+}
+
+interface Props {
+ editor: Editor;
+ menu: HandleMenuState | null;
+ onClose: () => void;
+}
+
+const Item = ({
+ onClick,
+ danger = false,
+ children,
+}: {
+ onClick: () => void;
+ danger?: boolean;
+ children: React.ReactNode;
+}) => (
+
+);
+
+export const TableHandleMenu = ({ editor, menu, onClose }: Props) => {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (!menu) return;
+ const handler = (e: MouseEvent) => {
+ if (ref.current && !ref.current.contains(e.target as Node)) onClose();
+ };
+ document.addEventListener('mousedown', handler);
+ return () => document.removeEventListener('mousedown', handler);
+ }, [menu, onClose]);
+
+ if (!menu) return null;
+
+ const { type, index, x, y } = menu;
+ const isRow = type === 'row';
+ const isHeader = isRow && index === 0;
+
+ const run = (fn: () => void) => {
+ fn();
+ onClose();
+ editor.view.focus();
+ };
+
+ return (
+
+ {isRow ? (
+ <>
+
- run(() => editor.chain().focus().addRowBefore().run())}>
+ 위에 행 추가
+
+
- run(() => editor.chain().focus().addRowAfter().run())}>
+ 아래에 행 추가
+
+ {!isHeader && (
+ <>
+
+
- run(() => editor.chain().focus().deleteRow().run())} danger>
+ 행 삭제
+
+ >
+ )}
+ >
+ ) : (
+ <>
+
- run(() => editor.chain().focus().addColumnBefore().run())}>
+ 왼쪽에 열 추가
+
+
- run(() => editor.chain().focus().addColumnAfter().run())}>
+ 오른쪽에 열 추가
+
+
+
- run(() => editor.chain().focus().deleteColumn().run())} danger>
+ 열 삭제
+
+ >
+ )}
+
+
- 취소
+
+ );
+};
From 3fa2e715889080a0b6c7214c6491792d6cf00c7e Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Tue, 3 Mar 2026 19:34:29 +0900
Subject: [PATCH 04/15] =?UTF-8?q?#154=20[FEAT]=20=ED=96=89/=EC=97=B4=20?=
=?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../recruit/editor/table/TableHandles.tsx | 421 ++++++++++++++++++
.../recruit/editor/table/tableUtils.ts | 71 +++
2 files changed, 492 insertions(+)
create mode 100644 src/components/recruit/editor/table/TableHandles.tsx
create mode 100644 src/components/recruit/editor/table/tableUtils.ts
diff --git a/src/components/recruit/editor/table/TableHandles.tsx b/src/components/recruit/editor/table/TableHandles.tsx
new file mode 100644
index 0000000..aceea94
--- /dev/null
+++ b/src/components/recruit/editor/table/TableHandles.tsx
@@ -0,0 +1,421 @@
+'use client';
+
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Editor } from '@tiptap/react';
+import { HandleMenuState, TableHandleMenu } from './TableHandleMenu';
+import { moveTableRow, moveTableCol, getTableInfo } from './tableUtils';
+
+interface Props {
+ editor: Editor;
+}
+
+interface HandleInfo {
+ type: 'row' | 'col';
+ index: number;
+ rect: DOMRect; // 핸들 버튼 위치용
+ isHeader: boolean;
+}
+
+// 드래그 상태
+
+interface DragState {
+ type: 'row' | 'col';
+ fromIndex: number;
+ toIndex: number; // hover 중인 대상 인덱스
+}
+
+export const TableHandles = ({ editor }: Props) => {
+ const [handles, setHandles] = useState([]);
+ const [hoveredHandle, setHoveredHandle] = useState(null); // handles 배열 idx
+ const [menu, setMenu] = useState(null);
+ const [drag, setDrag] = useState(null);
+
+ const longPressTimer = useRef | null>(null);
+ const isDragging = useRef(false);
+
+ // 테이블 DOM에서 행/열 핸들 위치 계산
+
+ const computeHandles = useCallback(() => {
+ const dom = editor.view.dom as HTMLElement;
+ const tables = dom.querySelectorAll('table');
+ if (!tables.length) {
+ setHandles([]);
+ return;
+ }
+
+ // 첫 번째 테이블만 처리
+ const table = tables[0] as HTMLTableElement;
+ const tableRect = table.getBoundingClientRect();
+ const newHandles: HandleInfo[] = [];
+
+ // 행 핸들
+ const rows = table.querySelectorAll('tr');
+ rows.forEach((tr, i) => {
+ const rect = tr.getBoundingClientRect();
+ newHandles.push({
+ type: 'row',
+ index: i,
+ rect,
+ isHeader: i === 0,
+ });
+ });
+
+ // 열 핸들
+ if (rows.length > 0) {
+ const firstRow = rows[0];
+ const cells = firstRow.querySelectorAll('th, td');
+ cells.forEach((cell, i) => {
+ const rect = cell.getBoundingClientRect();
+ newHandles.push({
+ type: 'col',
+ index: i,
+ rect,
+ isHeader: false,
+ });
+ });
+ }
+
+ setHandles(newHandles);
+ }, [editor]);
+
+ // 에디터 업데이트 / 스크롤 시 핸들 재계산
+ useEffect(() => {
+ const update = () => computeHandles();
+ editor.on('update', update);
+ editor.on('selectionUpdate', update);
+ window.addEventListener('scroll', update, true);
+ window.addEventListener('resize', update);
+ return () => {
+ editor.off('update', update);
+ editor.off('selectionUpdate', update);
+ window.removeEventListener('scroll', update, true);
+ window.removeEventListener('resize', update);
+ };
+ }, [editor, computeHandles]);
+
+ // 테이블 없으면 핸들 숨김
+ useEffect(() => {
+ if (!editor.isActive('table')) setHandles([]);
+ else computeHandles();
+ }, [editor.isActive('table'), computeHandles]);
+
+ // 드래그 중 하이라이트
+
+ useEffect(() => {
+ if (!drag) {
+ // 모든 하이라이트 제거
+ clearHighlights(editor);
+ return;
+ }
+ applyDragHighlight(editor, drag);
+ }, [drag, editor]);
+
+ // 마우스 롱프레스 → 드래그, 클릭 → 메뉴
+
+ const handleMouseDown = useCallback(
+ (e: React.MouseEvent, handle: HandleInfo) => {
+ if (e.button !== 0) return;
+ e.preventDefault();
+ e.stopPropagation();
+
+ isDragging.current = false;
+
+ // 해당 행/열에 커서 이동 (메뉴 명령 기준 위치 설정)
+ focusCell(editor, handle.type, handle.index);
+
+ // 롱프레스 타이머 (300ms)
+ longPressTimer.current = setTimeout(() => {
+ isDragging.current = true;
+ setDrag({ type: handle.type, fromIndex: handle.index, toIndex: handle.index });
+ }, 300);
+ },
+ [editor],
+ );
+
+ const handleMouseUp = useCallback(
+ (e: React.MouseEvent, handle: HandleInfo) => {
+ e.preventDefault();
+ if (longPressTimer.current) {
+ clearTimeout(longPressTimer.current);
+ longPressTimer.current = null;
+ }
+
+ if (isDragging.current && drag) {
+ // 드래그 완료 → 이동 실행
+ if (drag.fromIndex !== drag.toIndex) {
+ if (drag.type === 'row') moveTableRow(editor, drag.fromIndex, drag.toIndex);
+ else moveTableCol(editor, drag.fromIndex, drag.toIndex);
+ }
+ setDrag(null);
+ isDragging.current = false;
+ return;
+ }
+
+ // 클릭 → 메뉴 열기
+ const btnRect = e.currentTarget.getBoundingClientRect();
+ setMenu({
+ type: handle.type,
+ index: handle.index,
+ x: handle.type === 'row' ? btnRect.right + 4 : btnRect.left,
+ y: handle.type === 'row' ? btnRect.top : btnRect.bottom + 4,
+ });
+ },
+ [editor, drag],
+ );
+
+ const handleMouseMove = useCallback(
+ (e: React.MouseEvent, handle: HandleInfo) => {
+ if (!isDragging.current || !drag) return;
+ if (handle.type !== drag.type) return;
+ // 헤더 행(0)으로 이동 방지
+ if (drag.type === 'row' && handle.index === 0) return;
+ if (drag.toIndex !== handle.index) {
+ setDrag((prev) => (prev ? { ...prev, toIndex: handle.index } : null));
+ }
+ },
+ [drag],
+ );
+
+ // 전역 mouseup (드래그 중 핸들 밖에서 손 뗄 때)
+ useEffect(() => {
+ const onUp = () => {
+ if (isDragging.current && drag) {
+ if (drag.fromIndex !== drag.toIndex) {
+ if (drag.type === 'row') moveTableRow(editor, drag.fromIndex, drag.toIndex);
+ else moveTableCol(editor, drag.fromIndex, drag.toIndex);
+ }
+ }
+ if (longPressTimer.current) {
+ clearTimeout(longPressTimer.current);
+ longPressTimer.current = null;
+ }
+ isDragging.current = false;
+ setDrag(null);
+ };
+ window.addEventListener('mouseup', onUp);
+ return () => window.removeEventListener('mouseup', onUp);
+ }, [editor, drag]);
+
+ if (!handles.length) return null;
+
+ const rowHandles = handles.filter((h) => h.type === 'row');
+ const colHandles = handles.filter((h) => h.type === 'col');
+
+ return (
+ <>
+ {/* 행 핸들 */}
+ {rowHandles.map((h) => {
+ const isDragTarget =
+ drag?.type === 'row' && drag.toIndex === h.index && drag.fromIndex !== h.index;
+ const isDragSource = drag?.type === 'row' && drag.fromIndex === h.index;
+ return (
+ handleMouseDown(e, h)}
+ onMouseUp={(e) => handleMouseUp(e, h)}
+ onMouseMove={(e) => handleMouseMove(e, h)}
+ onMouseEnter={() => setHoveredHandle(handles.indexOf(h))}
+ onMouseLeave={() => setHoveredHandle(null)}
+ >
+
+
+
+
+ );
+ })}
+
+ {/* 열 핸들 */}
+ {colHandles.map((h) => {
+ const isDragTarget =
+ drag?.type === 'col' && drag.toIndex === h.index && drag.fromIndex !== h.index;
+ const isDragSource = drag?.type === 'col' && drag.fromIndex === h.index;
+ return (
+ handleMouseDown(e, h)}
+ onMouseUp={(e) => handleMouseUp(e, h)}
+ onMouseMove={(e) => handleMouseMove(e, h)}
+ onMouseEnter={() => setHoveredHandle(handles.indexOf(h))}
+ onMouseLeave={() => setHoveredHandle(null)}
+ >
+
+
+
+
+ );
+ })}
+
+ {/* 드래그 시 행/열 하이라이트 오버레이 */}
+ {drag && }
+
+ {/* 클릭 드롭다운 메뉴 */}
+ setMenu(null)} />
+ >
+ );
+};
+
+// 드래그 하이라이트 오버레이
+
+const DragHighlightOverlay = ({
+ editor,
+ drag,
+ handles,
+}: {
+ editor: Editor;
+ drag: DragState;
+ handles: HandleInfo[];
+}) => {
+ const targetHandle = handles.find((h) => h.type === drag.type && h.index === drag.toIndex);
+ if (!targetHandle || drag.fromIndex === drag.toIndex) return null;
+
+ const isRow = drag.type === 'row';
+
+ // 테이블 전체 rect 구하기
+ const dom = editor.view.dom as HTMLElement;
+ const table = dom.querySelector('table');
+ if (!table) return null;
+ const tableRect = table.getBoundingClientRect();
+
+ const style: React.CSSProperties = isRow
+ ? {
+ position: 'fixed',
+ left: tableRect.left,
+ top: targetHandle.rect.top,
+ width: tableRect.width,
+ height: 2,
+ zIndex: 39,
+ }
+ : {
+ position: 'fixed',
+ left: targetHandle.rect.left,
+ top: tableRect.top,
+ width: 2,
+ height: tableRect.height,
+ zIndex: 39,
+ };
+
+ return ;
+};
+
+// ProseMirror 셀에 커서 이동
+
+function focusCell(editor: Editor, type: 'row' | 'col', index: number) {
+ const { state, view } = editor;
+ const { doc } = state;
+ let targetPos: number | null = null;
+
+ doc.descendants((node, pos) => {
+ if (targetPos !== null) return false;
+ if (node.type.name === 'table') {
+ let rowIdx = 0;
+ node.forEach((rowNode, rowOffset) => {
+ if (targetPos !== null) return;
+ if (type === 'row' && rowIdx === index) {
+ // 해당 행 첫 번째 셀
+ targetPos = pos + rowOffset + 2;
+ }
+ if (type === 'col') {
+ let colIdx = 0;
+ rowNode.forEach((cellNode, cellOffset) => {
+ if (targetPos !== null) return;
+ if (rowIdx === 0 && colIdx === index) {
+ targetPos = pos + rowOffset + cellOffset + 2;
+ }
+ colIdx++;
+ });
+ }
+ rowIdx++;
+ });
+ return false;
+ }
+ });
+
+ if (targetPos !== null) {
+ const tr = state.tr.setSelection(
+ state.selection.constructor.prototype.constructor.near(state.doc.resolve(targetPos)),
+ );
+ view.dispatch(tr);
+ }
+}
+
+// 하이라이트 유틸
+
+function clearHighlights(editor: Editor) {
+ const dom = editor.view.dom as HTMLElement;
+ dom.querySelectorAll('.table-drag-highlight').forEach((el) => {
+ el.classList.remove('table-drag-highlight');
+ });
+}
+
+function applyDragHighlight(editor: Editor, drag: DragState) {
+ clearHighlights(editor);
+ const dom = editor.view.dom as HTMLElement;
+ const table = dom.querySelector('table');
+ if (!table) return;
+
+ if (drag.type === 'row') {
+ const rows = table.querySelectorAll('tr');
+ rows[drag.fromIndex]?.classList.add('table-drag-highlight');
+ } else {
+ const rows = table.querySelectorAll('tr');
+ rows.forEach((row) => {
+ const cells = row.querySelectorAll('th, td');
+ cells[drag.fromIndex]?.classList.add('table-drag-highlight');
+ });
+ }
+}
+
+// 드래그 핸들 아이콘
+const DragIcon = ({ rotate = false }: { rotate?: boolean }) => (
+
+);
diff --git a/src/components/recruit/editor/table/tableUtils.ts b/src/components/recruit/editor/table/tableUtils.ts
new file mode 100644
index 0000000..912566e
--- /dev/null
+++ b/src/components/recruit/editor/table/tableUtils.ts
@@ -0,0 +1,71 @@
+import { Editor } from '@tiptap/react';
+import { Node as PmNode } from '@tiptap/pm/model';
+
+export interface TableInfo {
+ tableStart: number;
+ tableNode: PmNode;
+ rows: number;
+ cols: number;
+}
+
+export function getTableInfo(editor: Editor): TableInfo | null {
+ const { $from } = editor.state.selection;
+ for (let depth = $from.depth; depth > 0; depth--) {
+ const node = $from.node(depth);
+ if (node.type.name === 'table') {
+ return {
+ tableStart: $from.start(depth),
+ tableNode: node,
+ rows: node.childCount,
+ cols: node.childCount > 0 ? node.child(0).childCount : 0,
+ };
+ }
+ }
+ return null;
+}
+
+/** 행 이동 (헤더 보호) */
+export function moveTableRow(editor: Editor, fromIndex: number, toIndex: number): boolean {
+ const info = getTableInfo(editor);
+ if (!info) return false;
+ const { tableStart, tableNode, rows } = info;
+ if (fromIndex === toIndex || fromIndex < 1 || toIndex < 1 || fromIndex >= rows || toIndex >= rows)
+ return false;
+
+ const rowNodes: PmNode[] = [];
+ tableNode.forEach((row) => rowNodes.push(row));
+ const reordered = [...rowNodes];
+ const [removed] = reordered.splice(fromIndex, 1);
+ reordered.splice(toIndex, 0, removed);
+
+ const newTable = tableNode.type.create(tableNode.attrs, reordered, tableNode.marks);
+ const tr = editor.state.tr;
+ tr.replaceWith(tableStart - 1, tableStart - 1 + tableNode.nodeSize, newTable);
+ editor.view.dispatch(tr);
+ return true;
+}
+
+/** 열 이동 */
+export function moveTableCol(editor: Editor, fromCol: number, toCol: number): boolean {
+ const info = getTableInfo(editor);
+ if (!info) return false;
+ const { tableStart, tableNode, cols } = info;
+ if (fromCol === toCol || fromCol < 0 || toCol < 0 || fromCol >= cols || toCol >= cols)
+ return false;
+
+ const newRows: PmNode[] = [];
+ tableNode.forEach((rowNode) => {
+ const cells: PmNode[] = [];
+ rowNode.forEach((cell) => cells.push(cell));
+ const reordered = [...cells];
+ const [removed] = reordered.splice(fromCol, 1);
+ reordered.splice(toCol, 0, removed);
+ newRows.push(rowNode.type.create(rowNode.attrs, reordered, rowNode.marks));
+ });
+
+ const newTable = tableNode.type.create(tableNode.attrs, newRows, tableNode.marks);
+ const tr = editor.state.tr;
+ tr.replaceWith(tableStart - 1, tableStart - 1 + tableNode.nodeSize, newTable);
+ editor.view.dispatch(tr);
+ return true;
+}
From 60754a8d4e2a85b2012fb6eec58c5fa5d9702a0c Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Tue, 3 Mar 2026 19:34:49 +0900
Subject: [PATCH 05/15] =?UTF-8?q?#154=20[FEAT]=20=ED=91=9C=20=EB=A9=94?=
=?UTF-8?q?=EB=89=B4=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/globals.css | 49 +++++++++++++++++++
src/components/recruit/editor/TextContent.tsx | 6 ++-
2 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/src/app/globals.css b/src/app/globals.css
index aef22c4..b06f49e 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -262,6 +262,55 @@ body {
word-break: break-all;
}
+.table-drag-highlight {
+ opacity: 0.4;
+ background-color: rgb(var(--color-primary-100) / 0.3);
+}
+
+.selectedCell {
+ background-color: rgb(0 0 0 / 0.05);
+}
+
+/* 테이블 컬럼 리사이즈 핸들 */
+.column-resize-handle {
+ position: absolute;
+ right: -2px;
+ top: 0;
+ bottom: 0;
+ width: 4px;
+ background-color: transparent;
+ cursor: col-resize;
+ z-index: 20;
+ transition: background-color 0.15s;
+}
+
+.column-resize-handle:hover,
+.resize-cursor .column-resize-handle {
+ background-color: rgb(0 0 0 / 0.2);
+}
+
+/* 리사이즈 중 커서 전체 적용 */
+.resize-cursor {
+ cursor: col-resize;
+}
+
+.ProseMirror table {
+ table-layout: fixed;
+ width: 100%;
+ min-width: 100%; /* 추가 */
+}
+
+.ProseMirror table colgroup col {
+ min-width: 40px; /* 열이 너무 좁아지는 것 방지 */
+}
+
+.ProseMirror table td,
+.ProseMirror table th {
+ overflow: hidden;
+ word-break: break-word;
+ /* width: auto 제거 — col이 제어하도록 */
+}
+
/* slide-up 애니메이션 */
@keyframes slide-up {
from {
diff --git a/src/components/recruit/editor/TextContent.tsx b/src/components/recruit/editor/TextContent.tsx
index 49b6509..467a081 100644
--- a/src/components/recruit/editor/TextContent.tsx
+++ b/src/components/recruit/editor/TextContent.tsx
@@ -7,6 +7,8 @@ import Toolbar from './Toolbar';
import { handleLink } from './linkUtils';
import { Control, useController } from 'react-hook-form';
import { RecruitFormType } from '@/libs/schemas/projectSchema';
+import { TableBubbleMenu } from './table/TableBubbleMenu';
+import { TableHandles } from './table/TableHandles';
type Props = {
control: Control;
@@ -62,11 +64,9 @@ const TextContent = ({ control, name = 'content' }: Props) => {
if (!editor) return;
const update = () => forceUpdate((v) => v + 1);
editor.on('selectionUpdate', update);
- editor.on('transaction', update);
editor.on('update', update);
return () => {
editor.off('selectionUpdate', update);
- editor.off('transaction', update);
editor.off('update', update);
};
}, [editor]);
@@ -86,6 +86,8 @@ const TextContent = ({ control, name = 'content' }: Props) => {
{textLength}
+
+
);
};
From 35f6149d503e894a44bca5875bb4673b23fa0113 Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Wed, 4 Mar 2026 01:06:06 +0900
Subject: [PATCH 06/15] =?UTF-8?q?#154=20[FEAT]=20=EB=AA=A8=EB=B0=94?=
=?UTF-8?q?=EC=9D=BC=20=ED=91=9C=20=ED=8E=B8=EC=A7=91=20=EB=A9=94=EB=89=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/globals.css | 28 +++++++++++++++++++
src/components/recruit/editor/TextContent.tsx | 4 ++-
.../recruit/editor/editorExtensions.ts | 2 +-
.../recruit/editor/table/TableBubbleMenu.tsx | 3 +-
.../recruit/editor/table/TableHandleMenu.tsx | 18 ++++++++++--
5 files changed, 49 insertions(+), 6 deletions(-)
diff --git a/src/app/globals.css b/src/app/globals.css
index b06f49e..d9a67a9 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -311,6 +311,34 @@ body {
/* width: auto 제거 — col이 제어하도록 */
}
+.table-mobile-handle {
+ display: none;
+}
+@media (max-width: 640px) {
+ .table-mobile-handle {
+ display: flex;
+ }
+}
+
+/* 모바일 테이블 가로 스크롤 */
+@media (max-width: 640px) {
+ .ProseMirror table {
+ width: 100%;
+ table-layout: auto;
+ }
+
+ .ProseMirror td,
+ .ProseMirror th {
+ min-width: 120px;
+ }
+}
+
+@media (max-width: 640px) {
+ .column-resize-handle {
+ display: none;
+ }
+}
+
/* slide-up 애니메이션 */
@keyframes slide-up {
from {
diff --git a/src/components/recruit/editor/TextContent.tsx b/src/components/recruit/editor/TextContent.tsx
index 467a081..027cf9b 100644
--- a/src/components/recruit/editor/TextContent.tsx
+++ b/src/components/recruit/editor/TextContent.tsx
@@ -81,7 +81,9 @@ const TextContent = ({ control, name = 'content' }: Props) => {
className={`desktop:px-8 desktop:pb-9 flex flex-col rounded-2xl border-1 border-gray-300 px-6 pt-4 pb-7`}
>
handleLink(editor)} />
-
+
+
+
{textLength}
diff --git a/src/components/recruit/editor/editorExtensions.ts b/src/components/recruit/editor/editorExtensions.ts
index 3fd3ccf..f4b78dd 100644
--- a/src/components/recruit/editor/editorExtensions.ts
+++ b/src/components/recruit/editor/editorExtensions.ts
@@ -11,7 +11,7 @@ export const getEditorExtensions = () => [
}),
TableKit.configure({
table: {
- resizable: true,
+ resizable: false,
HTMLAttributes: {
class: 'border-collapse w-full my-4',
},
diff --git a/src/components/recruit/editor/table/TableBubbleMenu.tsx b/src/components/recruit/editor/table/TableBubbleMenu.tsx
index 5d681a5..feecc79 100644
--- a/src/components/recruit/editor/table/TableBubbleMenu.tsx
+++ b/src/components/recruit/editor/table/TableBubbleMenu.tsx
@@ -77,6 +77,7 @@ export const TableBubbleMenu = ({ editor }: Props) => {
{
+ if (typeof window !== 'undefined' && window.innerWidth < 640) return false;
const { selection } = editor.state;
return selection instanceof CellSelection;
}}
@@ -101,7 +102,7 @@ export const TableBubbleMenu = ({ editor }: Props) => {
{/* 행 조작 */}
- editor.chain().focus().addRowBefore().run()}>
+ editor.chain().focus().addRowBefore().run()} disabled={isHeader}>
위에 행
editor.chain().focus().addRowAfter().run()}>
diff --git a/src/components/recruit/editor/table/TableHandleMenu.tsx b/src/components/recruit/editor/table/TableHandleMenu.tsx
index 6fa5490..d7ac9f0 100644
--- a/src/components/recruit/editor/table/TableHandleMenu.tsx
+++ b/src/components/recruit/editor/table/TableHandleMenu.tsx
@@ -74,9 +74,13 @@ export const TableHandleMenu = ({ editor, menu, onClose }: Props) => {
>
{isRow ? (
<>
- - run(() => editor.chain().focus().addRowBefore().run())}>
- 위에 행 추가
-
+ {!isHeader && (
+ <>
+ - run(() => editor.chain().focus().addRowBefore().run())}>
+ 위에 행 추가
+
+ >
+ )}
- run(() => editor.chain().focus().addRowAfter().run())}>
아래에 행 추가
@@ -88,6 +92,10 @@ export const TableHandleMenu = ({ editor, menu, onClose }: Props) => {
>
)}
+
+ - run(() => editor.chain().focus().deleteTable().run())} danger>
+ 표 삭제
+
>
) : (
<>
@@ -101,6 +109,10 @@ export const TableHandleMenu = ({ editor, menu, onClose }: Props) => {
- run(() => editor.chain().focus().deleteColumn().run())} danger>
열 삭제
+
+ - run(() => editor.chain().focus().deleteTable().run())} danger>
+ 표 삭제
+
>
)}
From 075984aa0a2cac563b537307c937e537ee571399 Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Wed, 4 Mar 2026 01:11:52 +0900
Subject: [PATCH 07/15] =?UTF-8?q?#154=20[FEAT]=20=EB=AA=A8=EB=B0=94?=
=?UTF-8?q?=EC=9D=BC=20=ED=96=89=EB=A0=AC=20=ED=95=B8=EB=93=A4=20=EC=84=A4?=
=?UTF-8?q?=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../recruit/editor/table/TableHandles.tsx | 78 ++++++++++++++++++-
1 file changed, 75 insertions(+), 3 deletions(-)
diff --git a/src/components/recruit/editor/table/TableHandles.tsx b/src/components/recruit/editor/table/TableHandles.tsx
index aceea94..576b5d3 100644
--- a/src/components/recruit/editor/table/TableHandles.tsx
+++ b/src/components/recruit/editor/table/TableHandles.tsx
@@ -30,6 +30,12 @@ export const TableHandles = ({ editor }: Props) => {
const [menu, setMenu] = useState(null);
const [drag, setDrag] = useState(null);
+ const [mobileCell, setMobileCell] = useState<{
+ rect: DOMRect;
+ rowIndex: number;
+ colIndex: number;
+ } | null>(null);
+
const longPressTimer = useRef | null>(null);
const isDragging = useRef(false);
@@ -80,7 +86,38 @@ export const TableHandles = ({ editor }: Props) => {
// 에디터 업데이트 / 스크롤 시 핸들 재계산
useEffect(() => {
- const update = () => computeHandles();
+ const update = () => {
+ computeHandles();
+
+ // 모바일 셀 위치 계산
+ const { $from } = editor.state.selection;
+ for (let depth = $from.depth; depth > 0; depth--) {
+ const node = $from.node(depth);
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
+ const domNode = editor.view.nodeDOM($from.before(depth));
+ if (!(domNode instanceof HTMLElement)) {
+ setMobileCell(null);
+ return;
+ }
+
+ const tableNode = $from.node(depth - 2);
+ const rowNode = $from.node(depth - 1);
+ let rowIndex = 0;
+ let colIndex = 0;
+ tableNode.forEach((row, _, i) => {
+ if (row === rowNode) rowIndex = i;
+ });
+ rowNode.forEach((cell, _, i) => {
+ if (cell === node) colIndex = i;
+ });
+
+ setMobileCell({ rect: domNode.getBoundingClientRect(), rowIndex, colIndex });
+ return;
+ }
+ }
+ setMobileCell(null);
+ };
+
editor.on('update', update);
editor.on('selectionUpdate', update);
window.addEventListener('scroll', update, true);
@@ -231,10 +268,28 @@ export const TableHandles = ({ editor }: Props) => {
'flex h-5 w-4 cursor-grab items-center justify-center rounded transition-all',
isDragTarget ? 'bg-primary-100 opacity-100' : '',
isDragSource ? 'opacity-40' : '',
+ // PC: hover 시만 표시 / 모바일: mobileCell의 행일 때만 표시
!isDragTarget && !isDragSource
- ? 'opacity-0 group-hover:opacity-100 hover:bg-gray-100'
+ ? mobileCell?.rowIndex === h.index
+ ? 'tablet:opacity-0 tablet:group-hover:opacity-100 opacity-100 hover:bg-gray-100'
+ : 'opacity-0 group-hover:opacity-100 hover:bg-gray-100'
: '',
].join(' ')}
+ onPointerDown={
+ mobileCell?.rowIndex === h.index
+ ? (e) => {
+ if (e.pointerType === 'touch') {
+ e.preventDefault();
+ setMenu({
+ type: 'row',
+ index: h.index,
+ x: h.rect.right + 4,
+ y: h.rect.top,
+ });
+ }
+ }
+ : undefined
+ }
>
@@ -271,9 +326,26 @@ export const TableHandles = ({ editor }: Props) => {
isDragTarget ? 'bg-primary-100 opacity-100' : '',
isDragSource ? 'opacity-40' : '',
!isDragTarget && !isDragSource
- ? 'opacity-0 group-hover:opacity-100 hover:bg-gray-100'
+ ? mobileCell?.colIndex === h.index
+ ? 'tablet:opacity-0 tablet:group-hover:opacity-100 opacity-100 hover:bg-gray-100'
+ : 'opacity-0 group-hover:opacity-100 hover:bg-gray-100'
: '',
].join(' ')}
+ onPointerDown={
+ mobileCell?.colIndex === h.index
+ ? (e) => {
+ if (e.pointerType === 'touch') {
+ e.preventDefault();
+ setMenu({
+ type: 'col',
+ index: h.index,
+ x: h.rect.left,
+ y: h.rect.bottom + 4,
+ });
+ }
+ }
+ : undefined
+ }
>
From 90ca051ab99832b916f40327c1fd482c0700315e Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Wed, 4 Mar 2026 01:14:44 +0900
Subject: [PATCH 08/15] =?UTF-8?q?#154=20[FEAT]=20=ED=88=B4=EB=B0=94=20?=
=?UTF-8?q?=ED=91=9C=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
public/icons/editor/table-selected.svg | 5 +++++
public/icons/editor/table.svg | 5 +++++
src/components/recruit/editor/Toolbar.tsx | 12 +++++++++++-
3 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 public/icons/editor/table-selected.svg
create mode 100644 public/icons/editor/table.svg
diff --git a/public/icons/editor/table-selected.svg b/public/icons/editor/table-selected.svg
new file mode 100644
index 0000000..aa3d092
--- /dev/null
+++ b/public/icons/editor/table-selected.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icons/editor/table.svg b/public/icons/editor/table.svg
new file mode 100644
index 0000000..0a545e2
--- /dev/null
+++ b/public/icons/editor/table.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/components/recruit/editor/Toolbar.tsx b/src/components/recruit/editor/Toolbar.tsx
index d67fb8d..ec11aba 100644
--- a/src/components/recruit/editor/Toolbar.tsx
+++ b/src/components/recruit/editor/Toolbar.tsx
@@ -196,7 +196,17 @@ const Toolbar = ({ editor, onLinkButtonClick }: ToolbarProps) => {
title="표 삽입"
className="cursor-pointer transition-opacity hover:opacity-80"
>
- {editor.isActive('table') ? 'o' : 'x'}
+
);
From 50ab9bf78589808bb3e2e52fd790a16eb56bddce Mon Sep 17 00:00:00 2001
From: sunhwaaRj
Date: Wed, 4 Mar 2026 01:26:10 +0900
Subject: [PATCH 09/15] =?UTF-8?q?#154=20[CHORE]=20=EC=A4=91=EB=B3=B5=20?=
=?UTF-8?q?=ED=91=9C=20=EC=83=9D=EC=84=B1=20=EB=A7=89=EA=B8=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/recruit/editor/Toolbar.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/components/recruit/editor/Toolbar.tsx b/src/components/recruit/editor/Toolbar.tsx
index ec11aba..2f35f89 100644
--- a/src/components/recruit/editor/Toolbar.tsx
+++ b/src/components/recruit/editor/Toolbar.tsx
@@ -176,6 +176,9 @@ const Toolbar = ({ editor, onLinkButtonClick }: ToolbarProps) => {