diff --git a/frontend/package.json b/frontend/package.json index 944bab1..eeee9ad 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,13 @@ "test": "vitest run" }, "dependencies": { + "@tiptap/core": "^3.29.2", + "@tiptap/extension-placeholder": "^3.29.2", + "@tiptap/markdown": "^3.29.2", + "@tiptap/pm": "^3.29.2", + "@tiptap/starter-kit": "^3.29.2", + "@tiptap/vue-3": "^3.29.2", + "dompurify": "^3.4.13", "konsta": "~5.2.0", "lucide-vue-next": "^0.525.0", "pinia": "^3.0.3", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 3620d6d..99bf2de 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,27 @@ importers: .: dependencies: + '@tiptap/core': + specifier: ^3.29.2 + version: 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/extension-placeholder': + specifier: ^3.29.2 + version: 3.29.2(@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/markdown': + specifier: ^3.29.2 + version: 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/pm': + specifier: ^3.29.2 + version: 3.29.2 + '@tiptap/starter-kit': + specifier: ^3.29.2 + version: 3.29.2 + '@tiptap/vue-3': + specifier: ^3.29.2 + version: 3.29.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)(vue@3.5.40(typescript@5.8.3)) + dompurify: + specifier: ^3.4.13 + version: 3.4.13 konsta: specifier: ~5.2.0 version: 5.2.0 @@ -292,6 +313,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -333,6 +363,7 @@ packages: engines: {node: ^22.20 || ^24.12 || >=25} cpu: [x64] os: [linux] + libc: [glibc] '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -387,66 +418,79 @@ packages: resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.62.4': resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.62.4': resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.62.4': resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.62.4': resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.62.4': resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.62.4': resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.62.4': resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.62.4': resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.62.4': resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.62.4': resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.62.4': resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.62.4': resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.62.4': resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} @@ -516,24 +560,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.3': resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.3': resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.3': resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.3': resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} @@ -568,6 +616,165 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 + '@tiptap/core@3.29.2': + resolution: {integrity: sha512-oKUkiPUB7noilVYxI9lNzUD4rX17sHub+PYjMfHMWHG9A3nvIy+FdePIVIIhThKWF7ijhr3eIqHY51Bn+GAFtw==} + peerDependencies: + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-blockquote@3.29.2': + resolution: {integrity: sha512-ca4OzKDh0yaxg2+Z56bC2QnWsNsFp2YMRfVig1PDXyMVFMNJpLcnhxgq/9btn+xYAlYrj8RymOeCTYREOR6Zjg==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-bold@3.29.2': + resolution: {integrity: sha512-elYbGxJsYnBb4leqrcjdIJuiG380BcOgN+UUzvOv+qEjfGVzHodFOMBl3qnmD6urYHNu5/qQK2S0qSSRXKCLNQ==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-bubble-menu@3.29.2': + resolution: {integrity: sha512-kzcWarcpr031rZu+R3Hzut5uxb3Qj/xxMDEYZIQ3uiq1yb5vEMXj8/SIyWautk6Nu8Rr1leV3rGdR4NzhzyFAA==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-bullet-list@3.29.2': + resolution: {integrity: sha512-3bWcCUPbCHv0XttlMdnAtXLNYWx2pblByMgxmGsaP9FU0QnslGXty6A6gHCqI33ygRg1vrA6U5Wtpwbi5aKu5g==} + peerDependencies: + '@tiptap/extension-list': 3.29.2 + + '@tiptap/extension-code-block@3.29.2': + resolution: {integrity: sha512-w153ct8g6dLiPTdXQ6SOIMxX4SEo5Q50AmjdEEEcJ7ZcYUcde/ScSskLHfOYmyt5ZFAiyEwr121+pux+p3/oAQ==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-code@3.29.2': + resolution: {integrity: sha512-c6W5UGuB7WNLpYocsgRzpO2OOTI4QjaI9jjHRMuty9z+s9DtaYM/HrRLNwVh6MopkHb+i/89Wkv8gCS34fftig==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-document@3.29.2': + resolution: {integrity: sha512-YUamvefLnsqu6124GavVTI7nqcFlQJ12ROB0oSwG69eSBZYNjg1tIs05LFrBxkwf4Xgqd6YzfJ9+FeG428RvzQ==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-dropcursor@3.29.2': + resolution: {integrity: sha512-KKno7cU9r1HdR48CRrsDu69/1UjZdoslq/UcE+Kx+tdhAv/aljXMkRSNzGMrBNOBDmHRgS1+58zm21WQWdQzwA==} + peerDependencies: + '@tiptap/extensions': 3.29.2 + + '@tiptap/extension-floating-menu@3.29.2': + resolution: {integrity: sha512-CBTq4Xs5aGWr65uFCIkKBms796OhmirWf/ax//iJ4fl9IfwqjwFuc2vQs9PmuwpKn9ctDHo2sMTtvyeCvIt5LQ==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-gapcursor@3.29.2': + resolution: {integrity: sha512-8Q39UR4/Tit759IeW9xZIe3NMwN11GsuA3FLheDyyGn7RrW02HD3HhUDlazE54Ki4HoosjFmChPlN4Ik2ubdRQ==} + peerDependencies: + '@tiptap/extensions': 3.29.2 + + '@tiptap/extension-hard-break@3.29.2': + resolution: {integrity: sha512-eUW3LN3fq8rXnjEUeI3D2QONYdLsU3yYQm4jxlErs2h4cfwrFjgf19VSUFVmm6LrFbbQ0OnDVPeVLL6iOwDw2w==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-heading@3.29.2': + resolution: {integrity: sha512-6W4aIy70Mh7BNlbG9zZ5FBLhJhU2UUEzgZJ/jwYSCcB30o8McLxJSEjhtoHiX8R78Ah2/JzBGvIe5olZlbeE4A==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-horizontal-rule@3.29.2': + resolution: {integrity: sha512-8/ZPzbB9X85Mc9/7xVLZupQKBr2UVcQTGr512xtqMW+XkCQRHCph46tRo828YE13IMWI5fWn/FaNCqXG9cULSw==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-italic@3.29.2': + resolution: {integrity: sha512-iH63V/5wsaMnY4Jz0+meaAGhaec4AiOzOduzl6ZZr5IyGhZ1kthyW84ELt0dyLI3hNceAUhaNWc+I7+vX0aoXA==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-link@3.29.2': + resolution: {integrity: sha512-DcVer5SqrexKCEP6Ip1UPxJUMvcRCCItSv0wxoGytanrimBh2smvcg6X0DWnjlsi5H0updhyl+atYCmmQXUIXA==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-list-item@3.29.2': + resolution: {integrity: sha512-s8vBVHHFT0Qpu7CzAZ7S1kYmSiVaDvUNvMNcZUWnxj6VPfiwmx0eXd9FsjePrRCMoM5tFmPnhFTHDxY3D/eZeQ==} + peerDependencies: + '@tiptap/extension-list': 3.29.2 + + '@tiptap/extension-list-keymap@3.29.2': + resolution: {integrity: sha512-R+3k8OLnxdCH7Xy9ieOwUt5m2Je74u8mikothGmsYVO2Zyq48fIbmZ+X6RBPCu7DBOI2FIUhHEFbKQeDWvDNmA==} + peerDependencies: + '@tiptap/extension-list': 3.29.2 + + '@tiptap/extension-list@3.29.2': + resolution: {integrity: sha512-WPZ9BHAPT6QeIm1vdVkuoOWvy9a8/EZeJwV2VhU8LXyTAttvzyj4rsbbHyJWvYWlUSTt/QF2AZ2zhKo7u1w3/A==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-ordered-list@3.29.2': + resolution: {integrity: sha512-ndCunC+UsYOpkOtL7vGnDz21UNa45WUlcO9wMT1fbuYow2QnRhsuMlCWENXI52YPPARWuQ0RDgN7q6TaxPERBg==} + peerDependencies: + '@tiptap/extension-list': 3.29.2 + + '@tiptap/extension-paragraph@3.29.2': + resolution: {integrity: sha512-7qJj5YTr11vvjNgjDN1ypOfwTovc0QOCYcit/rskeuVgnmQZOZQzC/BbyKLLG7UGnpRLemU/mEGbW9pAqjAXkQ==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-placeholder@3.29.2': + resolution: {integrity: sha512-/BlpPIq2JZk6zLaeYVOXazi6dmd0iRriTymNEnFn1doManN1y5HED9LsMeb/AhNhauL5AgmcHgpvAjbsN9k4SA==} + peerDependencies: + '@tiptap/extensions': 3.29.2 + + '@tiptap/extension-strike@3.29.2': + resolution: {integrity: sha512-aEvLAbddUQZ+FukCreV3q4G2HfNI+odE7E9U+wbq6XsSWKyo8/pDu1muz+TFKNre4blSMOQ3JQmw5UeHDKy+fg==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-text@3.29.2': + resolution: {integrity: sha512-Ubko45JWWHe8glBt2PiGNF8hcbys/JNalFhiR7Y1X4iOOtAxAKJJxh3+eq+//NTlGuBPdWGp7zw8EEUp7anjKA==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extension-underline@3.29.2': + resolution: {integrity: sha512-K7XwH/xS/5AIREWQ00VTEf/W5U0olp7j6wwit7cdd/8nHv6h6AGr1+iEApHKoLXWQZLfGQKzJlT9W61LAl+fHA==} + peerDependencies: + '@tiptap/core': 3.29.2 + + '@tiptap/extensions@3.29.2': + resolution: {integrity: sha512-BCz+FCAChSYtUe4BFj97HEO+nSK+J7GxbJgZG4Hg7DT/gI+hRyeNndU8efiQAx3WGdzsFi3UxRpcF1tTQM7iMQ==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/markdown@3.29.2': + resolution: {integrity: sha512-Knf9YqG3fxOyFuS3pe3eapDz0ya9eqPGzmyMSd5PuHNmR9/Ir6i5qk5+Md6bCPgDNjYskztNm7DoBXPfbXbO/w==} + peerDependencies: + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + + '@tiptap/pm@3.29.2': + resolution: {integrity: sha512-GCOme7xHaS+DSoaA4CDcAD3l6JyBlvZhvCyfsy2Vp6j8tEoBkZWio7soYVosmlyn7zq8/64VeFZP5s47yfG7fQ==} + + '@tiptap/starter-kit@3.29.2': + resolution: {integrity: sha512-oTu0tysiqk4zgjEtxRHjAQgxUKaAevZwueOWwSWubHdokqp7SpcbE5n9USJv89HKuTUDm3GjnQH6q8HNn/2DsA==} + + '@tiptap/vue-3@3.29.2': + resolution: {integrity: sha512-rLX+V+HXbsVlk+My8mslvbnbVXZiQZHYtbOiRgxugsqZ9SK1mr1FpgZ/ZNcotJAjecT8FJmPa45ccDnVu66eGA==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.29.2 + '@tiptap/pm': 3.29.2 + vue: ^3.0.0 + '@tsconfig/node22@22.0.5': resolution: {integrity: sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==} @@ -589,6 +796,9 @@ packages: '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@typescript-eslint/eslint-plugin@8.65.0': resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -956,6 +1166,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dompurify@3.4.13: + resolution: {integrity: sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1348,24 +1561,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -1383,6 +1600,9 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1402,6 +1622,11 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + marked@17.0.6: + resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1484,6 +1709,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1565,6 +1793,45 @@ packages: engines: {node: '>=14'} hasBin: true + prosemirror-changeset@2.4.1: + resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} + + prosemirror-commands@1.7.2: + resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} + + prosemirror-dropcursor@1.8.3: + resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-model@1.25.11: + resolution: {integrity: sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-transform@1.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.42.2: + resolution: {integrity: sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -1608,6 +1875,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -1908,6 +2178,9 @@ packages: typescript: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2088,6 +2361,17 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -2285,6 +2569,182 @@ snapshots: tailwindcss: 4.3.3 vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + '@tiptap/core@3.29.2(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-blockquote@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-bold@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-bubble-menu@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + optional: true + + '@tiptap/extension-bullet-list@3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extension-list': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-code-block@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-code@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-document@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-dropcursor@3.29.2(@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extensions': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-floating-menu@3.29.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + optional: true + + '@tiptap/extension-gapcursor@3.29.2(@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extensions': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-hard-break@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-heading@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-horizontal-rule@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-italic@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-link@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extension-list': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-list-keymap@3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extension-list': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + + '@tiptap/extension-ordered-list@3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extension-list': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-paragraph@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-placeholder@3.29.2(@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/extensions': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + + '@tiptap/extension-strike@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-text@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extension-underline@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + + '@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + + '@tiptap/markdown@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + marked: 17.0.6 + + '@tiptap/pm@3.29.2': + dependencies: + prosemirror-changeset: 2.4.1 + prosemirror-commands: 1.7.2 + prosemirror-dropcursor: 1.8.3 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + '@tiptap/starter-kit@3.29.2': + dependencies: + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/extension-blockquote': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/extension-bold': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-bullet-list': 3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/extension-code': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-code-block': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/extension-document': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-dropcursor': 3.29.2(@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/extension-gapcursor': 3.29.2(@tiptap/extensions@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/extension-hard-break': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-heading': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-horizontal-rule': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/extension-italic': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-link': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/extension-list': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/extension-list-item': 3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/extension-list-keymap': 3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/extension-ordered-list': 3.29.2(@tiptap/extension-list@3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)) + '@tiptap/extension-paragraph': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-strike': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-text': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extension-underline': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2)) + '@tiptap/extensions': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + + '@tiptap/vue-3@3.29.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2)(vue@3.5.40(typescript@5.8.3))': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.29.2(@tiptap/pm@3.29.2) + '@tiptap/pm': 3.29.2 + vue: 3.5.40(typescript@5.8.3) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.29.2(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tiptap/extension-floating-menu': 3.29.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.29.2(@tiptap/pm@3.29.2))(@tiptap/pm@3.29.2) + '@tsconfig/node22@22.0.5': {} '@types/chai@5.2.3': @@ -2304,6 +2764,9 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/trusted-types@2.0.7': + optional: true + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -2740,6 +3203,10 @@ snapshots: detect-libc@2.1.2: {} + dompurify@3.4.13: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3183,6 +3650,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + linkifyjs@4.3.3: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3199,6 +3668,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + marked@17.0.6: {} + math-intrinsics@1.1.0: {} media-typer@1.1.1: {} @@ -3267,6 +3738,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3327,6 +3800,80 @@ snapshots: prettier@3.5.3: {} + prosemirror-changeset@2.4.1: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-commands@1.7.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.3: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-view: 1.42.2 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-model@1.25.11: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.11 + + prosemirror-view@1.42.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -3390,6 +3937,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.4 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + router@2.2.0: dependencies: debug: 4.4.3 @@ -3700,6 +4249,8 @@ snapshots: optionalDependencies: typescript: 5.8.3 + w3c-keyname@2.2.8: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/frontend/src/components/MailMarkdownEditor.vue b/frontend/src/components/MailMarkdownEditor.vue new file mode 100644 index 0000000..3e3cb33 --- /dev/null +++ b/frontend/src/components/MailMarkdownEditor.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index 0d0f8b5..0ea66e3 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -281,6 +281,9 @@ const defaultLocales: LocaleTree = { email: 'Email', password: 'Password', confirmPassword: 'Confirm password', + accountEyebrow: 'Your iFruit account', + emailPlaceholder: 'name@ifruit.com', + passwordPlaceholder: 'Enter password', passwordWarning: 'Use an in-character password. Do not reuse a real-world password.', mailboxes: 'Mailboxes', @@ -291,9 +294,14 @@ const defaultLocales: LocaleTree = { compose: 'New Message', recipients: 'To', recipientHint: 'Separate up to 10 addresses with commas.', + recipientPlaceholder: 'name@ifruit.com', subject: 'Subject', + subjectPlaceholder: "What's this about?", body: 'Message', + messagePlaceholder: 'Write a message...', search: 'Search mail', + messages: 'messages', + noMessageContent: 'No message content', noMail: 'No Mail', noMailBody: 'Messages in this mailbox will appear here.', noResults: 'No Results', @@ -306,7 +314,16 @@ const defaultLocales: LocaleTree = { reply: 'Reply', replyAll: 'Reply All', forward: 'Forward', + formatBold: 'Bold', + formatItalic: 'Italic', + formatBulletList: 'Bullet list', + formatNumberedList: 'Numbered list', + formatQuote: 'Quote', + undo: 'Undo', + redo: 'Redo', + markRead: 'Read', markUnread: 'Mark as Unread', + delete: 'Delete', moveToTrash: 'Move to Trash', restore: 'Restore', deleteForever: 'Delete Forever', diff --git a/frontend/src/utils/mail.test.ts b/frontend/src/utils/mail.test.ts index 33745bf..11acb1e 100644 --- a/frontend/src/utils/mail.test.ts +++ b/frontend/src/utils/mail.test.ts @@ -4,6 +4,9 @@ import type { MailMessage } from '@/types/mail' import { buildForwardDraft, buildReplyDraft, + filterMailAddressInput, + filterMailRecipientInput, + mailPlainText, normalizeMailAddress, parseMailRecipients, } from '@/utils/mail' @@ -22,6 +25,23 @@ const message: MailMessage = { } describe('mail addresses', () => { + it('filters account fields to the supported email character set', () => { + expect(filterMailAddressInput('Al+ex! ä@ifruit.com Hello')).toBe( + 'alert(1) Hello', + ) }) }) diff --git a/frontend/src/utils/mail.ts b/frontend/src/utils/mail.ts index fb044de..c68d4e9 100644 --- a/frontend/src/utils/mail.ts +++ b/frontend/src/utils/mail.ts @@ -2,6 +2,21 @@ import type { MailComposeDraft, MailMessage } from '@/types/mail' export const MAIL_DOMAIN = 'ifruit.com' export const MAIL_MAX_RECIPIENTS = 10 +export const MAIL_ADDRESS_INPUT_MAX_LENGTH = 64 +export const MAIL_RECIPIENT_INPUT_MAX_LENGTH = + MAIL_MAX_RECIPIENTS * (MAIL_ADDRESS_INPUT_MAX_LENGTH + 2) + +export function filterMailAddressInput(value: string): string { + return value + .replace(/[^a-z0-9@._-]/gi, '') + .slice(0, MAIL_ADDRESS_INPUT_MAX_LENGTH) +} + +export function filterMailRecipientInput(value: string): string { + return value + .replace(/[^a-z0-9@._,; -]/gi, '') + .slice(0, MAIL_RECIPIENT_INPUT_MAX_LENGTH) +} export function normalizeMailAddress(value: string): string | null { const normalized = value.trim().toLocaleLowerCase('en-US') @@ -43,6 +58,25 @@ export function parseMailRecipients(value: string): string[] | null { return recipients } +export function mailPlainText(value: string): string { + return value + .replace(/<[^>]*>/g, ' ') + .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') + .replace(/^\s{0,3}(?:#{1,6}|>|[-+*]|\d+\.)\s+/gm, '') + .replace(/[*_~`]/g, '') + .replace(/\s+/g, ' ') + .trim() +} + +function quoteMarkdown(value: string): string { + return value + .trim() + .split('\n') + .map((line) => `> ${line}`) + .join('\n') +} + function prefixedSubject(prefix: 'Re' | 'Fwd', subject: string): string { const clean = subject.trim() if (new RegExp(`^${prefix}:`, 'i').test(clean)) return clean @@ -62,7 +96,7 @@ export function buildReplyDraft( ) return { - body: `\n--- ${message.sender} ---\n${message.body}`, + body: `\n\n> **${message.sender}**\n>\n${quoteMarkdown(message.body)}`, recipients, subject: prefixedSubject('Re', message.subject), } @@ -70,7 +104,7 @@ export function buildReplyDraft( export function buildForwardDraft(message: MailMessage): MailComposeDraft { return { - body: `\n--- Forwarded message ---\nFrom: ${message.sender}\nTo: ${message.recipients.join(', ')}\n\n${message.body}`, + body: `\n\n---\n\n**Forwarded message**\n\n**From:** ${message.sender}\n\n**To:** ${message.recipients.join(', ')}\n\n${message.body}`, recipients: [], subject: prefixedSubject('Fwd', message.subject), } diff --git a/frontend/src/utils/mailSwipe.test.ts b/frontend/src/utils/mailSwipe.test.ts new file mode 100644 index 0000000..76172be --- /dev/null +++ b/frontend/src/utils/mailSwipe.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' + +import { + clampMailSwipeOffset, + MAIL_SWIPE_ACTION_WIDTH, + resolveMailSwipeAction, + resolveMailSwipeAxis, +} from '@/utils/mailSwipe' + +describe('mail swipe gestures', () => { + it('waits for a clear horizontal or vertical direction', () => { + expect(resolveMailSwipeAxis(5, 2)).toBeNull() + expect(resolveMailSwipeAxis(24, 5)).toBe('horizontal') + expect(resolveMailSwipeAxis(10, 18)).toBe('vertical') + }) + + it('limits horizontal movement and blocks unavailable directions', () => { + expect(clampMailSwipeOffset(70, true, true)).toBe(70) + expect(clampMailSwipeOffset(70, false, true)).toBe(0) + expect(clampMailSwipeOffset(-70, true, false)).toBe(0) + expect(clampMailSwipeOffset(-240, true, true)).toBeGreaterThan( + -MAIL_SWIPE_ACTION_WIDTH - 19, + ) + }) + + it('only commits an action after crossing its threshold', () => { + expect(resolveMailSwipeAction(63, true, true)).toBeNull() + expect(resolveMailSwipeAction(64, true, true)).toBe('read') + expect(resolveMailSwipeAction(-64, true, true)).toBe('delete') + expect(resolveMailSwipeAction(80, false, true)).toBeNull() + }) +}) diff --git a/frontend/src/utils/mailSwipe.ts b/frontend/src/utils/mailSwipe.ts new file mode 100644 index 0000000..789c0c0 --- /dev/null +++ b/frontend/src/utils/mailSwipe.ts @@ -0,0 +1,48 @@ +export type MailSwipeAction = 'delete' | 'read' +export type MailSwipeAxis = 'horizontal' | 'vertical' + +export const MAIL_SWIPE_ACTION_WIDTH = 84 +export const MAIL_SWIPE_AXIS_LOCK = 8 +export const MAIL_SWIPE_TRIGGER = 64 + +export function resolveMailSwipeAxis( + deltaX: number, + deltaY: number, +): MailSwipeAxis | null { + const horizontalDistance = Math.abs(deltaX) + const verticalDistance = Math.abs(deltaY) + if (Math.max(horizontalDistance, verticalDistance) < MAIL_SWIPE_AXIS_LOCK) { + return null + } + + return horizontalDistance > verticalDistance * 1.15 + ? 'horizontal' + : 'vertical' +} + +export function clampMailSwipeOffset( + deltaX: number, + canRead: boolean, + canDelete: boolean, +): number { + if ((deltaX > 0 && !canRead) || (deltaX < 0 && !canDelete)) return 0 + + const direction = Math.sign(deltaX) + const distance = Math.abs(deltaX) + const resistedDistance = + distance <= MAIL_SWIPE_ACTION_WIDTH + ? distance + : MAIL_SWIPE_ACTION_WIDTH + (distance - MAIL_SWIPE_ACTION_WIDTH) * 0.18 + + return direction * Math.min(resistedDistance, MAIL_SWIPE_ACTION_WIDTH + 18) +} + +export function resolveMailSwipeAction( + offset: number, + canRead: boolean, + canDelete: boolean, +): MailSwipeAction | null { + if (canRead && offset >= MAIL_SWIPE_TRIGGER) return 'read' + if (canDelete && offset <= -MAIL_SWIPE_TRIGGER) return 'delete' + return null +} diff --git a/frontend/src/views/apps/MailApp.vue b/frontend/src/views/apps/MailApp.vue index ac825d9..e716764 100644 --- a/frontend/src/views/apps/MailApp.vue +++ b/frontend/src/views/apps/MailApp.vue @@ -1,35 +1,30 @@ + + diff --git a/frontend/src/views/apps/SettingsApp.vue b/frontend/src/views/apps/SettingsApp.vue index ec4ea41..6ece15c 100644 --- a/frontend/src/views/apps/SettingsApp.vue +++ b/frontend/src/views/apps/SettingsApp.vue @@ -48,6 +48,10 @@ import { IFRUIT_AUTH_INPUT_COLORS } from '@/config/ifruit' import { usePhoneStore } from '@/stores/phone' import { useAccountStore } from '@/stores/account' import type { PhoneAppDefinition, PhoneAppId } from '@/types/apps' +import { + filterMailAddressInput, + MAIL_ADDRESS_INPUT_MAX_LENGTH, +} from '@/utils/mail' import { nuiCall } from '@/utils/nui' import { formatPhoneNumber } from '@/utils/phone' import { @@ -320,6 +324,23 @@ function eventValue(event: Event): string { return (event.target as HTMLInputElement).value } +function updateAccountEmail(event: Event): void { + const input = event.target as HTMLInputElement + const original = input.value + const selectionStart = input.selectionStart ?? original.length + const filtered = filterMailAddressInput(original) + + if (filtered !== original) { + const nextSelection = filterMailAddressInput( + original.slice(0, selectionStart), + ).length + input.value = filtered + input.setSelectionRange(nextSelection, nextSelection) + } + + accountEmail.value = filtered +} + function accountError(error?: string): string { const known = [ 'invalid_email', @@ -587,9 +608,13 @@ onBeforeUnmount(() => { :input-class="accountMode === 'register' ? 'pr-20' : undefined" autocomplete="username" autocapitalize="none" + autocorrect="off" + inputmode="email" + :maxlength="MAIL_ADDRESS_INPUT_MAX_LENGTH" + pattern="[A-Za-z0-9@._-]*" spellcheck="false" :clear-button="accountMode === 'login'" - @input="accountEmail = eventValue($event)" + @input="updateAccountEmail" @clear="accountEmail = ''" > Tip: use the small formatting bar while composing.', created_at: '2026-08-04 11:30:00', folder: 'inbox', id: 1, is_read: false, message_id: 'b73f3872-54cc-4d74-a058-6caa24f0dfff', - preview: 'Welcome to iFruit Mail. Your shared mailbox is ready to use.', + preview: + 'Your shared mailbox is **ready to use**. Send formatted messages and keep drafts on every linked device.', recipients: ['demo@ifruit.com'], sender: 'support@ifruit.com', subject: 'Welcome to iFruit Mail', diff --git a/sky_phone/config/locales/en.lua b/sky_phone/config/locales/en.lua index 55f86e2..748f4cb 100644 --- a/sky_phone/config/locales/en.lua +++ b/sky_phone/config/locales/en.lua @@ -89,14 +89,19 @@ Locales["en"] = { name = "Mail", login = "Sign In", register = "Create Account", registerLink = "Register", loginTitle = "iFruit Mail", loginBody = "Sign in to use your shared iFruit mailbox.", registerBody = "Choose your new @ifruit.com address.", localPart = "Email address", email = "Email", password = "Password", confirmPassword = "Confirm password", + accountEyebrow = "Your iFruit account", emailPlaceholder = "name@ifruit.com", passwordPlaceholder = "Enter password", passwordWarning = "Use an in-character password. Do not reuse a real-world password.", mailboxes = "Mailboxes", inbox = "Inbox", sent = "Sent", drafts = "Drafts", trash = "Trash", compose = "New Message", recipients = "To", recipientHint = "Separate up to 10 addresses with commas.", - subject = "Subject", body = "Message", search = "Search mail", noMail = "No Mail", + recipientPlaceholder = "name@ifruit.com", subject = "Subject", subjectPlaceholder = "What's this about?", + body = "Message", messagePlaceholder = "Write a message...", search = "Search mail", messages = "messages", + noMessageContent = "No message content", noMail = "No Mail", noMailBody = "Messages in this mailbox will appear here.", noResults = "No Results", noResultsBody = "Try a different search.", untitled = "(no subject)", loadMore = "Load More", logout = "Sign Out", from = "From", to = "To", reply = "Reply", replyAll = "Reply All", forward = "Forward", - markUnread = "Mark as Unread", moveToTrash = "Move to Trash", restore = "Restore", + formatBold = "Bold", formatItalic = "Italic", formatBulletList = "Bullet list", + formatNumberedList = "Numbered list", formatQuote = "Quote", undo = "Undo", redo = "Redo", + markRead = "Read", markUnread = "Mark as Unread", delete = "Delete", moveToTrash = "Move to Trash", restore = "Restore", deleteForever = "Delete Forever", emptyTrash = "Empty Trash", emptyTrashTitle = "Empty Trash?", emptyTrashBody = "Every message in Trash will be permanently deleted.", deleteDraft = "Delete Draft", sentSuccess = "Message sent.", newMessage = "New mail from {sender}", passwordsMismatch = "Passwords do not match.", diff --git a/sky_phone/source/html/index.html b/sky_phone/source/html/index.html index 2d66aff..40ad1f5 100644 --- a/sky_phone/source/html/index.html +++ b/sky_phone/source/html/index.html @@ -4,8 +4,8 @@ Sky Phone - - + +
diff --git a/sky_phone/source/server/mail.lua b/sky_phone/source/server/mail.lua index 8fa959d..f68a9e6 100644 --- a/sky_phone/source/server/mail.lua +++ b/sky_phone/source/server/mail.lua @@ -373,7 +373,6 @@ Bridge.Callbacks.Register("sky_phone:mail:delete-draft", function(source, data) broadcast_mailbox_changed(session.id) return { success = true } end) -end) Bridge.Callbacks.Register("sky_phone:mail:send", function(source, data) local session, error_response = require_session(source) @@ -581,3 +580,4 @@ Bridge.Callbacks.Register("sky_phone:mail:empty-trash", function(source) broadcast_mailbox_changed(session.id) return { success = true } end) +end)