diff --git a/web/package.json b/web/package.json index b982bc516..b37a87348 100644 --- a/web/package.json +++ b/web/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-progress": "^1.0.3", @@ -42,14 +43,20 @@ "@tanstack/react-query-devtools": "^5.28.6", "@tanstack/react-router": "^1.22.0", "@tanstack/react-table": "^8.9.3", - "@tiptap/extension-bold": "^2.5.8", - "@tiptap/extension-character-count": "^2.5.8", - "@tiptap/extension-document": "^2.5.8", - "@tiptap/extension-link": "^2.5.8", - "@tiptap/extension-paragraph": "^2.5.8", - "@tiptap/extension-text": "^2.5.8", - "@tiptap/react": "^2.5.8", - "@tiptap/starter-kit": "^2.5.8", + "@tiptap/core": "^2.8.0", + "@tiptap/extension-bold": "^2.8.0", + "@tiptap/extension-character-count": "^2.8.0", + "@tiptap/extension-document": "^2.8.0", + "@tiptap/extension-heading": "^2.8.0", + "@tiptap/extension-link": "^2.8.0", + "@tiptap/extension-paragraph": "^2.8.0", + "@tiptap/extension-placeholder": "^2.8.0", + "@tiptap/extension-text": "^2.8.0", + "@tiptap/extension-text-style": "^2.8.0", + "@tiptap/extension-typography": "^2.8.0", + "@tiptap/pm": "^2.8.0", + "@tiptap/react": "^2.8.0", + "@tiptap/starter-kit": "^2.8.0", "@types/lodash": "^4.17.7", "@uidotdev/usehooks": "^2.4.1", "axios": "^1.6.2", @@ -91,6 +98,7 @@ "@heroicons/react": "^2.0.18", "@hookform/devtools": "^4.3.1", "@playwright/test": "^1.36.2", + "@tailwindcss/typography": "^0.5.15", "@tanstack/eslint-plugin-query": "^5.28.6", "@tanstack/react-table-devtools": "^8.7.6", "@tanstack/router-devtools": "^1.22.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 693fee1af..63883c035 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 version: 2.0.6(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-icons': + specifier: ^1.3.0 + version: 1.3.0(react@18.2.0) '@radix-ui/react-label': specifier: ^2.0.2 version: 2.0.2(@types/react-dom@18.2.7)(@types/react@18.2.17)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -83,30 +86,48 @@ importers: '@tanstack/react-table': specifier: ^8.9.3 version: 8.9.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@tiptap/core': + specifier: ^2.8.0 + version: 2.8.0(@tiptap/pm@2.8.0) '@tiptap/extension-bold': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) '@tiptap/extension-character-count': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) '@tiptap/extension-document': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-heading': + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) '@tiptap/extension-link': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) '@tiptap/extension-paragraph': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-placeholder': + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) '@tiptap/extension-text': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-text-style': + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-typography': + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/pm': + specifier: ^2.8.0 + version: 2.8.0 '@tiptap/react': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tiptap/starter-kit': - specifier: ^2.5.8 - version: 2.5.8(@tiptap/pm@2.5.8) + specifier: ^2.8.0 + version: 2.8.0(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))) '@types/lodash': specifier: ^4.17.7 version: 4.17.7 @@ -225,6 +246,9 @@ importers: '@playwright/test': specifier: ^1.36.2 version: 1.36.2 + '@tailwindcss/typography': + specifier: ^0.5.15 + version: 0.5.15(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.4.8(@swc/helpers@0.5.10))(@types/node@20.5.1)(typescript@5.4.3))) '@tanstack/eslint-plugin-query': specifier: ^5.28.6 version: 5.28.6(eslint@8.45.0)(typescript@5.1.6) @@ -1073,6 +1097,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.0': + resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x + '@radix-ui/react-id@1.0.1': resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -1523,8 +1552,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - '@remirror/core-constants@2.0.2': - resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} '@rollup/pluginutils@5.1.0': resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} @@ -1689,6 +1718,11 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + '@tailwindcss/typography@0.5.15': + resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' + '@tanstack/eslint-plugin-query@5.28.6': resolution: {integrity: sha512-kIvdN/EvbOrk4bbXOBm/Ik+uhQl5hawikkF5dLLmlvK3aZJzwRaRGpezYDM5Xw/6GCsATy+woh+Wvzj//BRvsg==} peerDependencies: @@ -1800,143 +1834,163 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tiptap/core@2.5.8': - resolution: {integrity: sha512-lkWCKyoAoMTxM137MoEsorG7tZ5MZU6O3wMRuZ0P9fcTRY5vd1NWncWuPzuGSJIpL20gwBQOsS6PaQSfR3xjlA==} + '@tiptap/core@2.8.0': + resolution: {integrity: sha512-xsqDI4BNzYRWRtBq7+/38ThhqEr7uG9Njip1x+9/wgR3vWPBFnBkYJTz6jSxS35NRE6BSnERm4/B/vrLuY1Hdw==} peerDependencies: - '@tiptap/pm': ^2.5.8 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-blockquote@2.5.8': - resolution: {integrity: sha512-P8vDiagtRrUfIewfCKrJe0ddDSjPgOTKzqoM1UXKS+MenT8C/wT4bjiwopAoWP6zMoV0TfHWXah9emllmCfXFA==} + '@tiptap/extension-blockquote@2.8.0': + resolution: {integrity: sha512-m3CKrOIvV7fY1Ak2gYf5LkKiz6AHxHpg6wxfVaJvdBqXgLyVtHo552N+A4oSHOSRbB4AG9EBQ2NeBM8cdEQ4MA==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-bold@2.5.8': - resolution: {integrity: sha512-4vEn+U7Y8B4e8izcL7QuEKYJ9thCSdo+UF1K3TOqQWuJTzTrJLPMwTZ4vYOHzvuq5uIXyPLnWzLgnRLgy5mJRg==} + '@tiptap/extension-bold@2.8.0': + resolution: {integrity: sha512-U1YkZBxDkSLNvPNiqxB5g42IeJHr27C7zDb/yGQN2xL4UBeg4O9xVhCFfe32f6tLwivSL0dar4ScElpaCJuqow==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-bubble-menu@2.5.8': - resolution: {integrity: sha512-COmd1Azudu7i281emZFIESECe7FnvWiRoBoQBVjjWSyq5PVzwJaA3PAlnU7GyNZKtVXMZ4xbrckdyNQfDeVQDA==} + '@tiptap/extension-bubble-menu@2.8.0': + resolution: {integrity: sha512-swg+myJPN60LduQvLMF4hVBqP5LOIN01INZBzBI8egz8QufqtSyRCgXl7Xcma0RT5xIXnZSG9XOqNFf2rtkjKA==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-bullet-list@2.5.8': - resolution: {integrity: sha512-Wvf0HWBI0ulssoCsCOguxJB1Ntmj9PtE8b/ieFwFvrNptP+sf25XiWgjMs7H1KQrtmpngBu/Bhh5jJRgAmAgeQ==} + '@tiptap/extension-bullet-list@2.8.0': + resolution: {integrity: sha512-H4O2X0ozbc/ce9/XF1H98sqWVUdtt7jzy7hMBunwmY8ZxI4dHtcRkeg81CZbpKTqOqRrMCLWjE3M2tgiDXrDkA==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/extension-list-item': ^2.7.0 + '@tiptap/extension-text-style': ^2.7.0 - '@tiptap/extension-character-count@2.5.8': - resolution: {integrity: sha512-uu9FNY9yUMkXEVMfBdTovEyPHOJCZWtEdTVuU+nbOIOpaggNFBG6YcVU4W1NC99USSFnbr45SbCsxP3gySmPIA==} + '@tiptap/extension-character-count@2.8.0': + resolution: {integrity: sha512-wTV5RFNqKh202qPFrvoAaueL9BJVpIHPCc1fGRrLe7ri95oMmEO+50SoFH1xOVjU0Pd8SXKwLKPiYTpbi3SRXQ==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-code-block@2.5.8': - resolution: {integrity: sha512-atMtT1Ddc4hv9+OiH/UCLfQ6Ooo45xpPaaOhqs1Ab509YyqxoyEbfNSOth/yx9DFb8VOenRWE1WV3Z3C0ial0Q==} + '@tiptap/extension-code-block@2.8.0': + resolution: {integrity: sha512-POuA5Igx+Dto0DTazoBFAQTj/M/FCdkqRVD9Uhsxhv49swPyANTJRr05vgbgtHB+NDDsZfCawVh7pI0IAD/O0w==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-code@2.5.8': - resolution: {integrity: sha512-56lb4NnaYAbIkqBTCIg4ZoITrw86Dj8C2HSi6DrU7f5q9cfvGuH+2057I5n8eEEfASu1AeDN6tSnCz3NR+yiHw==} + '@tiptap/extension-code@2.8.0': + resolution: {integrity: sha512-VSFn3sFF6qPpOGkXFhik8oYRH5iByVJpFEFd/duIEftmS0MdPzkbSItOpN3mc9xsJ5dCX80LYaResSj5hr5zkA==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-document@2.5.8': - resolution: {integrity: sha512-r3rP4ihCJAdp3VRIeqd80etHx7jttzZaKNFX8hkQShHK6eTHwrR92VL0jDE4K+NOE3bxjMsOlYizJYWV042BtA==} + '@tiptap/extension-document@2.8.0': + resolution: {integrity: sha512-mp7Isx1sVc/ifeW4uW/PexGQ9exN3NRUOebSpnLfqXeWYk4y1RS1PA/3+IHkOPVetbnapgPjFx/DswlCP3XLjA==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-dropcursor@2.5.8': - resolution: {integrity: sha512-xPmIfTYqurFF8RukCPlHd8mT8I7hDinWrgq7CQTRROxcJ3DNw8PooWrKWaBYs9HXHe1pbiQ5EK0uOsNvQ1bcDg==} + '@tiptap/extension-dropcursor@2.8.0': + resolution: {integrity: sha512-rAFvx44YuT6dtS1c+ALw0ROAGI16l5L1HxquL4hR1gtxDcTieST5xhw5bkshXlmrlfotZXPrhokzqA7qjhZtJw==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-floating-menu@2.5.8': - resolution: {integrity: sha512-qsM6tCyRlXnI/gADrkO/2p0Tldu5aY96CnsXpZMaflMgsO577qhcXD0ReGg17uLXBzJa5xmV8qOik0Ptq3WEWg==} + '@tiptap/extension-floating-menu@2.8.0': + resolution: {integrity: sha512-H4QT61CrkLqisnGGC7zgiYmsl2jXPHl89yQCbdlkQN7aw11H7PltcJS2PJguL0OrRVJS/Mv/VTTUiMslmsEV5g==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-gapcursor@2.5.8': - resolution: {integrity: sha512-nR7AUOE4xWdp0sDbLbe4uwAhQ/xq+MTLVafvffMLT81U/Hl9R+w0Ap2XF0+c6/JTQwVjZiOalAmg4dobx7rJUQ==} + '@tiptap/extension-gapcursor@2.8.0': + resolution: {integrity: sha512-Be1LWCmvteQInOnNVN+HTqc1XWsj1bCl+Q7et8qqNjtGtTaCbdCp8ppcH1SKJxNTM/RLUtPyJ8FDgOTj51ixCA==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-hard-break@2.5.8': - resolution: {integrity: sha512-samZEL0EXzHSmMQ7KyLnfSxdDv3qSjia0JzelfCnFZS6LLcbwjrIjV8ZPxEhJ7UlZqroQdFxPegllkLHZj/MdQ==} + '@tiptap/extension-hard-break@2.8.0': + resolution: {integrity: sha512-vqiIfviNiCmy/pJTHuDSCAGL2O4QDEdDmAvGJu8oRmElUrnlg8DbJUfKvn6DWQHNSQwRb+LDrwWlzAYj1K9u6A==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-heading@2.5.8': - resolution: {integrity: sha512-fDQoUkTLN+U8MNQ8PI+syKyshS9qFHlKihxzMLf/+tRisJvP47gzHDur99nffTSbXFDnASDqhavhKjI/2xTWlQ==} + '@tiptap/extension-heading@2.8.0': + resolution: {integrity: sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-history@2.5.8': - resolution: {integrity: sha512-5IrZZfp2Rg9Tov/08aYTKhwoiqdun8v3j3vleuqyW5RB7LU/NKLR19EtSSMh9mVkFZVbhab2zDOFmn5ilsEOhw==} + '@tiptap/extension-history@2.8.0': + resolution: {integrity: sha512-u5YS0J5Egsxt8TUWMMAC3QhPZaak+IzQeyHch4gtqxftx96tprItY7AD/A3pGDF2uCSnN+SZrk6yVexm6EncDw==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-horizontal-rule@2.5.8': - resolution: {integrity: sha512-L8Is73WGaP6VNdKrIry+lCIM9W1KaL/Tw2Z6DGMVMU5mr1lLx0xq7nWEStqD7e4zh+n4+3PV15cZSA2F34DZrg==} + '@tiptap/extension-horizontal-rule@2.8.0': + resolution: {integrity: sha512-Sn/MI8WVFBoIYSIHA9NJryJIyCEzZdRysau8pC5TFnfifre0QV1ksPz2bgF+DyCD69ozQiRdBBHDEwKe47ZbfQ==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-italic@2.5.8': - resolution: {integrity: sha512-Kh35a7slBai+Qr/tiF9XFXmuWMgUQz4Nt51hmzqVGVuG+QsdWzQE8IZBGypKm8aAzxTGSY0d0QA0rys+YRNq1Q==} + '@tiptap/extension-italic@2.8.0': + resolution: {integrity: sha512-PwwSE2LTYiHI47NJnsfhBmPiLE8IXZYqaSoNPU6flPrk1KxEzqvRI1joKZBmD9wuqzmHJ93VFIeZcC+kfwi8ZA==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-link@2.5.8': - resolution: {integrity: sha512-qfeWR7sG2V7bn8z0f3HMyoR68pFlxYJmLs9cbW30diE9/zKClYEd3zTMPCgJ9yMSagCj4PWkqksIuktAhyRqOQ==} + '@tiptap/extension-link@2.8.0': + resolution: {integrity: sha512-p67hCG/pYCiOK/oCTPZnlkw9Ei7KJ7kCKFaluTcAmr5j8IBdYfDqSMDNCT4vGXBvKFh4X6xD7S7QvOqcH0Gn9A==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-list-item@2.5.8': - resolution: {integrity: sha512-RFIIzHxxXdPmdf7BL0zhE4VPHoR6BTWtfi3JCTftmNqKoH7o+mLKT0RHMGvF1CGNn2HewHzXAF0iXfKCwmEgHQ==} + '@tiptap/extension-list-item@2.8.0': + resolution: {integrity: sha512-o7OGymGxB0B9x3x2prp3KBDYFuBYGc5sW69O672jk8G52DqhzzndgPnkk0qUn8nXAUKuDGbJmpmHVA2kagqnRg==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-ordered-list@2.5.8': - resolution: {integrity: sha512-84gWdWhc8rUCCssn8+6Z1rFKdG7/yIe+gwYkU6WqAtDrcluJdt5jRHrcMOLxb2dbY8ww9pa72EYV/bwOisZlFQ==} + '@tiptap/extension-ordered-list@2.8.0': + resolution: {integrity: sha512-sCvNbcTS1+5QTTXwUPFa10vf5I1pr8sGcOTIh0G+a5ZkS5+6FxT12k7VLzPt39QyNbOi+77U2o4Xr4XyaEkfSg==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/extension-list-item': ^2.7.0 + '@tiptap/extension-text-style': ^2.7.0 - '@tiptap/extension-paragraph@2.5.8': - resolution: {integrity: sha512-AMfD3lfGSiomfkSE2tUourUjVahLtIfWUQew13NTPuWoxAXaSyoCGO0ULkiou/lO3JVUUUmF9+KJrAHWGIARdA==} + '@tiptap/extension-paragraph@2.8.0': + resolution: {integrity: sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-strike@2.5.8': - resolution: {integrity: sha512-uiHhBIEqawX9Up2ofklotVQ5XpGIjwRL6wprZF38s1le3XpsgyhVV7oDnqDkC7ujCsGkOJJfXZtv3LsO3R2nzQ==} + '@tiptap/extension-placeholder@2.8.0': + resolution: {integrity: sha512-BMqv/C9Tcjd7L1/OphUAJTZhWfpWs0rTQJ0bs3RRGsC8L+K20Fg+li45vw7M0teojpfrw57zwJogJd/m23Zr1Q==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-text@2.5.8': - resolution: {integrity: sha512-CNkD51jRMdcYCqFVOkrnebqBQ6pCD3ZD5z9kO5bOC5UPZKZBkLsWdlrHGAVwosxcGxdJACbqJ0Nj+fMgIw4tNA==} + '@tiptap/extension-strike@2.8.0': + resolution: {integrity: sha512-ezkDiXxQ3ME/dDMMM7tAMkKRi6UWw7tIu+Mx7Os0z8HCGpVBk1gFhLlhEd8I5rJaPZr4tK1wtSehMA9bscFGQw==} peerDependencies: - '@tiptap/core': ^2.5.8 + '@tiptap/core': ^2.7.0 - '@tiptap/pm@2.5.8': - resolution: {integrity: sha512-CVhHaTG4QNHSkvuh6HHsUR4hE+nbUnk7z+VMUedaqPU8tNqkTwWGCMbiyTc+PCsz0T9Mni7vvBR+EXgEQ3+w4g==} + '@tiptap/extension-text-style@2.8.0': + resolution: {integrity: sha512-jJp0vcZ2Ty7RvIL0VU6dm1y+fTfXq1lN2GwtYzYM0ueFuESa+Qo8ticYOImyWZ3wGJGVrjn7OV9r0ReW0/NYkQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 - '@tiptap/react@2.5.8': - resolution: {integrity: sha512-twUMm8HV7scUgR/E1hYS9N6JDtKPl7cgDiPjxTynNHc5S5f5Ecv4ns/BZRq3TMZ/JDrp4rghLvgq+ImQsLvPOA==} + '@tiptap/extension-text@2.8.0': + resolution: {integrity: sha512-EDAdFFzWOvQfVy7j3qkKhBpOeE5thkJaBemSWfXI93/gMVc0ZCdLi24mDvNNgUHlT+RjlIoQq908jZaaxLKN2A==} peerDependencies: - '@tiptap/core': ^2.5.8 - '@tiptap/pm': ^2.5.8 + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-typography@2.8.0': + resolution: {integrity: sha512-g+tbUba6cHbz62rLlmAdXw3GqBfZBVnYXtRqnQzTTPgb8a00+vEoUWA3sNyloq8FhfKFYcMWQDiqaVDqmity+w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.8.0': + resolution: {integrity: sha512-eMGpRooUMvKz/vOpnKKppApMSoNM325HxTdAJvTlVAmuHp5bOY5kyY1kfUlePRiVx1t1UlFcXs3kecFwkkBD3Q==} + + '@tiptap/react@2.8.0': + resolution: {integrity: sha512-o/aSCjO5Nu4MsNpTF+N1SzYzVQvvBiclmTOZX2E6usZ8jre5zmKfXHDSZnjGSRTK6z6kw5KW8wpjRQha03f9mg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - '@tiptap/starter-kit@2.5.8': - resolution: {integrity: sha512-Beb6Q3cFmJ1pE22WlFrG3wj8XAGXqaGkbqtsGAJDnoyWL4uoSs4vLt5I/UJshK/nQlNqTWFdpd9SxRFsxBYpqg==} + '@tiptap/starter-kit@2.8.0': + resolution: {integrity: sha512-r7UwaTrECkQoheWVZKFDqtL5tBx07x7IFT+prfgnsVlYFutGWskVVqzCDvD3BDmrg5PzeCWYZrQGlPaLib7tjg==} '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} @@ -2189,6 +2243,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -3424,6 +3483,9 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} @@ -3816,6 +3878,10 @@ packages: peerDependencies: postcss: ^8.2.14 + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss-selector-parser@6.0.16: resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} engines: {node: '>=4'} @@ -3886,8 +3952,8 @@ packages: prosemirror-menu@1.2.4: resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} - prosemirror-model@1.22.2: - resolution: {integrity: sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww==} + prosemirror-model@1.23.0: + resolution: {integrity: sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==} prosemirror-schema-basic@1.2.3: resolution: {integrity: sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==} @@ -3901,18 +3967,18 @@ packages: prosemirror-tables@1.4.0: resolution: {integrity: sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA==} - prosemirror-trailing-node@2.0.9: - resolution: {integrity: sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==} + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} peerDependencies: prosemirror-model: ^1.22.1 prosemirror-state: ^1.4.2 prosemirror-view: ^1.33.8 - prosemirror-transform@1.9.0: - resolution: {integrity: sha512-5UXkr1LIRx3jmpXXNKDhv8OyAOeLTGuXNwdVfg8x27uASna/wQkr9p6fD3eupGOi4PLJfbezxTyi/7fSJypXHg==} + prosemirror-transform@1.10.0: + resolution: {integrity: sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==} - prosemirror-view@1.33.9: - resolution: {integrity: sha512-xV1A0Vz9cIcEnwmMhKKFAOkfIp8XmJRnaZoPqNXrPS7EK5n11Ov8V76KhR0RsfQd/SIzmWY+bg+M44A2Lx/Nnw==} + prosemirror-view@1.34.3: + resolution: {integrity: sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==} proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -4189,8 +4255,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.3.0: - resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} @@ -4486,6 +4552,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -5645,6 +5714,10 @@ snapshots: '@types/react': 18.2.17 '@types/react-dom': 18.2.7 + '@radix-ui/react-icons@1.3.0(react@18.2.0)': + dependencies: + react: 18.2.0 + '@radix-ui/react-id@1.0.1(@types/react@18.2.17)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -6146,7 +6219,7 @@ snapshots: dependencies: react: 18.2.0 - '@remirror/core-constants@2.0.2': {} + '@remirror/core-constants@3.0.0': {} '@rollup/pluginutils@5.1.0(rollup@3.29.4)': dependencies: @@ -6290,6 +6363,14 @@ snapshots: mini-svg-data-uri: 1.4.4 tailwindcss: 3.4.3(ts-node@10.9.2(@swc/core@1.4.8(@swc/helpers@0.5.10))(@types/node@20.5.1)(typescript@5.4.3)) + '@tailwindcss/typography@0.5.15(tailwindcss@3.4.3(ts-node@10.9.2(@swc/core@1.4.8(@swc/helpers@0.5.10))(@types/node@20.5.1)(typescript@5.4.3)))': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.3(ts-node@10.9.2(@swc/core@1.4.8(@swc/helpers@0.5.10))(@types/node@20.5.1)(typescript@5.4.3)) + '@tanstack/eslint-plugin-query@5.28.6(eslint@8.45.0)(typescript@5.1.6)': dependencies: '@typescript-eslint/utils': 6.21.0(eslint@8.45.0)(typescript@5.1.6) @@ -6414,111 +6495,128 @@ snapshots: dependencies: '@testing-library/dom': 9.3.4 - '@tiptap/core@2.5.8(@tiptap/pm@2.5.8)': + '@tiptap/core@2.8.0(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/pm': 2.5.8 + '@tiptap/pm': 2.8.0 - '@tiptap/extension-blockquote@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-blockquote@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-bold@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-bold@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-bubble-menu@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-bubble-menu@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 tippy.js: 6.3.7 - '@tiptap/extension-bullet-list@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-bullet-list@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/extension-list-item': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-text-style': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) - '@tiptap/extension-character-count@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-character-count@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-code-block@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-code-block@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-code@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-code@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-document@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-document@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-dropcursor@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-dropcursor@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-floating-menu@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-floating-menu@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 tippy.js: 6.3.7 - '@tiptap/extension-gapcursor@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-gapcursor@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-hard-break@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-hard-break@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-heading@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-heading@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-history@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-history@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-horizontal-rule@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-horizontal-rule@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-italic@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-italic@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-link@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)': + '@tiptap/extension-link@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 linkifyjs: 4.1.3 - '@tiptap/extension-list-item@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-list-item@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-ordered-list@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-ordered-list@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/extension-list-item': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-text-style': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) - '@tiptap/extension-paragraph@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-paragraph@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/extension-strike@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-placeholder@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 - '@tiptap/extension-text@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))': + '@tiptap/extension-strike@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) - '@tiptap/pm@2.5.8': + '@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': + dependencies: + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + + '@tiptap/extension-text@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': + dependencies: + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + + '@tiptap/extension-typography@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))': + dependencies: + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + + '@tiptap/pm@2.8.0': dependencies: prosemirror-changeset: 2.2.1 prosemirror-collab: 1.3.1 @@ -6530,49 +6628,51 @@ snapshots: prosemirror-keymap: 1.2.2 prosemirror-markdown: 1.13.0 prosemirror-menu: 1.2.4 - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-schema-basic: 1.2.3 prosemirror-schema-list: 1.4.1 prosemirror-state: 1.4.3 prosemirror-tables: 1.4.0 - prosemirror-trailing-node: 2.0.9(prosemirror-model@1.22.2)(prosemirror-state@1.4.3)(prosemirror-view@1.33.9) - prosemirror-transform: 1.9.0 - prosemirror-view: 1.33.9 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.23.0)(prosemirror-state@1.4.3)(prosemirror-view@1.34.3) + prosemirror-transform: 1.10.0 + prosemirror-view: 1.34.3 - '@tiptap/react@2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@tiptap/react@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/extension-bubble-menu': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/extension-floating-menu': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/pm': 2.5.8 + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/extension-bubble-menu': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/extension-floating-menu': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/pm': 2.8.0 '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) use-sync-external-store: 1.2.2(react@18.2.0) - '@tiptap/starter-kit@2.5.8(@tiptap/pm@2.5.8)': - dependencies: - '@tiptap/core': 2.5.8(@tiptap/pm@2.5.8) - '@tiptap/extension-blockquote': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-bold': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-bullet-list': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-code': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-code-block': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/extension-document': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-dropcursor': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/extension-gapcursor': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/extension-hard-break': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-heading': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-history': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/extension-horizontal-rule': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8))(@tiptap/pm@2.5.8) - '@tiptap/extension-italic': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-list-item': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-ordered-list': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-paragraph': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-strike': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) - '@tiptap/extension-text': 2.5.8(@tiptap/core@2.5.8(@tiptap/pm@2.5.8)) + '@tiptap/starter-kit@2.8.0(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))': + dependencies: + '@tiptap/core': 2.8.0(@tiptap/pm@2.8.0) + '@tiptap/extension-blockquote': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-bold': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-bullet-list': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))) + '@tiptap/extension-code': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-code-block': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/extension-document': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-dropcursor': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/extension-gapcursor': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/extension-hard-break': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-heading': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-history': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/extension-horizontal-rule': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0) + '@tiptap/extension-italic': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-list-item': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-ordered-list': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))) + '@tiptap/extension-paragraph': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-strike': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/extension-text': 2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0)) + '@tiptap/pm': 2.8.0 transitivePeerDependencies: - - '@tiptap/pm' + - '@tiptap/extension-text-style' '@tootallnate/once@2.0.0': {} @@ -6878,6 +6978,9 @@ snapshots: acorn@8.11.3: {} + acorn@8.12.1: + optional: true + agent-base@6.0.2: dependencies: debug: 4.3.4 @@ -8292,7 +8395,7 @@ snapshots: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.6.2 + tslib: 2.7.0 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -8340,6 +8443,8 @@ snapshots: lodash.camelcase@4.3.0: {} + lodash.castarray@4.4.0: {} + lodash.isfunction@3.0.9: {} lodash.isplainobject@4.0.6: {} @@ -8503,7 +8608,7 @@ snapshots: needle@3.3.1: dependencies: iconv-lite: 0.6.3 - sax: 1.3.0 + sax: 1.4.1 optional: true no-case@3.0.4: @@ -8707,6 +8812,11 @@ snapshots: postcss: 8.4.38 postcss-selector-parser: 6.0.16 + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-selector-parser@6.0.16: dependencies: cssesc: 3.0.0 @@ -8752,7 +8862,7 @@ snapshots: prosemirror-changeset@2.2.1: dependencies: - prosemirror-transform: 1.9.0 + prosemirror-transform: 1.10.0 prosemirror-collab@1.3.1: dependencies: @@ -8760,34 +8870,34 @@ snapshots: prosemirror-commands@1.6.0: dependencies: - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 + prosemirror-transform: 1.10.0 prosemirror-dropcursor@1.8.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 - prosemirror-view: 1.33.9 + prosemirror-transform: 1.10.0 + prosemirror-view: 1.34.3 prosemirror-gapcursor@1.3.2: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-state: 1.4.3 - prosemirror-view: 1.33.9 + prosemirror-view: 1.34.3 prosemirror-history@1.4.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 - prosemirror-view: 1.33.9 + prosemirror-transform: 1.10.0 + prosemirror-view: 1.34.3 rope-sequence: 1.3.4 prosemirror-inputrules@1.4.0: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 + prosemirror-transform: 1.10.0 prosemirror-keymap@1.2.2: dependencies: @@ -8797,7 +8907,7 @@ snapshots: prosemirror-markdown@1.13.0: dependencies: markdown-it: 14.1.0 - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-menu@1.2.4: dependencies: @@ -8806,51 +8916,51 @@ snapshots: prosemirror-history: 1.4.1 prosemirror-state: 1.4.3 - prosemirror-model@1.22.2: + prosemirror-model@1.23.0: dependencies: orderedmap: 2.1.1 prosemirror-schema-basic@1.2.3: dependencies: - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-schema-list@1.4.1: dependencies: - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 + prosemirror-transform: 1.10.0 prosemirror-state@1.4.3: dependencies: - prosemirror-model: 1.22.2 - prosemirror-transform: 1.9.0 - prosemirror-view: 1.33.9 + prosemirror-model: 1.23.0 + prosemirror-transform: 1.10.0 + prosemirror-view: 1.34.3 prosemirror-tables@1.4.0: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 - prosemirror-view: 1.33.9 + prosemirror-transform: 1.10.0 + prosemirror-view: 1.34.3 - prosemirror-trailing-node@2.0.9(prosemirror-model@1.22.2)(prosemirror-state@1.4.3)(prosemirror-view@1.33.9): + prosemirror-trailing-node@3.0.0(prosemirror-model@1.23.0)(prosemirror-state@1.4.3)(prosemirror-view@1.34.3): dependencies: - '@remirror/core-constants': 2.0.2 + '@remirror/core-constants': 3.0.0 escape-string-regexp: 4.0.0 - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-state: 1.4.3 - prosemirror-view: 1.33.9 + prosemirror-view: 1.34.3 - prosemirror-transform@1.9.0: + prosemirror-transform@1.10.0: dependencies: - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 - prosemirror-view@1.33.9: + prosemirror-view@1.34.3: dependencies: - prosemirror-model: 1.22.2 + prosemirror-model: 1.23.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.9.0 + prosemirror-transform: 1.10.0 proxy-from-env@1.1.0: {} @@ -9121,7 +9231,7 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.3.0: + sax@1.4.1: optional: true saxes@6.0.0: @@ -9364,7 +9474,7 @@ snapshots: terser@5.29.2: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.11.3 + acorn: 8.12.1 commander: 2.20.3 source-map-support: 0.5.21 optional: true @@ -9456,6 +9566,9 @@ snapshots: tslib@2.6.2: {} + tslib@2.7.0: + optional: true + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/web/src/components/rich-text-editor/components/bubble-menu/link-bubble-menu.tsx b/web/src/components/rich-text-editor/components/bubble-menu/link-bubble-menu.tsx new file mode 100644 index 000000000..b76b5bd9c --- /dev/null +++ b/web/src/components/rich-text-editor/components/bubble-menu/link-bubble-menu.tsx @@ -0,0 +1,58 @@ +import type { Editor } from '@tiptap/core'; +import { BubbleMenu } from '@tiptap/react'; +import { useState } from 'react'; +import { LinkProps, ShouldShowProps } from '../../types'; +import { setLink } from '../../utils'; +import { LinkEditBlock } from '../link/link-edit-block'; +import { LinkPopoverBlock } from '../link/link-popover-block'; + +const LinkBubbleMenu = ({ editor }: { editor: Editor }) => { + const [showEdit, setShowEdit] = useState(false); + const shouldShow = ({ editor, from, to }: ShouldShowProps) => { + if (from === to) { + return false; + } + + const link = editor.getAttributes('link'); + + if (link['href']) { + return true; + } + + return false; + }; + + const unSetLink = () => { + editor.chain().extendMarkRange('link').unsetLink().focus().run(); + setShowEdit(false); + }; + + function onSetLink(props: LinkProps) { + setLink(editor, props); + setShowEdit(false); + } + + return ( + { + setShowEdit(false); + }, + }}> + {showEdit ? ( + + ) : ( + setShowEdit(true)} /> + )} + + ); +}; + +export { LinkBubbleMenu }; diff --git a/web/src/components/rich-text-editor/components/headings.tsx b/web/src/components/rich-text-editor/components/headings.tsx new file mode 100644 index 000000000..9c57825f3 --- /dev/null +++ b/web/src/components/rich-text-editor/components/headings.tsx @@ -0,0 +1,118 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import type { toggleVariants } from '@/components/ui/toggle'; +import { cn } from '@/lib/utils'; +import { CaretDownIcon, LetterCaseCapitalizeIcon } from '@radix-ui/react-icons'; +import type { Level } from '@tiptap/extension-heading'; +import type { Editor } from '@tiptap/react'; +import type { VariantProps } from 'class-variance-authority'; +import * as React from 'react'; +import type { FormatAction } from '../types'; +import { ToolbarButton } from './toolbar-button'; + +interface TextStyle extends Omit { + element: keyof JSX.IntrinsicElements; + level?: Level; + className: string; +} + +const formatActions: TextStyle[] = [ + { + label: 'Normal Text', + element: 'span', + className: 'grow', + shortcuts: ['mod', 'alt', '0'], + }, + { + label: 'Heading 1', + element: 'h1', + level: 1, + className: 'm-0 grow text-3xl font-extrabold', + shortcuts: ['mod', 'alt', '1'], + }, + { + label: 'Heading 2', + element: 'h2', + level: 2, + className: 'm-0 grow text-xl font-bold', + shortcuts: ['mod', 'alt', '2'], + }, + { + label: 'Heading 3', + element: 'h3', + level: 3, + className: 'm-0 grow text-lg font-semibold', + shortcuts: ['mod', 'alt', '3'], + }, + +]; + +interface HeadingsProps extends VariantProps { + editor: Editor; + activeLevels?: Level[]; +} + +export const Headings: React.FC = React.memo( + ({ editor, activeLevels = [1, 2, 3, 4, 5, 6], size, variant }) => { + const filteredActions = React.useMemo( + () => formatActions.filter((action) => !action.level || activeLevels.includes(action.level)), + [activeLevels] + ); + + const handleStyleChange = React.useCallback( + (level?: Level) => { + if (level) { + editor.chain().focus().toggleHeading({ level }).run(); + } else { + editor.chain().focus().setParagraph().run(); + } + }, + [editor] + ); + + const renderMenuItem = React.useCallback( + ({ label, element: Element, level, className, shortcuts }: TextStyle) => ( + handleStyleChange(level)} + className={cn('flex flex-row items-center justify-between gap-4', { + 'bg-accent': level ? editor.isActive('heading', { level }) : editor.isActive('paragraph'), + })} + aria-label={label}> + {label} + + ), + [editor, handleStyleChange] + ); + + return ( + + + + + + + + + {filteredActions.map(renderMenuItem)} + + + ); + } +); + +Headings.displayName = 'Headings'; + +export default Headings; diff --git a/web/src/components/rich-text-editor/components/link/link-edit-block.tsx b/web/src/components/rich-text-editor/components/link/link-edit-block.tsx new file mode 100644 index 000000000..359640e75 --- /dev/null +++ b/web/src/components/rich-text-editor/components/link/link-edit-block.tsx @@ -0,0 +1,126 @@ +import type { Editor } from '@tiptap/core' +import * as React from 'react' +import { Button } from '@/components/ui/button' +import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui/input' +import { LinkProps, LinkType } from '../../types' +import { cn } from '@/lib/utils' +import { Toggle } from '@/components/ui/toggle' +import { Link2Icon, MobileIcon, EnvelopeClosedIcon } from '@radix-ui/react-icons' + +interface LinkEditBlockProps extends React.HTMLAttributes { + editor: Editor + onSetLink: ({ url, text, type }: LinkProps) => void + close?: () => void +} + +const LinkEditBlock = ({ editor, onSetLink, close, className, ...props }: LinkEditBlockProps) => { + const formRef = React.useRef(null) + + const [field, setField] = React.useState({ + url: '', + text: '', + type: LinkType.URL + }) + + const data = React.useMemo(() => { + const { href } = editor.getAttributes('link') + const { from, to } = editor.state.selection + const text = editor.state.doc.textBetween(from, to, ' ') + + const isTelephone = href?.startsWith('tel:'); + const isEmail = href?.startsWith('mailto:'); + + let url: string = href; + + if (isTelephone) { + url = url.replace('tel:', '') + } + + if (isEmail) { + url = url.replace('mailto:', '') + } + + return { + url, + text, + type: isTelephone ? LinkType.TELEPHONE : isEmail ? LinkType.EMAIL : LinkType.URL + } + }, [editor]) + + React.useEffect(() => { + console.log('effect'); + setField(data) + }, [data]) + + const handleClick = (e: React.FormEvent) => { + e.preventDefault() + + if (formRef.current) { + const isValid = Array.from(formRef.current.querySelectorAll('input')).every(input => input.checkValidity()) + + if (isValid) { + onSetLink(field) + close?.() + } else { + formRef.current.querySelectorAll('input').forEach(input => { + if (!input.checkValidity()) { + input.reportValidity() + } + }) + } + } + } + + return ( +
+
+
+ setField({ ...field, type: LinkType.URL })} pressed={field.type === LinkType.URL}> + + + setField({ ...field, type: LinkType.EMAIL })} pressed={field.type === LinkType.EMAIL} > + + + setField({ ...field, type: LinkType.TELEPHONE })} pressed={field.type === LinkType.TELEPHONE}> + + +
+
+ + setField({ ...field, url: e.target.value })} + /> +
+ +
+ + setField({ ...field, text: e.target.value })} + /> +
+ +
+ {close && ( + + )} + + +
+
+
+ ) +} + +export { LinkEditBlock } diff --git a/web/src/components/rich-text-editor/components/link/link-edit-popover.tsx b/web/src/components/rich-text-editor/components/link/link-edit-popover.tsx new file mode 100644 index 000000000..98ea51254 --- /dev/null +++ b/web/src/components/rich-text-editor/components/link/link-edit-popover.tsx @@ -0,0 +1,37 @@ +import type { Editor } from '@tiptap/core' +import * as React from 'react' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Link2Icon } from '@radix-ui/react-icons' +import { ToolbarButton } from '../toolbar-button' +import { LinkEditBlock } from './link-edit-block' +import { LinkProps } from '../../types' +import { setLink } from '../../utils' + +const LinkEditPopover = ({ editor }: { editor: Editor }) => { + const [open, setOpen] = React.useState(false) + + const onSetLink = (props: LinkProps) => { + setLink(editor, props) + editor.commands.enter() + } + + return ( + + + + + + + + setOpen(false)} onSetLink={onSetLink} /> + + + ) +} + +export { LinkEditPopover } diff --git a/web/src/components/rich-text-editor/components/link/link-popover-block.tsx b/web/src/components/rich-text-editor/components/link/link-popover-block.tsx new file mode 100644 index 000000000..97ae6f3d9 --- /dev/null +++ b/web/src/components/rich-text-editor/components/link/link-popover-block.tsx @@ -0,0 +1,33 @@ +import { Separator } from '@/components/ui/separator'; +import { ExternalLinkIcon, LinkBreak2Icon } from '@radix-ui/react-icons'; +import { ToolbarButton } from '../toolbar-button'; + +const LinkPopoverBlock = ({ + link, + onClear, + onEdit, +}: { + link: Record; + onClear: () => void; + onEdit: (e: React.MouseEvent) => void; +}) => { + return ( +
+
+ + Edit link + + + window.open(link['href'] as string, '_blank')}> + + + + + + +
+
+ ); +}; + +export { LinkPopoverBlock }; diff --git a/web/src/components/rich-text-editor/components/toolbar-button.tsx b/web/src/components/rich-text-editor/components/toolbar-button.tsx new file mode 100644 index 000000000..3b4f0d069 --- /dev/null +++ b/web/src/components/rich-text-editor/components/toolbar-button.tsx @@ -0,0 +1,40 @@ +import * as React from 'react' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' +import { Toggle } from '@/components/ui/toggle' +import { cn } from '@/lib/utils' +import type { TooltipContentProps } from '@radix-ui/react-tooltip' + +interface ToolbarButtonProps extends React.ComponentPropsWithoutRef { + isActive?: boolean + tooltip?: string + tooltipOptions?: TooltipContentProps +} + +export const ToolbarButton = React.forwardRef( + ({ isActive, children, tooltip, className, tooltipOptions, ...props }, ref) => { + const toggleButton = ( + + {children} + + ) + + if (!tooltip) { + return toggleButton + } + + return ( + + + {toggleButton} + +
{tooltip}
+
+
+
+ ) + } +) + +ToolbarButton.displayName = 'ToolbarButton' + +export default ToolbarButton diff --git a/web/src/components/rich-text-editor/extensions/link/index.ts b/web/src/components/rich-text-editor/extensions/link/index.ts new file mode 100644 index 000000000..6bbafd208 --- /dev/null +++ b/web/src/components/rich-text-editor/extensions/link/index.ts @@ -0,0 +1 @@ +export * from './link' diff --git a/web/src/components/rich-text-editor/extensions/link/link.ts b/web/src/components/rich-text-editor/extensions/link/link.ts new file mode 100644 index 000000000..75b1d75fc --- /dev/null +++ b/web/src/components/rich-text-editor/extensions/link/link.ts @@ -0,0 +1,93 @@ +import { getMarkRange, mergeAttributes } from '@tiptap/core'; +import TiptapLink from '@tiptap/extension-link'; +import { Plugin, TextSelection } from '@tiptap/pm/state'; +import { EditorView } from '@tiptap/pm/view'; + +export const Link = TiptapLink.extend({ + /* + * Determines whether typing next to a link automatically becomes part of the link. + * In this case, we dont want any characters to be included as part of the link. + */ + inclusive: false, + + /* + * Match all elements that have an href attribute, except for: + * - elements with a data-type attribute set to button + * - elements with an href attribute that contains 'javascript:' + */ + parseHTML() { + return [{ tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])' }]; + }, + + renderHTML({ HTMLAttributes }) { + return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]; + }, + + addOptions() { + return { + ...this.parent?.(), + openOnClick: true, + protocols: ['https', 'mailto'], + HTMLAttributes: { + class: 'link', + }, + }; + }, + + addPasteRules() { + return []; + }, + + addProseMirrorPlugins() { + const { editor } = this; + + return [ + ...(this.parent?.() || []), + new Plugin({ + props: { + handleKeyDown: (_: EditorView, event: KeyboardEvent) => { + const { selection } = editor.state; + + /* + * Handles the 'Escape' key press when there's a selection within the link. + * This will move the cursor to the end of the link. + */ + if (event.key === 'Escape' && selection.empty !== true) { + editor.commands.focus(selection.to, { scrollIntoView: false }); + } + + return false; + }, + handleClick(view, pos) { + /* + * Marks the entire link when the user clicks on it. + */ + + const { schema, doc, tr } = view.state; + const range = getMarkRange(doc.resolve(pos), schema.marks['link'] as any); + + if (!range) { + return; + } + + const { from, to } = range; + const start = Math.min(from, to); + const end = Math.max(from, to); + + if (pos < start || pos > end) { + return; + } + + const $start = doc.resolve(start); + const $end = doc.resolve(end); + const transaction = tr.setSelection(new TextSelection($start, $end)); + + view.dispatch(transaction); + }, + }, + }), + ]; + }, +}); + +export default Link; diff --git a/web/src/components/rich-text-editor/extensions/selection/index.ts b/web/src/components/rich-text-editor/extensions/selection/index.ts new file mode 100644 index 000000000..75df11a69 --- /dev/null +++ b/web/src/components/rich-text-editor/extensions/selection/index.ts @@ -0,0 +1 @@ +export * from './selection' diff --git a/web/src/components/rich-text-editor/extensions/selection/selection.ts b/web/src/components/rich-text-editor/extensions/selection/selection.ts new file mode 100644 index 000000000..7e28ac2f2 --- /dev/null +++ b/web/src/components/rich-text-editor/extensions/selection/selection.ts @@ -0,0 +1,36 @@ +import { Extension } from '@tiptap/core' +import { Plugin, PluginKey } from '@tiptap/pm/state' +import { Decoration, DecorationSet } from '@tiptap/pm/view' + +export const Selection = Extension.create({ + name: 'selection', + + addProseMirrorPlugins() { + const { editor } = this + + return [ + new Plugin({ + key: new PluginKey('selection'), + props: { + decorations(state) { + if (state.selection.empty) { + return null + } + + if (editor.isFocused === true) { + return null + } + + return DecorationSet.create(state.doc, [ + Decoration.inline(state.selection.from, state.selection.to, { + class: 'selection' + }) + ]) + } + } + }) + ] + } +}) + +export default Selection diff --git a/web/src/components/rich-text-editor/index.ts b/web/src/components/rich-text-editor/index.ts new file mode 100644 index 000000000..43199eede --- /dev/null +++ b/web/src/components/rich-text-editor/index.ts @@ -0,0 +1 @@ +export * from './rich-text-editor' diff --git a/web/src/components/rich-text-editor/rich-text-editor.tsx b/web/src/components/rich-text-editor/rich-text-editor.tsx new file mode 100644 index 000000000..46ac827cd --- /dev/null +++ b/web/src/components/rich-text-editor/rich-text-editor.tsx @@ -0,0 +1,191 @@ +import { Separator } from '@/components/ui/separator'; +import { cn } from '@/lib/utils'; +import { + AlignLeftIcon, + EraserIcon, + FontBoldIcon, + FontItalicIcon, + ListBulletIcon, + StrikethroughIcon, +} from '@radix-ui/react-icons'; +import type { Editor as TiptapEditor } from '@tiptap/core'; +import CharacterCount from '@tiptap/extension-character-count'; +import Heading from '@tiptap/extension-heading'; +import { Placeholder } from '@tiptap/extension-placeholder'; +import { TextStyle } from '@tiptap/extension-text-style'; +import { Typography } from '@tiptap/extension-typography'; +import { EditorContent, useEditor } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import * as React from 'react'; +import { LinkBubbleMenu } from './components/bubble-menu/link-bubble-menu'; +import Headings from './components/headings'; +import { LinkEditPopover } from './components/link/link-edit-popover'; +import ToolbarButton from './components/toolbar-button'; +import { Link } from './extensions/link'; +import { Selection } from './extensions/selection'; +import './styles/index.css'; + +export interface RichTextProps extends Omit, 'onChange'> { + value?: string | null; + placeholder?: string; + disabled?: boolean; + contentClass?: string; + characterLimit?: number; + onValueChange: (value: string) => void; +} + +const useRichTextEditor = (props: RichTextProps) => { + const { value, placeholder, disabled, characterLimit, onValueChange } = props; + + return useEditor({ + extensions: [ + StarterKit.configure({ + horizontalRule: false, + codeBlock: false, + paragraph: { + HTMLAttributes: { + class: 'text-node', + }, + }, + bulletList: { + HTMLAttributes: { + class: 'list-node', + }, + }, + orderedList: { + HTMLAttributes: { + class: 'list-node', + }, + }, + dropcursor: { + width: 2, + class: 'ProseMirror-dropcursor border', + }, + }), + Link, + TextStyle, + Selection, + Typography, + Placeholder.configure({ + placeholder: () => placeholder || '', + }), + Heading.configure({ levels: [1, 2, 3] }), + CharacterCount.configure({ + limit: characterLimit, + }), + ], + editorProps: { + attributes: { + class: cn('prose max-w-none [&_ol]:list-decimal [&_ul]:list-disc'), + }, + }, + onUpdate: ({ editor }) => { + const html = editor.getHTML(); + onValueChange(html); + }, + content: value, + editable: !disabled, + onCreate: ({ editor }) => { + if (value) { + editor.chain().setContent(value).run(); + } + }, + }); +}; + +const Toolbar = ({ editor }: { editor: TiptapEditor }) => ( +
+
+ + editor.chain().focus().toggleBold().run()} + disabled={!editor.can().chain().focus().toggleBold().run()} + isActive={editor.isActive('bold')} + tooltip='Bold' + aria-label='Bold'> + + + + editor.chain().focus().toggleItalic().run()} + disabled={!editor.can().chain().focus().toggleItalic().run()} + isActive={editor.isActive('italic')} + tooltip='Italic' + aria-label='Italic'> + + + + editor.chain().focus().toggleStrike().run()} + disabled={!editor.can().chain().focus().toggleStrike().run()} + isActive={editor.isActive('strike')} + tooltip='Strikethrough' + aria-label='Strikethrough'> + + + + editor.chain().focus().unsetAllMarks().run()} + disabled={!editor.can().chain().focus().unsetAllMarks().run()} + isActive={false} + tooltip='Clear formatting' + aria-label='Clear formatting'> + + + + editor.chain().focus().toggleOrderedList().run()} + isActive={editor.isActive('orderedList')} + tooltip='Numbered list' + aria-label='Numbered list'> + + + + editor.chain().focus().toggleBulletList().run()} + isActive={editor.isActive('bulletList')} + tooltip='Bullet list' + aria-label='Bullet list'> + + + + +
+
+); + +export const RichTextEditor = React.forwardRef( + ({ value, disabled, characterLimit, contentClass, onValueChange, placeholder, className, ...props }, ref) => { + const editor = useRichTextEditor({ value, placeholder, disabled, characterLimit, onValueChange }); + + return ( +
+ {editor && ( + <> + + + + )} +
editor?.chain().focus().run()}> + +
+
+ ); + } +); + +RichTextEditor.displayName = 'RichTextEditor'; + +export default RichTextEditor; diff --git a/web/src/components/rich-text-editor/styles/index.css b/web/src/components/rich-text-editor/styles/index.css new file mode 100644 index 000000000..25f46c969 --- /dev/null +++ b/web/src/components/rich-text-editor/styles/index.css @@ -0,0 +1,181 @@ +@import './partials/placeholder.css'; +@import './partials/lists.css'; +@import './partials/typography.css'; + +:root { + --minimal-tiptap-code-background: rgba(8, 43, 120, 0.047); + --minimal-tiptap-code-color: rgb(212, 212, 212); + --minimal-tiptap-secondary: rgb(157, 157, 159); + --minimal-tiptap-pre-background: rgb(236, 236, 236); + --minimal-tiptap-pre-border: rgb(224, 224, 224); + --minimal-tiptap-pre-color: rgb(47, 47, 49); + --minimal-tiptap-hr: rgb(220, 220, 220); + --minimal-tiptap-drag-handle-hover: rgb(92, 92, 94); + + --mt-accent-bold-blue: #05c; + --mt-accent-bold-teal: #206a83; + --mt-accent-bold-green: #216e4e; + --mt-accent-bold-orange: #a54800; + --mt-accent-bold-red: #ae2e24; + --mt-accent-bold-purple: #5e4db2; + + --mt-accent-gray: #758195; + --mt-accent-blue: #1d7afc; + --mt-accent-teal: #2898bd; + --mt-accent-green: #22a06b; + --mt-accent-orange: #fea362; + --mt-accent-red: #c9372c; + --mt-accent-purple: #8270db; + + --mt-accent-blue-subtler: #cce0ff; + --mt-accent-teal-subtler: #c6edfb; + --mt-accent-green-subtler: #baf3db; + --mt-accent-yellow-subtler: #f8e6a0; + --mt-accent-red-subtler: #ffd5d2; + --mt-accent-purple-subtler: #dfd8fd; + + --hljs-string: rgb(170, 67, 15); + --hljs-title: rgb(176, 136, 54); + --hljs-comment: rgb(153, 153, 153); + --hljs-keyword: rgb(12, 94, 177); + --hljs-attr: rgb(58, 146, 188); + --hljs-literal: rgb(200, 43, 15); + --hljs-name: rgb(37, 151, 146); + --hljs-selector-tag: rgb(200, 80, 15); + --hljs-number: rgb(61, 160, 103); +} + +.dark { + --minimal-tiptap-code-background: rgba(255, 255, 255, 0.075); + --minimal-tiptap-code-color: rgb(44, 46, 51); + --minimal-tiptap-secondary: rgb(89, 90, 92); + --minimal-tiptap-pre-background: rgb(8, 8, 8); + --minimal-tiptap-pre-border: rgb(35, 37, 42); + --minimal-tiptap-pre-color: rgb(227, 228, 230); + --minimal-tiptap-hr: rgb(38, 40, 45); + --minimal-tiptap-drag-handle-hover: rgb(150, 151, 153); + + --mt-accent-bold-blue: #85b8ff; + --mt-accent-bold-teal: #9dd9ee; + --mt-accent-bold-green: #7ee2b8; + --mt-accent-bold-orange: #fec195; + --mt-accent-bold-red: #fd9891; + --mt-accent-bold-purple: #b8acf6; + + --mt-accent-gray: #738496; + --mt-accent-blue: #388bff; + --mt-accent-teal: #42b2d7; + --mt-accent-green: #2abb7f; + --mt-accent-orange: #a54800; + --mt-accent-red: #e2483d; + --mt-accent-purple: #8f7ee7; + + --mt-accent-blue-subtler: #09326c; + --mt-accent-teal-subtler: #164555; + --mt-accent-green-subtler: #164b35; + --mt-accent-yellow-subtler: #533f04; + --mt-accent-red-subtler: #5d1f1a; + --mt-accent-purple-subtler: #352c63; + + --hljs-string: rgb(218, 147, 107); + --hljs-title: rgb(241, 213, 157); + --hljs-comment: rgb(170, 170, 170); + --hljs-keyword: rgb(102, 153, 204); + --hljs-attr: rgb(144, 202, 232); + --hljs-literal: rgb(242, 119, 122); + --hljs-name: rgb(95, 192, 160); + --hljs-selector-tag: rgb(232, 199, 133); + --hljs-number: rgb(182, 231, 182); +} + +.minimal-tiptap-editor .ProseMirror { + @apply flex max-w-full flex-1 cursor-text flex-col; + @apply z-0 outline-0; +} + +.minimal-tiptap-editor .ProseMirror > div.editor { + @apply block flex-1 whitespace-pre-wrap; +} + +.minimal-tiptap-editor .ProseMirror .block-node:not(:last-child), +.minimal-tiptap-editor .ProseMirror .list-node:not(:last-child), +.minimal-tiptap-editor .ProseMirror .text-node:not(:last-child) { + @apply mb-2.5; +} + +.minimal-tiptap-editor .ProseMirror ol, +.minimal-tiptap-editor .ProseMirror ul { + @apply pl-6; +} + +.minimal-tiptap-editor .ProseMirror blockquote, +.minimal-tiptap-editor .ProseMirror dl, +.minimal-tiptap-editor .ProseMirror ol, +.minimal-tiptap-editor .ProseMirror p, +.minimal-tiptap-editor .ProseMirror pre, +.minimal-tiptap-editor .ProseMirror ul { + @apply m-0; +} + +.minimal-tiptap-editor .ProseMirror li { + @apply leading-7; +} + +.minimal-tiptap-editor .ProseMirror p { + @apply break-words text-base leading-7; +} + +.minimal-tiptap-editor .ProseMirror li .text-node:has(+ .list-node), +.minimal-tiptap-editor .ProseMirror li > .list-node, +.minimal-tiptap-editor .ProseMirror li > .text-node, +.minimal-tiptap-editor .ProseMirror li p { + @apply mb-0; +} + +.minimal-tiptap-editor .ProseMirror blockquote { + @apply relative pl-3.5; +} + +.minimal-tiptap-editor .ProseMirror blockquote::before, +.minimal-tiptap-editor .ProseMirror blockquote.is-empty::before { + @apply absolute bottom-0 left-0 top-0 h-full w-1 rounded-sm bg-accent content-['']; +} + +.minimal-tiptap-editor .ProseMirror hr { + @apply my-3 h-0.5 w-full border-none bg-[var(--minimal-tiptap-hr)]; +} + +.minimal-tiptap-editor .ProseMirror-focused hr.ProseMirror-selectednode { + @apply rounded-full outline outline-2 outline-offset-1 outline-muted-foreground; +} + +.minimal-tiptap-editor .ProseMirror .ProseMirror-gapcursor { + @apply pointer-events-none absolute hidden; +} + +.minimal-tiptap-editor .ProseMirror .ProseMirror-hideselection { + @apply caret-transparent; +} + +.minimal-tiptap-editor .ProseMirror.resize-cursor { + @apply cursor-col-resize; +} + +.minimal-tiptap-editor .ProseMirror .selection { + @apply inline-block; +} + +.minimal-tiptap-editor .ProseMirror .selection, +.minimal-tiptap-editor .ProseMirror *::selection, +::selection { + @apply bg-primary/40; +} + +/* Override native selection when custom selection is present */ +.minimal-tiptap-editor .ProseMirror .selection::selection { + background: transparent; +} + +[data-theme='slash-command'] { + width: 1000vw; +} diff --git a/web/src/components/rich-text-editor/styles/partials/lists.css b/web/src/components/rich-text-editor/styles/partials/lists.css new file mode 100644 index 000000000..c3b171e11 --- /dev/null +++ b/web/src/components/rich-text-editor/styles/partials/lists.css @@ -0,0 +1,82 @@ +.minimal-tiptap-editor .ProseMirror { + @apply text-base; +} + +.minimal-tiptap-editor .ProseMirror ol { + @apply list-decimal; +} + +.minimal-tiptap-editor .ProseMirror ol ol { + list-style: lower-alpha; +} + +.minimal-tiptap-editor .ProseMirror ol ol ol { + list-style: lower-roman; +} + +.minimal-tiptap-editor .ProseMirror ul { + list-style: disc; +} + +.minimal-tiptap-editor .ProseMirror ul ul { + list-style: circle; +} + +.minimal-tiptap-editor .ProseMirror ul ul ul { + list-style: square; +} + +.minimal-tiptap-editor .ProseMirror ul[data-type='taskList'] { + @apply list-none pl-1; +} + +.minimal-tiptap-editor .ProseMirror ul[data-type='taskList'] p { + @apply m-0; +} + +.minimal-tiptap-editor .ProseMirror ul[data-type='taskList'] li > label { + @apply mr-2 mt-0.5 flex-none select-none; +} + +.minimal-tiptap-editor .ProseMirror li[data-type='taskItem'] { + @apply flex flex-row items-start; +} + +.minimal-tiptap-editor .ProseMirror li[data-type='taskItem'] .taskItem-checkbox-container { + @apply relative pr-2; +} + +.minimal-tiptap-editor .ProseMirror .taskItem-drag-handle { + @apply absolute -left-5 top-1.5 h-[18px] w-[18px] cursor-move pl-0.5 text-[var(--minimal-tiptap-secondary)] opacity-0; +} + +.minimal-tiptap-editor + .ProseMirror + li[data-type='taskItem']:hover:not(:has(li:hover)) + > .taskItem-checkbox-container + > .taskItem-drag-handle { + @apply opacity-100; +} + +.minimal-tiptap-editor .ProseMirror .taskItem-drag-handle:hover { + @apply text-[var(--minimal-tiptap-drag-handle-hover)]; +} + +.minimal-tiptap-editor .ProseMirror .taskItem-checkbox { + fill-opacity: 0; + @apply h-3.5 w-3.5 flex-shrink-0 cursor-pointer select-none appearance-none rounded border border-solid border-[var(--minimal-tiptap-secondary)] bg-transparent bg-[1px_2px] p-0.5 align-middle transition-colors duration-75 ease-out; +} + +.minimal-tiptap-editor .ProseMirror .taskItem-checkbox:checked { + @apply border-primary bg-primary bg-no-repeat; + background-image: url('data:image/svg+xml;utf8,%3Csvg%20width=%2210%22%20height=%229%22%20viewBox=%220%200%2010%208%22%20xmlns=%22http://www.w3.org/2000/svg%22%20fill=%22%23fbfbfb%22%3E%3Cpath%20d=%22M3.46975%205.70757L1.88358%204.1225C1.65832%203.8974%201.29423%203.8974%201.06897%204.1225C0.843675%204.34765%200.843675%204.7116%201.06897%204.93674L3.0648%206.93117C3.29006%207.15628%203.65414%207.15628%203.8794%206.93117L8.93103%201.88306C9.15633%201.65792%209.15633%201.29397%208.93103%201.06883C8.70578%200.843736%208.34172%200.843724%208.11646%201.06879C8.11645%201.0688%208.11643%201.06882%208.11642%201.06883L3.46975%205.70757Z%22%20stroke-width=%220.2%22%20/%3E%3C/svg%3E'); +} + +.minimal-tiptap-editor .ProseMirror .taskItem-content { + @apply min-w-0 flex-1; +} + +.minimal-tiptap-editor .ProseMirror li[data-checked='true'] .taskItem-content > :not([data-type='taskList']), +.minimal-tiptap-editor .ProseMirror li[data-checked='true'] .taskItem-content .taskItem-checkbox { + @apply opacity-75; +} diff --git a/web/src/components/rich-text-editor/styles/partials/placeholder.css b/web/src/components/rich-text-editor/styles/partials/placeholder.css new file mode 100644 index 000000000..eff7fefb7 --- /dev/null +++ b/web/src/components/rich-text-editor/styles/partials/placeholder.css @@ -0,0 +1,8 @@ +.minimal-tiptap-editor .ProseMirror .is-empty::before { + @apply pointer-events-none float-left h-0 w-full text-[var(--minimal-tiptap-secondary)]; +} + +.minimal-tiptap-editor .ProseMirror > p.is-editor-empty::before { + content: attr(data-placeholder); + @apply pointer-events-none float-left h-0 text-[var(--minimal-tiptap-secondary)]; +} diff --git a/web/src/components/rich-text-editor/styles/partials/typography.css b/web/src/components/rich-text-editor/styles/partials/typography.css new file mode 100644 index 000000000..f366220f8 --- /dev/null +++ b/web/src/components/rich-text-editor/styles/partials/typography.css @@ -0,0 +1,13 @@ +.minimal-tiptap-editor .ProseMirror h1, +.minimal-tiptap-editor .ProseMirror h2, +.minimal-tiptap-editor .ProseMirror h3 { + margin-top: 0; +} + +.minimal-tiptap-editor .ProseMirror a.link { + @apply cursor-pointer text-primary; +} + +.minimal-tiptap-editor .ProseMirror a.link:hover { + @apply underline; +} diff --git a/web/src/components/rich-text-editor/types.ts b/web/src/components/rich-text-editor/types.ts new file mode 100644 index 000000000..35cf226fa --- /dev/null +++ b/web/src/components/rich-text-editor/types.ts @@ -0,0 +1,34 @@ +import type { Editor } from '@tiptap/core'; +import type { EditorState } from '@tiptap/pm/state'; +import type { EditorView } from '@tiptap/pm/view'; + +export enum LinkType { + URL = 'url', + EMAIL = 'email', + TELEPHONE = 'telephone', +} + +export interface LinkProps { + url: string; + text?: string; + type: LinkType; +} + +export interface ShouldShowProps { + editor: Editor; + view: EditorView; + state: EditorState; + oldState?: EditorState; + from: number; + to: number; +} + +export interface FormatAction { + label: string; + icon?: React.ReactNode; + action: (editor: Editor) => void; + isActive: (editor: Editor) => boolean; + canExecute: (editor: Editor) => boolean; + shortcuts: string[]; + value: string; +} diff --git a/web/src/components/rich-text-editor/utils.ts b/web/src/components/rich-text-editor/utils.ts new file mode 100644 index 000000000..6add8c26b --- /dev/null +++ b/web/src/components/rich-text-editor/utils.ts @@ -0,0 +1,36 @@ +import type { Editor } from '@tiptap/core' +import { LinkProps, LinkType } from './types' + + +export function getOutput(editor: Editor, format: string) { + if (format === 'json') { + return JSON.stringify(editor.getJSON()) + } + + if (format === 'html') { + return editor.getText() ? String(editor.getHTML()) : '' + } + + return editor.getText() +} + +export function setLink(editor: Editor, { url, text, type }: LinkProps) { + editor + .chain() + .extendMarkRange('link') + .insertContent({ + type: 'text', + text: text || url, + marks: [ + { + type: 'link', + attrs: { + href: type === LinkType.EMAIL ? 'mailto:' + url : type === LinkType.TELEPHONE ? 'tel:' + url : url, + } + } + ] + }) + .setLink({ href: url }) + .focus() + .run() +} diff --git a/web/src/features/election-event/components/Guides/AddGuideForm.tsx b/web/src/features/election-event/components/Guides/AddGuideForm.tsx index 0f655a1a3..8dd8e81b7 100644 --- a/web/src/features/election-event/components/Guides/AddGuideForm.tsx +++ b/web/src/features/election-event/components/Guides/AddGuideForm.tsx @@ -1,12 +1,13 @@ import { authApi } from '@/common/auth-api'; import { FunctionComponent } from '@/common/types'; +import { RichTextEditor } from '@/components/rich-text-editor'; import { useConfirm } from '@/components/ui/alert-dialog-provider'; import { FileUploader } from '@/components/ui/file-uploader'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui/use-toast'; import { useCurrentElectionRoundStore } from '@/context/election-round.store'; +import { isNilOrWhitespace, isNotNilOrWhitespace } from '@/lib/utils'; import { queryClient } from '@/main'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation } from '@tanstack/react-query'; @@ -15,9 +16,8 @@ import { ReactNode, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { citizenGuidesKeys } from '../../hooks/citizen-guides-hooks'; -import { GuideModel, GuidePageType, GuideType } from '../../models/guide'; -import { isNilOrWhitespace, isNotNilOrWhitespace } from '@/lib/utils'; import { observerGuidesKeys } from '../../hooks/observer-guides-hooks'; +import { GuideModel, GuidePageType, GuideType } from '../../models/guide'; export interface AddGuideFormProps { guidePageType: GuidePageType; guideType: GuideType; @@ -191,7 +191,7 @@ export default function AddGuideForm({ ( + render={({ field, fieldState }) => ( Title * @@ -213,7 +213,7 @@ export default function AddGuideForm({ Guide url * - + @@ -256,7 +256,7 @@ export default function AddGuideForm({ Text * -