mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
ENH - improve mail experience and interactions
This commit is contained in:
@@ -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",
|
||||
|
||||
Generated
+551
@@ -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
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
<script setup lang="ts">
|
||||
import Placeholder from '@tiptap/extension-placeholder'
|
||||
import { Markdown } from '@tiptap/markdown'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||
import DOMPurify from 'dompurify'
|
||||
import {
|
||||
Bold,
|
||||
Italic,
|
||||
List,
|
||||
ListOrdered,
|
||||
Quote,
|
||||
Redo2,
|
||||
Undo2,
|
||||
} from 'lucide-vue-next'
|
||||
import { onBeforeUnmount, watch } from 'vue'
|
||||
|
||||
export type MailEditorLabels = {
|
||||
bold: string
|
||||
bulletList: string
|
||||
italic: string
|
||||
numberedList: string
|
||||
quote: string
|
||||
redo: string
|
||||
undo: string
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
editable?: boolean
|
||||
labels?: MailEditorLabels
|
||||
modelValue: string
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
editable: true,
|
||||
labels: () => ({
|
||||
bold: 'Bold',
|
||||
bulletList: 'Bullet list',
|
||||
italic: 'Italic',
|
||||
numberedList: 'Numbered list',
|
||||
quote: 'Quote',
|
||||
redo: 'Redo',
|
||||
undo: 'Undo',
|
||||
}),
|
||||
placeholder: '',
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
function safeMarkdown(value: string): string {
|
||||
return String(
|
||||
DOMPurify.sanitize(value.replace(/\r\n?/g, '\n'), {
|
||||
ALLOWED_ATTR: [],
|
||||
ALLOWED_TAGS: [],
|
||||
KEEP_CONTENT: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const editor = useEditor({
|
||||
content: safeMarkdown(props.modelValue),
|
||||
contentType: 'markdown',
|
||||
editable: props.editable,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
codeBlock: false,
|
||||
heading: { levels: [2, 3] },
|
||||
horizontalRule: false,
|
||||
link: {
|
||||
autolink: true,
|
||||
linkOnPaste: true,
|
||||
openOnClick: false,
|
||||
protocols: ['http', 'https'],
|
||||
},
|
||||
strike: false,
|
||||
underline: false,
|
||||
}),
|
||||
Markdown.configure({ markedOptions: { breaks: true, gfm: true } }),
|
||||
Placeholder.configure({ placeholder: props.placeholder }),
|
||||
],
|
||||
injectCSS: false,
|
||||
onUpdate: ({ editor: currentEditor }) => {
|
||||
emit('update:modelValue', currentEditor.getMarkdown())
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
if (!editor.value) return
|
||||
const safeValue = safeMarkdown(value)
|
||||
if (editor.value.getMarkdown() === safeValue) return
|
||||
editor.value.commands.setContent(safeValue, {
|
||||
contentType: 'markdown',
|
||||
emitUpdate: false,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.editable,
|
||||
(value) => editor.value?.setEditable(value),
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => editor.value?.destroy())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mail-editor" :class="{ 'mail-editor--readonly': !editable }">
|
||||
<div v-if="editable && editor" class="mail-editor__toolbar">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-active': editor.isActive('bold') }"
|
||||
:aria-label="labels.bold"
|
||||
:title="labels.bold"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
>
|
||||
<Bold :size="17" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-active': editor.isActive('italic') }"
|
||||
:aria-label="labels.italic"
|
||||
:title="labels.italic"
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
>
|
||||
<Italic :size="17" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-active': editor.isActive('bulletList') }"
|
||||
:aria-label="labels.bulletList"
|
||||
:title="labels.bulletList"
|
||||
@click="editor.chain().focus().toggleBulletList().run()"
|
||||
>
|
||||
<List :size="17" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-active': editor.isActive('orderedList') }"
|
||||
:aria-label="labels.numberedList"
|
||||
:title="labels.numberedList"
|
||||
@click="editor.chain().focus().toggleOrderedList().run()"
|
||||
>
|
||||
<ListOrdered :size="17" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ 'is-active': editor.isActive('blockquote') }"
|
||||
:aria-label="labels.quote"
|
||||
:title="labels.quote"
|
||||
@click="editor.chain().focus().toggleBlockquote().run()"
|
||||
>
|
||||
<Quote :size="17" />
|
||||
</button>
|
||||
<span class="mail-editor__toolbar-spacer" />
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!editor.can().chain().focus().undo().run()"
|
||||
:aria-label="labels.undo"
|
||||
:title="labels.undo"
|
||||
@click="editor.chain().focus().undo().run()"
|
||||
>
|
||||
<Undo2 :size="17" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!editor.can().chain().focus().redo().run()"
|
||||
:aria-label="labels.redo"
|
||||
:title="labels.redo"
|
||||
@click="editor.chain().focus().redo().run()"
|
||||
>
|
||||
<Redo2 :size="17" />
|
||||
</button>
|
||||
</div>
|
||||
<EditorContent v-if="editor" :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mail-editor {
|
||||
min-height: 210px;
|
||||
color: #f5f5f7;
|
||||
}
|
||||
|
||||
.mail-editor__toolbar {
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
min-height: 43px;
|
||||
padding: 5px 7px;
|
||||
border-bottom: 1px solid #ffffff14;
|
||||
background: #1c1c1ee8;
|
||||
backdrop-filter: blur(18px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(160%);
|
||||
}
|
||||
|
||||
.mail-editor__toolbar button {
|
||||
display: grid;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: #f5f5f7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mail-editor__toolbar button.is-active {
|
||||
background: #0a84ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mail-editor__toolbar button:disabled {
|
||||
opacity: 0.28;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mail-editor__toolbar-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:deep(.tiptap) {
|
||||
min-height: 210px;
|
||||
padding: 15px 16px 92px;
|
||||
outline: none;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.mail-editor--readonly {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mail-editor--readonly :deep(.tiptap) {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
:deep(.tiptap p) {
|
||||
margin: 0 0 0.8em;
|
||||
}
|
||||
|
||||
:deep(.tiptap p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.tiptap h2),
|
||||
:deep(.tiptap h3) {
|
||||
margin: 1em 0 0.4em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
:deep(.tiptap ul),
|
||||
:deep(.tiptap ol) {
|
||||
margin: 0.55em 0 0.85em;
|
||||
padding-left: 1.45em;
|
||||
}
|
||||
|
||||
:deep(.tiptap blockquote) {
|
||||
margin: 0.85em 0;
|
||||
padding-left: 0.9em;
|
||||
border-left: 3px solid #5e5e63;
|
||||
color: #a1a1a6;
|
||||
}
|
||||
|
||||
:deep(.tiptap a) {
|
||||
color: #0a84ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
:deep(.tiptap p.is-editor-empty:first-child::before) {
|
||||
float: left;
|
||||
height: 0;
|
||||
color: #8e8e93;
|
||||
content: attr(data-placeholder);
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -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',
|
||||
|
||||
@@ -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<script>')).toBe(
|
||||
'Alex@ifruit.comscript',
|
||||
)
|
||||
expect(filterMailAddressInput('sky.user_name-2@ifruit.com')).toBe(
|
||||
'sky.user_name-2@ifruit.com',
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps separators but filters recipient-list special characters', () => {
|
||||
expect(
|
||||
filterMailRecipientInput(
|
||||
'alex@ifruit.com, jamie+tag@ifruit.com; müller@ifruit.com',
|
||||
),
|
||||
).toBe('alex@ifruit.com, jamietag@ifruit.com; mller@ifruit.com')
|
||||
})
|
||||
|
||||
it('normalizes local parts and the iFruit domain', () => {
|
||||
expect(normalizeMailAddress(' Sky.User ')).toBe('sky.user@ifruit.com')
|
||||
expect(normalizeMailAddress('sky.user@ifruit.com')).toBe(
|
||||
@@ -48,12 +68,24 @@ describe('mail compose helpers', () => {
|
||||
expect(
|
||||
buildReplyDraft(message, 'alex@ifruit.com', true).recipients,
|
||||
).toEqual(['morgan@ifruit.com', 'jamie@ifruit.com'])
|
||||
expect(buildReplyDraft(message, 'alex@ifruit.com').body).toContain(
|
||||
'> Meet at Legion Square.',
|
||||
)
|
||||
})
|
||||
|
||||
it('builds forward content and avoids duplicate subject prefixes', () => {
|
||||
const forwarded = buildForwardDraft(message)
|
||||
expect(forwarded.recipients).toEqual([])
|
||||
expect(forwarded.subject).toBe('Fwd: Plans')
|
||||
expect(forwarded.body).toContain('From: morgan@ifruit.com')
|
||||
expect(forwarded.body).toContain('**From:** morgan@ifruit.com')
|
||||
})
|
||||
|
||||
it('turns formatted markdown into a compact mailbox preview', () => {
|
||||
expect(
|
||||
mailPlainText('## Update\n\n**Ready** [details](https://ifruit.com).'),
|
||||
).toBe('Update Ready details.')
|
||||
expect(mailPlainText('<script>alert(1)</script> Hello')).toBe(
|
||||
'alert(1) Hello',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
+1742
-331
File diff suppressed because it is too large
Load Diff
@@ -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 = ''"
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -23,13 +23,14 @@ const accountDevices = [
|
||||
]
|
||||
const messages = [
|
||||
{
|
||||
body: 'Welcome to iFruit Mail. Your shared mailbox is ready to use.',
|
||||
body: '## Welcome to iFruit Mail\n\nYour shared mailbox is **ready to use**.\n\n- Send formatted messages\n- Keep drafts on every linked device\n- Reply without losing the conversation\n\n> 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',
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sky Phone</title>
|
||||
<script type="module" crossorigin src="./assets/sky-index-D2k1S7R7.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/sky-index-G8JTwQWl.css">
|
||||
<script type="module" crossorigin src="./assets/sky-index-Bb3pn2fx.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/sky-index-0tsbAWVw.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user