diff --git a/package.json b/package.json index d03f80e9..baa1a624 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 df991276..4259d03c 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 diff --git a/public/icons/editor/table-selected.svg b/public/icons/editor/table-selected.svg new file mode 100644 index 00000000..aa3d0925 --- /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 00000000..0a545e2a --- /dev/null +++ b/public/icons/editor/table.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/globals.css b/src/app/globals.css index aef22c49..8d5723f9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -262,6 +262,90 @@ 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%; +} + +.ProseMirror table td, +.ProseMirror table th { + overflow: hidden; + overflow-wrap: anywhere; + min-width: 120px; +} + +.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; + } + + .column-resize-handle { + display: none; + } +} + +/* 모바일 테이블 가로 스크롤 */ +@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/PositionDropdown.tsx b/src/components/recruit/PositionDropdown.tsx index 8fc10593..8110ab01 100644 --- a/src/components/recruit/PositionDropdown.tsx +++ b/src/components/recruit/PositionDropdown.tsx @@ -55,7 +55,7 @@ export default function PositionDropdown({ {isPlaceholder ? placeholder : selected?.label} {selectedLabel} )} {open; @@ -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]); @@ -81,11 +81,15 @@ 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/Toolbar.tsx b/src/components/recruit/editor/Toolbar.tsx index 20c0aa3f..3aa102a7 100644 --- a/src/components/recruit/editor/Toolbar.tsx +++ b/src/components/recruit/editor/Toolbar.tsx @@ -173,6 +173,50 @@ 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 5320f621..3fd3ccf0 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자 이상부터 작성 가능해요', diff --git a/src/components/recruit/editor/table/TableBubbleMenu.tsx b/src/components/recruit/editor/table/TableBubbleMenu.tsx new file mode 100644 index 00000000..feecc790 --- /dev/null +++ b/src/components/recruit/editor/table/TableBubbleMenu.tsx @@ -0,0 +1,250 @@ +'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 ( + { + if (typeof window !== 'undefined' && window.innerWidth < 640) return false; + 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()} disabled={isHeader}> + 위에 행 + + 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 }) => ( + + {d && } + {extra} + +); + +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 00000000..d7ac9f03 --- /dev/null +++ b/src/components/recruit/editor/table/TableHandleMenu.tsx @@ -0,0 +1,122 @@ +'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 ? ( + <> + {!isHeader && ( + <> + run(() => editor.chain().focus().addRowBefore().run())}> + 위에 행 추가 + + + )} + run(() => editor.chain().focus().addRowAfter().run())}> + 아래에 행 추가 + + {!isHeader && ( + <> +
+ run(() => editor.chain().focus().deleteRow().run())} danger> + 행 삭제 + + + )} +
+ run(() => editor.chain().focus().deleteTable().run())} danger> + 표 삭제 + + + ) : ( + <> + run(() => editor.chain().focus().addColumnBefore().run())}> + 왼쪽에 열 추가 + + run(() => editor.chain().focus().addColumnAfter().run())}> + 오른쪽에 열 추가 + +
+ run(() => editor.chain().focus().deleteColumn().run())} danger> + 열 삭제 + +
+ run(() => editor.chain().focus().deleteTable().run())} danger> + 표 삭제 + + + )} +
+ 취소 +
+ ); +}; diff --git a/src/components/recruit/editor/table/TableHandles.tsx b/src/components/recruit/editor/table/TableHandles.tsx new file mode 100644 index 00000000..4182b805 --- /dev/null +++ b/src/components/recruit/editor/table/TableHandles.tsx @@ -0,0 +1,524 @@ +'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; + tableEl: HTMLTableElement; // 추가 +} + +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 [mobileCell, setMobileCell] = useState<{ + rect: DOMRect; + rowIndex: number; + colIndex: number; + } | null>(null); + + const longPressTimer = useRef | null>(null); + const isDragging = useRef(false); + + useEffect(() => { + return () => { + if (longPressTimer.current) { + clearTimeout(longPressTimer.current); + longPressTimer.current = null; + } + }; + }, []); + + // 선택 위치를 기반으로 현재 활성화된 표 구하기 + const getActiveTable = useCallback((): HTMLTableElement | null => { + const { from } = editor.state.selection; + const domAtPos = editor.view.domAtPos(from).node; + const anchor = domAtPos instanceof Element ? domAtPos : domAtPos.parentElement; + return (anchor?.closest('table') as HTMLTableElement) ?? null; + }, [editor]); + + // 테이블 DOM에서 행/열 핸들 위치 계산 + + const computeHandles = useCallback(() => { + const table = getActiveTable(); + if (!table) { + setHandles([]); + return; + } + 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, getActiveTable]); + + // 에디터 업데이트 / 스크롤 시 핸들 재계산 + useEffect(() => { + 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); + 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); + + longPressTimer.current = setTimeout(() => { + isDragging.current = true; + const table = getActiveTable(); // 드래그 시작 시점 테이블 고정 + if (!table) return; + setDrag({ + type: handle.type, + fromIndex: handle.index, + toIndex: handle.index, + tableEl: table, + }); + }, 300); + }, + [editor, getActiveTable], + ); + + 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)} + > +
{ + if (e.pointerType === 'touch') { + e.preventDefault(); + setMenu({ + type: 'row', + index: h.index, + x: h.rect.right + 4, + y: h.rect.top, + }); + } + } + : undefined + } + > + +
+
+ ); + })} + + {/* 열 핸들 */} + {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)} + > +
{ + if (e.pointerType === 'touch') { + e.preventDefault(); + setMenu({ + type: 'col', + index: h.index, + x: h.rect.left, + y: h.rect.bottom + 4, + }); + } + } + : undefined + } + > + +
+
+ ); + })} + + {/* 드래그 시 행/열 하이라이트 오버레이 */} + {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'; + + // 선택 위치를 기반으로 현재 활성화된 표 구하기 + const getActiveTableElement = (): HTMLTableElement | null => { + const { from } = editor.state.selection; + const domAtPos = editor.view.domAtPos(from).node; + const anchor = domAtPos instanceof Element ? domAtPos : domAtPos.parentElement; + return (anchor?.closest('table') as HTMLTableElement) ?? null; + }; + + const table = drag.tableEl; + 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 { $from } = state.selection; + let tablePos: number | null = null; + let tableNode = null; + + for (let depth = $from.depth; depth > 0; depth--) { + if ($from.node(depth).type.name === 'table') { + tablePos = $from.before(depth); + tableNode = $from.node(depth); + break; + } + } + + if (tablePos === null || !tableNode) return; + + let targetPos: number | null = null; + let rowIdx = 0; + + tableNode.forEach((rowNode, rowOffset) => { + if (targetPos !== null) return; + if (type === 'row' && rowIdx === index) { + targetPos = tablePos! + rowOffset + 2; + } + if (type === 'col') { + let colIdx = 0; + rowNode.forEach((_, cellOffset) => { + if (targetPos !== null) return; + if (rowIdx === 0 && colIdx === index) { + targetPos = tablePos! + rowOffset + cellOffset + 2; + } + colIdx++; + }); + } + rowIdx++; + }); + + if (targetPos !== null) { + try { + const resolvedPos = state.doc.resolve(targetPos); + const selection = (state.selection.constructor as any).near(resolvedPos); + view.dispatch(state.tr.setSelection(selection)); + } catch { + // silent + } + } +} + +// 하이라이트 유틸 + +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 table = drag.tableEl; // 고정된 테이블 사용 + if (!table) return; + + if (drag.type === 'row') { + table.querySelectorAll('tr')[drag.fromIndex]?.classList.add('table-drag-highlight'); + } else { + table.querySelectorAll('tr').forEach((row) => { + row.querySelectorAll('th, td')[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 00000000..2a275569 --- /dev/null +++ b/src/components/recruit/editor/table/tableUtils.ts @@ -0,0 +1,75 @@ +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; +}