your personal website on atproto - mirror blento.app

blog

Florian dc5f8040 92c22924

+2307 -255
+18 -8
package.json
··· 51 51 "@atcute/tid": "^1.1.1", 52 52 "@cloudflare/workers-types": "^4.20260123.0", 53 53 "@ethercorps/sveltekit-og": "^4.2.1", 54 + "@floating-ui/dom": "^1.7.5", 54 55 "@foxui/3d": "^0.4.7", 55 56 "@foxui/colors": "^0.4.7", 56 57 "@foxui/core": "^0.4.7", ··· 61 62 "@tailwindcss/typography": "^0.5.19", 62 63 "@threlte/core": "^8.3.1", 63 64 "@threlte/extras": "^9.7.1", 64 - "@tiptap/core": "^3.16.0", 65 - "@tiptap/extension-document": "^3.16.0", 66 - "@tiptap/extension-image": "^3.16.0", 67 - "@tiptap/extension-link": "^3.16.0", 68 - "@tiptap/extension-paragraph": "^3.16.0", 69 - "@tiptap/extension-placeholder": "^3.16.0", 70 - "@tiptap/extension-text": "^3.16.0", 71 - "@tiptap/starter-kit": "^3.16.0", 65 + "@tiptap/core": "^3.19.0", 66 + "@tiptap/extension-bubble-menu": "^3.19.0", 67 + "@tiptap/extension-code-block-lowlight": "^3.19.0", 68 + "@tiptap/extension-document": "^3.19.0", 69 + "@tiptap/extension-image": "^3.19.0", 70 + "@tiptap/extension-link": "^3.19.0", 71 + "@tiptap/extension-paragraph": "^3.19.0", 72 + "@tiptap/extension-placeholder": "^3.19.0", 73 + "@tiptap/extension-text": "^3.19.0", 74 + "@tiptap/extension-typography": "^3.19.0", 75 + "@tiptap/extension-underline": "^3.19.0", 76 + "@tiptap/markdown": "^3.19.0", 77 + "@tiptap/pm": "^3.19.0", 78 + "@tiptap/starter-kit": "^3.19.0", 79 + "@tiptap/suggestion": "^3.19.0", 72 80 "@types/three": "^0.176.0", 73 81 "bits-ui": "^2.15.4", 74 82 "clsx": "^2.1.1", ··· 77 85 "hls.js": "^1.6.15", 78 86 "leaflet": "^1.9.4", 79 87 "link-preview-js": "^4.0.0", 88 + "lowlight": "^3.3.0", 80 89 "maplibre-gl": "^5.17.0", 81 90 "marked": "^17.0.1", 82 91 "perfect-freehand": "^1.2.2", ··· 86 95 "simple-icons": "^16.6.0", 87 96 "svelte-boring-avatars": "^1.2.6", 88 97 "svelte-sonner": "^1.0.7", 98 + "svelte-tiptap": "^3.0.1", 89 99 "tailwind-merge": "^3.4.0", 90 100 "tailwind-variants": "^3.2.2", 91 101 "tailwindcss-animate": "^1.0.7",
+383 -219
pnpm-lock.yaml
··· 41 41 '@ethercorps/sveltekit-og': 42 42 specifier: ^4.2.1 43 43 version: 4.2.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))) 44 + '@floating-ui/dom': 45 + specifier: ^1.7.5 46 + version: 1.7.5 44 47 '@foxui/3d': 45 48 specifier: ^0.4.7 46 49 version: 0.4.7(svelte@5.48.0)(tailwindcss@4.1.18) ··· 72 75 specifier: ^9.7.1 73 76 version: 9.7.1(@types/three@0.176.0)(svelte@5.48.0)(three@0.176.0) 74 77 '@tiptap/core': 75 - specifier: ^3.16.0 76 - version: 3.16.0(@tiptap/pm@3.16.0) 78 + specifier: ^3.19.0 79 + version: 3.19.0(@tiptap/pm@3.19.0) 80 + '@tiptap/extension-bubble-menu': 81 + specifier: ^3.19.0 82 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 83 + '@tiptap/extension-code-block-lowlight': 84 + specifier: ^3.19.0 85 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/extension-code-block@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)(highlight.js@11.11.1)(lowlight@3.3.0) 77 86 '@tiptap/extension-document': 78 - specifier: ^3.16.0 79 - version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 87 + specifier: ^3.19.0 88 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 80 89 '@tiptap/extension-image': 81 - specifier: ^3.16.0 82 - version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 90 + specifier: ^3.19.0 91 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 83 92 '@tiptap/extension-link': 84 - specifier: ^3.16.0 85 - version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 93 + specifier: ^3.19.0 94 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 86 95 '@tiptap/extension-paragraph': 87 - specifier: ^3.16.0 88 - version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 96 + specifier: ^3.19.0 97 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 89 98 '@tiptap/extension-placeholder': 90 - specifier: ^3.16.0 91 - version: 3.16.0(@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 99 + specifier: ^3.19.0 100 + version: 3.19.0(@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 92 101 '@tiptap/extension-text': 93 - specifier: ^3.16.0 94 - version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 102 + specifier: ^3.19.0 103 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 104 + '@tiptap/extension-typography': 105 + specifier: ^3.19.0 106 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 107 + '@tiptap/extension-underline': 108 + specifier: ^3.19.0 109 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 110 + '@tiptap/markdown': 111 + specifier: ^3.19.0 112 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 113 + '@tiptap/pm': 114 + specifier: ^3.19.0 115 + version: 3.19.0 95 116 '@tiptap/starter-kit': 96 - specifier: ^3.16.0 97 - version: 3.16.0 117 + specifier: ^3.19.0 118 + version: 3.19.0 119 + '@tiptap/suggestion': 120 + specifier: ^3.19.0 121 + version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 98 122 '@types/three': 99 123 specifier: ^0.176.0 100 124 version: 0.176.0 ··· 119 143 link-preview-js: 120 144 specifier: ^4.0.0 121 145 version: 4.0.0 146 + lowlight: 147 + specifier: ^3.3.0 148 + version: 3.3.0 122 149 maplibre-gl: 123 150 specifier: ^5.17.0 124 151 version: 5.17.0 ··· 146 173 svelte-sonner: 147 174 specifier: ^1.0.7 148 175 version: 1.0.7(svelte@5.48.0) 176 + svelte-tiptap: 177 + specifier: ^3.0.1 178 + version: 3.0.1(@floating-ui/dom@1.7.5)(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/extension-bubble-menu@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))(@tiptap/extension-floating-menu@3.19.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)(svelte@5.48.0) 149 179 tailwind-merge: 150 180 specifier: ^3.4.0 151 181 version: 3.4.0 ··· 736 766 '@floating-ui/core@1.7.3': 737 767 resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} 738 768 739 - '@floating-ui/dom@1.7.4': 740 - resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} 769 + '@floating-ui/core@1.7.4': 770 + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==, tarball: https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz} 771 + 772 + '@floating-ui/dom@1.7.5': 773 + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==, tarball: https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz} 741 774 742 775 '@floating-ui/utils@0.2.10': 743 776 resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} ··· 1011 1044 resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} 1012 1045 1013 1046 '@remirror/core-constants@3.0.0': 1014 - resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} 1047 + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==, tarball: https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz} 1015 1048 1016 1049 '@resvg/resvg-wasm@2.6.2': 1017 1050 resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} ··· 1383 1416 svelte: '>=5' 1384 1417 three: '>=0.160' 1385 1418 1386 - '@tiptap/core@3.16.0': 1387 - resolution: {integrity: sha512-XegRaNuoQ/guzBQU2xHxOwFXXrtoXW9tiyXDhssSqylvZmBVSlRIPNHA6ArkHBKm6ehLf6+6Y9fF3uky1yCXYQ==} 1419 + '@tiptap/core@3.19.0': 1420 + resolution: {integrity: sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==, tarball: https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz} 1421 + peerDependencies: 1422 + '@tiptap/pm': ^3.19.0 1423 + 1424 + '@tiptap/extension-blockquote@3.19.0': 1425 + resolution: {integrity: sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A==, tarball: https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.19.0.tgz} 1388 1426 peerDependencies: 1389 - '@tiptap/pm': ^3.16.0 1427 + '@tiptap/core': ^3.19.0 1428 + 1429 + '@tiptap/extension-bold@3.19.0': 1430 + resolution: {integrity: sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q==, tarball: https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz} 1431 + peerDependencies: 1432 + '@tiptap/core': ^3.19.0 1390 1433 1391 - '@tiptap/extension-blockquote@3.16.0': 1392 - resolution: {integrity: sha512-c1bhJ3KDFXyNcMweiBzu0LouBXfUC/sUMtaEafQePR98BVu+d0tmWXcGlfVarGVoRyCYFa1mHpkgtxp4SS3lag==} 1434 + '@tiptap/extension-bubble-menu@3.19.0': 1435 + resolution: {integrity: sha512-klNVIYGCdznhFkrRokzGd6cwzoi8J7E5KbuOfZBwFwhMKZhlz/gJfKmYg9TJopeUhrr2Z9yHgWTk8dh/YIJCdQ==, tarball: https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.19.0.tgz} 1393 1436 peerDependencies: 1394 - '@tiptap/core': ^3.16.0 1437 + '@tiptap/core': ^3.19.0 1438 + '@tiptap/pm': ^3.19.0 1439 + 1440 + '@tiptap/extension-bullet-list@3.19.0': 1441 + resolution: {integrity: sha512-F9uNnqd0xkJbMmRxVI5RuVxwB9JaCH/xtRqOUNQZnRBt7IdAElCY+Dvb4hMCtiNv+enGM/RFGJuFHR9TxmI7rw==, tarball: https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.19.0.tgz} 1442 + peerDependencies: 1443 + '@tiptap/extension-list': ^3.19.0 1444 + 1445 + '@tiptap/extension-code-block-lowlight@3.19.0': 1446 + resolution: {integrity: sha512-P8O8i1J+XozEVA7bF/Ijwf/r1rVqrh1DBQ7dXxBcrUvLpIGyVjtxX228jBF/kD4kf2xOlphvjDhV2fLa8XOVsg==, tarball: https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.19.0.tgz} 1447 + peerDependencies: 1448 + '@tiptap/core': ^3.19.0 1449 + '@tiptap/extension-code-block': ^3.19.0 1450 + '@tiptap/pm': ^3.19.0 1451 + highlight.js: ^11 1452 + lowlight: ^2 || ^3 1453 + 1454 + '@tiptap/extension-code-block@3.19.0': 1455 + resolution: {integrity: sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg==, tarball: https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.19.0.tgz} 1456 + peerDependencies: 1457 + '@tiptap/core': ^3.19.0 1458 + '@tiptap/pm': ^3.19.0 1395 1459 1396 - '@tiptap/extension-bold@3.16.0': 1397 - resolution: {integrity: sha512-S61wtChbOigk2bklCJ2uEa8jbAnI9ChbW4d1z/Uv/Hr6eWo42vVBtjNZKFOsiBPDajFZbOfnvekGs731jNrHKg==} 1460 + '@tiptap/extension-code@3.19.0': 1461 + resolution: {integrity: sha512-2kqqQIXBXj2Or+4qeY3WoE7msK+XaHKL6EKOcKlOP2BW8eYqNTPzNSL+PfBDQ3snA7ljZQkTs/j4GYDj90vR1A==, tarball: https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.19.0.tgz} 1398 1462 peerDependencies: 1399 - '@tiptap/core': ^3.16.0 1463 + '@tiptap/core': ^3.19.0 1400 1464 1401 - '@tiptap/extension-bullet-list@3.16.0': 1402 - resolution: {integrity: sha512-GjKssVf9241GLdshdYRzPPApWQIB+7GJy0TZgx7bWmFUVgypYxDoE/rQRmvb3Fhup836bgfpfUzStevJ6eIClw==} 1465 + '@tiptap/extension-document@3.19.0': 1466 + resolution: {integrity: sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A==, tarball: https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.19.0.tgz} 1403 1467 peerDependencies: 1404 - '@tiptap/extension-list': ^3.16.0 1468 + '@tiptap/core': ^3.19.0 1405 1469 1406 - '@tiptap/extension-code-block@3.16.0': 1407 - resolution: {integrity: sha512-hAsXe6fIBsvIMWlVEXKLEzFQ8h6VUEBWqEEFIQgq+SpZCkGX+KzVmFXd5V2aDqb+BoOyqYiA2w1d/frBBxVEpw==} 1470 + '@tiptap/extension-dropcursor@3.19.0': 1471 + resolution: {integrity: sha512-sf3dEZXiLvsGqVK2maUIzXY6qtYYCvBumag7+VPTMGQ0D4hiZ1X/4ukt4+6VXDg5R2WP1CoIt/QvUetUjWNhbQ==, tarball: https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.19.0.tgz} 1408 1472 peerDependencies: 1409 - '@tiptap/core': ^3.16.0 1410 - '@tiptap/pm': ^3.16.0 1473 + '@tiptap/extensions': ^3.19.0 1411 1474 1412 - '@tiptap/extension-code@3.16.0': 1413 - resolution: {integrity: sha512-U8/bz/1BhQ39LJgUqJ8u1HzLcYdtubUWVAVC8seteLz1vIhXkTyfAC8478KQ+YdIDkMzAs+0vxk5BsWcWG16zQ==} 1475 + '@tiptap/extension-floating-menu@3.19.0': 1476 + resolution: {integrity: sha512-JaoEkVRkt+Slq3tySlIsxnMnCjS0L5n1CA1hctjLy0iah8edetj3XD5mVv5iKqDzE+LIjF4nwLRRVKJPc8hFBg==, tarball: https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.19.0.tgz} 1414 1477 peerDependencies: 1415 - '@tiptap/core': ^3.16.0 1478 + '@floating-ui/dom': ^1.0.0 1479 + '@tiptap/core': ^3.19.0 1480 + '@tiptap/pm': ^3.19.0 1416 1481 1417 - '@tiptap/extension-document@3.16.0': 1418 - resolution: {integrity: sha512-vOwBnJIonYmmFVMEnnE1jwoUMq0P/9BcaUocIG9o5iFRTV38I8YGn8n6DiE1pjSeLXRpLrXl6LLwdOMBJewhBg==} 1482 + '@tiptap/extension-gapcursor@3.19.0': 1483 + resolution: {integrity: sha512-w7DACS4oSZaDWjz7gropZHPc9oXqC9yERZTcjWxyORuuIh1JFf0TRYspleK+OK28plK/IftojD/yUDn1MTRhvA==, tarball: https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.19.0.tgz} 1419 1484 peerDependencies: 1420 - '@tiptap/core': ^3.16.0 1485 + '@tiptap/extensions': ^3.19.0 1421 1486 1422 - '@tiptap/extension-dropcursor@3.16.0': 1423 - resolution: {integrity: sha512-n9Gbt99K9oBChjp8puF0ffAJtBF6ZVjydG5u5QO2Z8sHNE+Hn6ARfgZqLjr11ZF4b+mLShqsmyROmITNf73W+A==} 1487 + '@tiptap/extension-hard-break@3.19.0': 1488 + resolution: {integrity: sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==, tarball: https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz} 1424 1489 peerDependencies: 1425 - '@tiptap/extensions': ^3.16.0 1490 + '@tiptap/core': ^3.19.0 1426 1491 1427 - '@tiptap/extension-gapcursor@3.16.0': 1428 - resolution: {integrity: sha512-8dxE4bkfn6Jog/JHDxN/kzcRbyJB7HyFqCKdiTq0f4atzysmnEUuMswwlwMPaErkzlETD6B8NEEtMknEUqowGA==} 1492 + '@tiptap/extension-heading@3.19.0': 1493 + resolution: {integrity: sha512-uLpLlfyp086WYNOc0ekm1gIZNlEDfmzOhKzB0Hbyi6jDagTS+p9mxUNYeYOn9jPUxpFov43+Wm/4E24oY6B+TQ==, tarball: https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.19.0.tgz} 1429 1494 peerDependencies: 1430 - '@tiptap/extensions': ^3.16.0 1495 + '@tiptap/core': ^3.19.0 1431 1496 1432 - '@tiptap/extension-hard-break@3.16.0': 1433 - resolution: {integrity: sha512-nwUTixlHYo9V1lfOYsRi2JiAYCRC7pObB3Kt7rEeMxB3XmcRcSpHtxYs6r+TvifsLFys8RG5wOFXIV/YXZHcDg==} 1497 + '@tiptap/extension-horizontal-rule@3.19.0': 1498 + resolution: {integrity: sha512-iqUHmgMGhMgYGwG6L/4JdelVQ5Mstb4qHcgTGd/4dkcUOepILvhdxajPle7OEdf9sRgjQO6uoAU5BVZVC26+ng==, tarball: https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.19.0.tgz} 1434 1499 peerDependencies: 1435 - '@tiptap/core': ^3.16.0 1500 + '@tiptap/core': ^3.19.0 1501 + '@tiptap/pm': ^3.19.0 1436 1502 1437 - '@tiptap/extension-heading@3.16.0': 1438 - resolution: {integrity: sha512-du4d1Ukvhr1zvPWlU/HS3NMlRswzGRSNDNfCFUhdYgQoHOSnUXshnlKD3E5H0EHfL9UwT4JFyqAT3+1ZnahkdA==} 1503 + '@tiptap/extension-image@3.19.0': 1504 + resolution: {integrity: sha512-/rGl8nBziBPVJJ/9639eQWFDKcI3RQsDM3s+cqYQMFQfMqc7sQB9h4o4sHCBpmKxk3Y0FV/0NjnjLbBVm8OKdQ==, tarball: https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.19.0.tgz} 1439 1505 peerDependencies: 1440 - '@tiptap/core': ^3.16.0 1506 + '@tiptap/core': ^3.19.0 1441 1507 1442 - '@tiptap/extension-horizontal-rule@3.16.0': 1443 - resolution: {integrity: sha512-yyKl45UCH55pIf8G4bHiUNFxggipRVT276c3t9vrkXU6BkJhzfxxcIc5svWkiThDjdYmJs1FfVCYAtGSuKiSyA==} 1508 + '@tiptap/extension-italic@3.19.0': 1509 + resolution: {integrity: sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==, tarball: https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz} 1444 1510 peerDependencies: 1445 - '@tiptap/core': ^3.16.0 1446 - '@tiptap/pm': ^3.16.0 1511 + '@tiptap/core': ^3.19.0 1447 1512 1448 - '@tiptap/extension-image@3.16.0': 1449 - resolution: {integrity: sha512-mTjt4kdyVtY/2dJcfxAgBae/dkH+r6GwARl7NlPtnI3EzpELFR65FNuOQyTxFXP3yfV9uMtPpq6Wevk8aLTsxQ==} 1513 + '@tiptap/extension-link@3.19.0': 1514 + resolution: {integrity: sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==, tarball: https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz} 1450 1515 peerDependencies: 1451 - '@tiptap/core': ^3.16.0 1516 + '@tiptap/core': ^3.19.0 1517 + '@tiptap/pm': ^3.19.0 1452 1518 1453 - '@tiptap/extension-italic@3.16.0': 1454 - resolution: {integrity: sha512-SVNnkRUK6G+dQse5Ms8Q/wudSTh37O94p02RDc3KneEtBk6wkokqCLuwKnWLPhlEqsuOku+wTD9DSJdvoRlq9w==} 1519 + '@tiptap/extension-list-item@3.19.0': 1520 + resolution: {integrity: sha512-VsSKuJz4/Tb6ZmFkXqWpDYkRzmaLTyE6dNSEpNmUpmZ32sMqo58mt11/huADNwfBFB0Ve7siH/VnFNIJYY3xvg==, tarball: https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.19.0.tgz} 1455 1521 peerDependencies: 1456 - '@tiptap/core': ^3.16.0 1522 + '@tiptap/extension-list': ^3.19.0 1457 1523 1458 - '@tiptap/extension-link@3.16.0': 1459 - resolution: {integrity: sha512-WPPJLtGXQadBVVwH6gcMpaXIgfvFF9NGpE2IVqleVKR3Epv2Rd4aWd4oyAdrT8KU9G6dzMXZfkrB8aArTDKxYQ==} 1524 + '@tiptap/extension-list-keymap@3.19.0': 1525 + resolution: {integrity: sha512-bxgmAgA3RzBGA0GyTwS2CC1c+QjkJJq9hC+S6PSOWELGRiTbwDN3MANksFXLjntkTa0N5fOnL27vBHtMStURqw==, tarball: https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.19.0.tgz} 1460 1526 peerDependencies: 1461 - '@tiptap/core': ^3.16.0 1462 - '@tiptap/pm': ^3.16.0 1527 + '@tiptap/extension-list': ^3.19.0 1463 1528 1464 - '@tiptap/extension-list-item@3.16.0': 1465 - resolution: {integrity: sha512-kshssUZEPoosPWbJNQEFJnVV3iPwsDU9l/RCdHJB5SE+aNWJyUk5hQ/YwngEHjV7rS+RnAuhbrcB5swgyzROuA==} 1529 + '@tiptap/extension-list@3.19.0': 1530 + resolution: {integrity: sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==, tarball: https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.19.0.tgz} 1466 1531 peerDependencies: 1467 - '@tiptap/extension-list': ^3.16.0 1532 + '@tiptap/core': ^3.19.0 1533 + '@tiptap/pm': ^3.19.0 1468 1534 1469 - '@tiptap/extension-list-keymap@3.16.0': 1470 - resolution: {integrity: sha512-AU3J9W6uo835ZdxiGmrYx1KUymzvfkU4d278X0OBAfujORXkbDNlo9er8pOrOpgXNxgtnlH32lWR4bWyKdUgwA==} 1535 + '@tiptap/extension-ordered-list@3.19.0': 1536 + resolution: {integrity: sha512-cxGsINquwHYE1kmhAcLNLHAofmoDEG6jbesR5ybl7tU5JwtKVO7S/xZatll2DU1dsDAXWPWEeeMl4e/9svYjCg==, tarball: https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.19.0.tgz} 1471 1537 peerDependencies: 1472 - '@tiptap/extension-list': ^3.16.0 1538 + '@tiptap/extension-list': ^3.19.0 1473 1539 1474 - '@tiptap/extension-list@3.16.0': 1475 - resolution: {integrity: sha512-tpjWGugfI0XYR9iG/QlYYtCY35TFWHNwGKc94wN4s7NmAjB4xlwdTkTZQ6PdZ39x1SeHkRjxAka+6GcBIoOHGQ==} 1540 + '@tiptap/extension-paragraph@3.19.0': 1541 + resolution: {integrity: sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg==, tarball: https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz} 1476 1542 peerDependencies: 1477 - '@tiptap/core': ^3.16.0 1478 - '@tiptap/pm': ^3.16.0 1543 + '@tiptap/core': ^3.19.0 1479 1544 1480 - '@tiptap/extension-ordered-list@3.16.0': 1481 - resolution: {integrity: sha512-mNKqwEgiXSMi5afGtnodsptveukpr3GqcGsw2fqJFyNq9SITznjiiuQfULtzVnayC8qHsk0Zzbpzf0zvdHlypg==} 1545 + '@tiptap/extension-placeholder@3.19.0': 1546 + resolution: {integrity: sha512-i15OfgyI4IDCYAcYSKUMnuZkYuUInfanjf9zquH8J2BETiomf/jZldVCp/QycMJ8DOXZ38fXDc99wOygnSNySg==, tarball: https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.19.0.tgz} 1482 1547 peerDependencies: 1483 - '@tiptap/extension-list': ^3.16.0 1548 + '@tiptap/extensions': ^3.19.0 1484 1549 1485 - '@tiptap/extension-paragraph@3.16.0': 1486 - resolution: {integrity: sha512-JHn3ev7US5FxtQFyEOeQ8XfvKcR5NiHkwDH2Gcwe+0ttpA/Qrrr5XN3tJIgI3rXfR5DjxArq/QO0OTVBm3xlJA==} 1550 + '@tiptap/extension-strike@3.19.0': 1551 + resolution: {integrity: sha512-xYpabHsv7PccLUBQaP8AYiFCnYbx6P93RHPd0lgNwhdOjYFd931Zy38RyoxPHAgbYVmhf1iyx7lpuLtBnhS5dA==, tarball: https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.19.0.tgz} 1487 1552 peerDependencies: 1488 - '@tiptap/core': ^3.16.0 1553 + '@tiptap/core': ^3.19.0 1489 1554 1490 - '@tiptap/extension-placeholder@3.16.0': 1491 - resolution: {integrity: sha512-sbffATC2fwyRF9i483fSRj5MTADCqD1QUl4LCAt8VO+cVEQbV19WV5J7EQ8wIjDEoFoOKIUXdU/0CEcF4IpjDQ==} 1555 + '@tiptap/extension-text@3.19.0': 1556 + resolution: {integrity: sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g==, tarball: https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.19.0.tgz} 1492 1557 peerDependencies: 1493 - '@tiptap/extensions': ^3.16.0 1558 + '@tiptap/core': ^3.19.0 1494 1559 1495 - '@tiptap/extension-strike@3.16.0': 1496 - resolution: {integrity: sha512-l5/4+gii53kET7ETyYpbTumoQdZ6HwJLUcDlGHutLZlBCaZPxFTi5qgHQBhNq5KAzRH3LVJeb0fEeMi+yCZBQA==} 1560 + '@tiptap/extension-typography@3.19.0': 1561 + resolution: {integrity: sha512-2Rwwz1ErNhqUcXPzPX2u4frdyrK4Yj6ZMvCLPxLt5lQXj9Eq9YEoD9isw8abR105ko3BCidvfElQYSFu6dWPSw==, tarball: https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-3.19.0.tgz} 1497 1562 peerDependencies: 1498 - '@tiptap/core': ^3.16.0 1563 + '@tiptap/core': ^3.19.0 1499 1564 1500 - '@tiptap/extension-text@3.16.0': 1501 - resolution: {integrity: sha512-KTewoX4wZq95cKnjBbogRwBFoGgM6qUg1yjCQ/M6Ajkp4Mtp8Iki9EiAxtfk76b/wtXFf3DsDhFOeVqgKyYbYg==} 1565 + '@tiptap/extension-underline@3.19.0': 1566 + resolution: {integrity: sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw==, tarball: https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.19.0.tgz} 1502 1567 peerDependencies: 1503 - '@tiptap/core': ^3.16.0 1568 + '@tiptap/core': ^3.19.0 1504 1569 1505 - '@tiptap/extension-underline@3.16.0': 1506 - resolution: {integrity: sha512-obXAPgHVZocMaW6HtKyCYsN4CxHogWr23gioyEQcpIX0LeegHDqxkoPrjIPX6Tn1isDyvXchcSKWHEfiHO3ZOA==} 1570 + '@tiptap/extensions@3.19.0': 1571 + resolution: {integrity: sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==, tarball: https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.19.0.tgz} 1507 1572 peerDependencies: 1508 - '@tiptap/core': ^3.16.0 1573 + '@tiptap/core': ^3.19.0 1574 + '@tiptap/pm': ^3.19.0 1509 1575 1510 - '@tiptap/extensions@3.16.0': 1511 - resolution: {integrity: sha512-0iVrn0FHcHIRMdsQLQbf16NgYrKz+Sup/8dDMVBy1QoHn5Hb51QZABqXJTZ6u7My34b4fNZrSggzBAE7l7N/pA==} 1576 + '@tiptap/markdown@3.19.0': 1577 + resolution: {integrity: sha512-Pnfacq2FHky1rqwmGwEmUJxuZu8VZ8XjaJIqsQC34S3CQWiOU+PukC9In2odzcooiVncLWT9s97jKuYpbmF1tQ==, tarball: https://registry.npmjs.org/@tiptap/markdown/-/markdown-3.19.0.tgz} 1512 1578 peerDependencies: 1513 - '@tiptap/core': ^3.16.0 1514 - '@tiptap/pm': ^3.16.0 1579 + '@tiptap/core': ^3.19.0 1580 + '@tiptap/pm': ^3.19.0 1581 + 1582 + '@tiptap/pm@3.19.0': 1583 + resolution: {integrity: sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==, tarball: https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz} 1515 1584 1516 - '@tiptap/pm@3.16.0': 1517 - resolution: {integrity: sha512-FMxZ6Tc5ONKa/EByDV8lswct6YW2lF/wn11zqXmrfBZhdG7UQPTijpSwb6TCqaO5GOHmixaIaDPj+zimUREHQA==} 1585 + '@tiptap/starter-kit@3.19.0': 1586 + resolution: {integrity: sha512-dTCkHEz+Y8ADxX7h+xvl6caAj+3nII/wMB1rTQchSuNKqJTOrzyUsCWm094+IoZmLT738wANE0fRIgziNHs/ug==, tarball: https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.19.0.tgz} 1518 1587 1519 - '@tiptap/starter-kit@3.16.0': 1520 - resolution: {integrity: sha512-eWi+77SgKyhSx91Hmn32ER+gPN6FfInGtod4A+XxSG+LqS/sn6kpUEdowYrnqiZzhUXZCSTSJvC+UcMUZHOkxQ==} 1588 + '@tiptap/suggestion@3.19.0': 1589 + resolution: {integrity: sha512-tUZwMRFqTVPIo566ZmHNRteyZxJy2EE4FA+S3IeIUOOvY6AW0h1imhbpBO7sXV8CeEQvpa+2DWwLvy7L3vmstA==, tarball: https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.19.0.tgz} 1590 + peerDependencies: 1591 + '@tiptap/core': ^3.19.0 1592 + '@tiptap/pm': ^3.19.0 1521 1593 1522 1594 '@tweenjs/tween.js@23.1.3': 1523 1595 resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} ··· 1534 1606 '@types/geojson@7946.0.16': 1535 1607 resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==, tarball: https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz} 1536 1608 1609 + '@types/hast@3.0.4': 1610 + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, tarball: https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz} 1611 + 1537 1612 '@types/json-schema@7.0.15': 1538 1613 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 1539 1614 1540 1615 '@types/linkify-it@5.0.0': 1541 - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} 1616 + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==, tarball: https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz} 1542 1617 1543 1618 '@types/markdown-it@14.1.2': 1544 - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} 1619 + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==, tarball: https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz} 1545 1620 1546 1621 '@types/mdurl@2.0.0': 1547 - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} 1622 + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==, tarball: https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz} 1548 1623 1549 1624 '@types/node@25.0.10': 1550 1625 resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==} ··· 1563 1638 1564 1639 '@types/turndown@5.0.6': 1565 1640 resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} 1641 + 1642 + '@types/unist@3.0.3': 1643 + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, tarball: https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz} 1566 1644 1567 1645 '@types/webxr@0.5.24': 1568 1646 resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} ··· 1771 1849 resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} 1772 1850 1773 1851 crelt@1.0.6: 1774 - resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} 1852 + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==, tarball: https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz} 1775 1853 1776 1854 cross-spawn@7.0.6: 1777 1855 resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} ··· 1835 1913 devalue@5.6.2: 1836 1914 resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} 1837 1915 1916 + devlop@1.1.0: 1917 + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, tarball: https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz} 1918 + 1838 1919 diet-sprite@0.0.1: 1839 1920 resolution: {integrity: sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A==} 1840 1921 ··· 2056 2137 resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} 2057 2138 engines: {node: '>=6'} 2058 2139 2140 + highlight.js@11.11.1: 2141 + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==, tarball: https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz} 2142 + engines: {node: '>=12.0.0'} 2143 + 2059 2144 hls.js@1.6.15: 2060 2145 resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} 2061 2146 ··· 2236 2321 engines: {node: '>=18'} 2237 2322 2238 2323 linkify-it@5.0.0: 2239 - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} 2324 + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==, tarball: https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz} 2240 2325 2241 2326 linkifyjs@4.3.2: 2242 - resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} 2327 + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==, tarball: https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz} 2243 2328 2244 2329 loadjs@4.3.0: 2245 2330 resolution: {integrity: sha512-vNX4ZZLJBeDEOBvdr2v/F+0aN5oMuPu7JTqrMwp+DtgK+AryOlpy6Xtm2/HpNr+azEa828oQjOtWsB6iDtSfSQ==} ··· 2257 2342 loose-envify@1.4.0: 2258 2343 resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz} 2259 2344 hasBin: true 2345 + 2346 + lowlight@3.3.0: 2347 + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==, tarball: https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz} 2260 2348 2261 2349 lz-string@1.5.0: 2262 2350 resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} ··· 2276 2364 engines: {node: '>=16.14.0', npm: '>=8.1.0'} 2277 2365 2278 2366 markdown-it@14.1.0: 2279 - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} 2367 + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==, tarball: https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz} 2280 2368 hasBin: true 2281 2369 2282 2370 marked@17.0.1: ··· 2285 2373 hasBin: true 2286 2374 2287 2375 mdurl@2.0.0: 2288 - resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} 2376 + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==, tarball: https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz} 2289 2377 2290 2378 meshoptimizer@0.18.1: 2291 2379 resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} ··· 2372 2460 engines: {node: '>= 0.8.0'} 2373 2461 2374 2462 orderedmap@2.1.1: 2375 - resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} 2463 + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==, tarball: https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz} 2376 2464 2377 2465 p-limit@3.1.0: 2378 2466 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} ··· 2554 2642 resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, tarball: https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz} 2555 2643 2556 2644 prosemirror-changeset@2.3.1: 2557 - resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} 2645 + resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==, tarball: https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz} 2558 2646 2559 2647 prosemirror-collab@1.3.1: 2560 - resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} 2648 + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==, tarball: https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz} 2561 2649 2562 2650 prosemirror-commands@1.7.1: 2563 - resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} 2651 + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==, tarball: https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz} 2564 2652 2565 2653 prosemirror-dropcursor@1.8.2: 2566 - resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} 2654 + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==, tarball: https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz} 2567 2655 2568 2656 prosemirror-gapcursor@1.4.0: 2569 - resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} 2657 + resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==, tarball: https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz} 2570 2658 2571 2659 prosemirror-history@1.5.0: 2572 - resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} 2660 + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==, tarball: https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz} 2573 2661 2574 2662 prosemirror-inputrules@1.5.1: 2575 - resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} 2663 + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==, tarball: https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz} 2576 2664 2577 2665 prosemirror-keymap@1.2.3: 2578 - resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} 2666 + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==, tarball: https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz} 2579 2667 2580 2668 prosemirror-markdown@1.13.3: 2581 - resolution: {integrity: sha512-3E+Et6cdXIH0EgN2tGYQ+EBT7N4kMiZFsW+hzx+aPtOmADDHWCdd2uUQb7yklJrfUYUOjEEu22BiN6UFgPe4cQ==} 2669 + resolution: {integrity: sha512-3E+Et6cdXIH0EgN2tGYQ+EBT7N4kMiZFsW+hzx+aPtOmADDHWCdd2uUQb7yklJrfUYUOjEEu22BiN6UFgPe4cQ==, tarball: https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.3.tgz} 2582 2670 2583 2671 prosemirror-menu@1.2.5: 2584 - resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} 2672 + resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==, tarball: https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz} 2585 2673 2586 2674 prosemirror-model@1.25.4: 2587 - resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} 2675 + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==, tarball: https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz} 2588 2676 2589 2677 prosemirror-schema-basic@1.2.4: 2590 - resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} 2678 + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==, tarball: https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz} 2591 2679 2592 2680 prosemirror-schema-list@1.5.1: 2593 - resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} 2681 + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==, tarball: https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz} 2594 2682 2595 2683 prosemirror-state@1.4.4: 2596 - resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} 2684 + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==, tarball: https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz} 2597 2685 2598 2686 prosemirror-tables@1.8.5: 2599 - resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} 2687 + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==, tarball: https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz} 2600 2688 2601 2689 prosemirror-trailing-node@3.0.0: 2602 - resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} 2690 + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==, tarball: https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz} 2603 2691 peerDependencies: 2604 2692 prosemirror-model: ^1.22.1 2605 2693 prosemirror-state: ^1.4.2 2606 2694 prosemirror-view: ^1.33.8 2607 2695 2608 2696 prosemirror-transform@1.11.0: 2609 - resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} 2697 + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==, tarball: https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz} 2610 2698 2611 2699 prosemirror-view@1.41.5: 2612 - resolution: {integrity: sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA==} 2700 + resolution: {integrity: sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA==, tarball: https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.5.tgz} 2613 2701 2614 2702 protocol-buffers-schema@3.6.0: 2615 2703 resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==, tarball: https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz} 2616 2704 2617 2705 punycode.js@2.3.1: 2618 - resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} 2706 + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==, tarball: https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz} 2619 2707 engines: {node: '>=6'} 2620 2708 2621 2709 punycode@2.3.1: ··· 2693 2781 hasBin: true 2694 2782 2695 2783 rope-sequence@1.3.4: 2696 - resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} 2784 + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==, tarball: https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz} 2697 2785 2698 2786 runed@0.23.4: 2699 2787 resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==} ··· 2831 2919 peerDependencies: 2832 2920 svelte: ^5.0.0 2833 2921 2922 + svelte-tiptap@3.0.1: 2923 + resolution: {integrity: sha512-Vi3kVGOd01f7mslOxGbJB7z2QavdvH+6WffhB+Y5fleTiZaW0YWqIboyO2u/uh4BQeosiINmmuRJ+Qwb7mYP+A==, tarball: https://registry.npmjs.org/svelte-tiptap/-/svelte-tiptap-3.0.1.tgz} 2924 + peerDependencies: 2925 + '@floating-ui/dom': ^1.0.0 2926 + '@tiptap/core': ^3.0.0 2927 + '@tiptap/extension-bubble-menu': ^3.0.0 2928 + '@tiptap/extension-floating-menu': ^3.0.0 2929 + '@tiptap/pm': ^3.0.0 2930 + svelte: ^5.0.0 2931 + 2834 2932 svelte-toolbelt@0.10.6: 2835 2933 resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==} 2836 2934 engines: {node: '>=18', pnpm: '>=8.7.0'} ··· 2970 3068 hasBin: true 2971 3069 2972 3070 uc.micro@2.1.0: 2973 - resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} 3071 + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==, tarball: https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz} 2974 3072 2975 3073 ufo@1.6.3: 2976 3074 resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} ··· 3062 3160 optional: true 3063 3161 3064 3162 w3c-keyname@2.2.8: 3065 - resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} 3163 + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==, tarball: https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz} 3066 3164 3067 3165 webgl-sdf-generator@1.1.1: 3068 3166 resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} ··· 3531 3629 dependencies: 3532 3630 '@floating-ui/utils': 0.2.10 3533 3631 3534 - '@floating-ui/dom@1.7.4': 3632 + '@floating-ui/core@1.7.4': 3633 + dependencies: 3634 + '@floating-ui/utils': 0.2.10 3635 + 3636 + '@floating-ui/dom@1.7.5': 3535 3637 dependencies: 3536 - '@floating-ui/core': 1.7.3 3638 + '@floating-ui/core': 1.7.4 3537 3639 '@floating-ui/utils': 0.2.10 3538 3640 3539 3641 '@floating-ui/utils@0.2.10': {} ··· 4104 4206 transitivePeerDependencies: 4105 4207 - '@types/three' 4106 4208 4107 - '@tiptap/core@3.16.0(@tiptap/pm@3.16.0)': 4209 + '@tiptap/core@3.19.0(@tiptap/pm@3.19.0)': 4210 + dependencies: 4211 + '@tiptap/pm': 3.19.0 4212 + 4213 + '@tiptap/extension-blockquote@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4108 4214 dependencies: 4109 - '@tiptap/pm': 3.16.0 4215 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4110 4216 4111 - '@tiptap/extension-blockquote@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4217 + '@tiptap/extension-bold@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4112 4218 dependencies: 4113 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4219 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4114 4220 4115 - '@tiptap/extension-bold@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4221 + '@tiptap/extension-bubble-menu@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4116 4222 dependencies: 4117 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4223 + '@floating-ui/dom': 1.7.5 4224 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4225 + '@tiptap/pm': 3.19.0 4118 4226 4119 - '@tiptap/extension-bullet-list@3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4227 + '@tiptap/extension-bullet-list@3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4120 4228 dependencies: 4121 - '@tiptap/extension-list': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4229 + '@tiptap/extension-list': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4122 4230 4123 - '@tiptap/extension-code-block@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)': 4231 + '@tiptap/extension-code-block-lowlight@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/extension-code-block@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)(highlight.js@11.11.1)(lowlight@3.3.0)': 4232 + dependencies: 4233 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4234 + '@tiptap/extension-code-block': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4235 + '@tiptap/pm': 3.19.0 4236 + highlight.js: 11.11.1 4237 + lowlight: 3.3.0 4238 + 4239 + '@tiptap/extension-code-block@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4240 + dependencies: 4241 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4242 + '@tiptap/pm': 3.19.0 4243 + 4244 + '@tiptap/extension-code@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4124 4245 dependencies: 4125 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4126 - '@tiptap/pm': 3.16.0 4246 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4127 4247 4128 - '@tiptap/extension-code@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4248 + '@tiptap/extension-document@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4129 4249 dependencies: 4130 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4250 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4131 4251 4132 - '@tiptap/extension-document@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4252 + '@tiptap/extension-dropcursor@3.19.0(@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4133 4253 dependencies: 4134 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4254 + '@tiptap/extensions': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4135 4255 4136 - '@tiptap/extension-dropcursor@3.16.0(@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4256 + '@tiptap/extension-floating-menu@3.19.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4137 4257 dependencies: 4138 - '@tiptap/extensions': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4258 + '@floating-ui/dom': 1.7.5 4259 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4260 + '@tiptap/pm': 3.19.0 4139 4261 4140 - '@tiptap/extension-gapcursor@3.16.0(@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4262 + '@tiptap/extension-gapcursor@3.19.0(@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4141 4263 dependencies: 4142 - '@tiptap/extensions': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4264 + '@tiptap/extensions': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4143 4265 4144 - '@tiptap/extension-hard-break@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4266 + '@tiptap/extension-hard-break@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4145 4267 dependencies: 4146 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4268 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4147 4269 4148 - '@tiptap/extension-heading@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4270 + '@tiptap/extension-heading@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4149 4271 dependencies: 4150 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4272 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4151 4273 4152 - '@tiptap/extension-horizontal-rule@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)': 4274 + '@tiptap/extension-horizontal-rule@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4153 4275 dependencies: 4154 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4155 - '@tiptap/pm': 3.16.0 4276 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4277 + '@tiptap/pm': 3.19.0 4156 4278 4157 - '@tiptap/extension-image@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4279 + '@tiptap/extension-image@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4158 4280 dependencies: 4159 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4281 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4160 4282 4161 - '@tiptap/extension-italic@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4283 + '@tiptap/extension-italic@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4162 4284 dependencies: 4163 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4285 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4164 4286 4165 - '@tiptap/extension-link@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)': 4287 + '@tiptap/extension-link@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4166 4288 dependencies: 4167 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4168 - '@tiptap/pm': 3.16.0 4289 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4290 + '@tiptap/pm': 3.19.0 4169 4291 linkifyjs: 4.3.2 4170 4292 4171 - '@tiptap/extension-list-item@3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4293 + '@tiptap/extension-list-item@3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4172 4294 dependencies: 4173 - '@tiptap/extension-list': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4295 + '@tiptap/extension-list': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4174 4296 4175 - '@tiptap/extension-list-keymap@3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4297 + '@tiptap/extension-list-keymap@3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4176 4298 dependencies: 4177 - '@tiptap/extension-list': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4299 + '@tiptap/extension-list': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4178 4300 4179 - '@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)': 4301 + '@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4180 4302 dependencies: 4181 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4182 - '@tiptap/pm': 3.16.0 4303 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4304 + '@tiptap/pm': 3.19.0 4305 + 4306 + '@tiptap/extension-ordered-list@3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4307 + dependencies: 4308 + '@tiptap/extension-list': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4309 + 4310 + '@tiptap/extension-paragraph@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4311 + dependencies: 4312 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4183 4313 4184 - '@tiptap/extension-ordered-list@3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4314 + '@tiptap/extension-placeholder@3.19.0(@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))': 4185 4315 dependencies: 4186 - '@tiptap/extension-list': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4316 + '@tiptap/extensions': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4187 4317 4188 - '@tiptap/extension-paragraph@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4318 + '@tiptap/extension-strike@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4189 4319 dependencies: 4190 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4320 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4191 4321 4192 - '@tiptap/extension-placeholder@3.16.0(@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0))': 4322 + '@tiptap/extension-text@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4193 4323 dependencies: 4194 - '@tiptap/extensions': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4324 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4195 4325 4196 - '@tiptap/extension-strike@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4326 + '@tiptap/extension-typography@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4197 4327 dependencies: 4198 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4328 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4199 4329 4200 - '@tiptap/extension-text@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4330 + '@tiptap/extension-underline@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))': 4201 4331 dependencies: 4202 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4332 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4203 4333 4204 - '@tiptap/extension-underline@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))': 4334 + '@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4205 4335 dependencies: 4206 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4336 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4337 + '@tiptap/pm': 3.19.0 4207 4338 4208 - '@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)': 4339 + '@tiptap/markdown@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4209 4340 dependencies: 4210 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4211 - '@tiptap/pm': 3.16.0 4341 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4342 + '@tiptap/pm': 3.19.0 4343 + marked: 17.0.1 4212 4344 4213 - '@tiptap/pm@3.16.0': 4345 + '@tiptap/pm@3.19.0': 4214 4346 dependencies: 4215 4347 prosemirror-changeset: 2.3.1 4216 4348 prosemirror-collab: 1.3.1 ··· 4231 4363 prosemirror-transform: 1.11.0 4232 4364 prosemirror-view: 1.41.5 4233 4365 4234 - '@tiptap/starter-kit@3.16.0': 4366 + '@tiptap/starter-kit@3.19.0': 4235 4367 dependencies: 4236 - '@tiptap/core': 3.16.0(@tiptap/pm@3.16.0) 4237 - '@tiptap/extension-blockquote': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4238 - '@tiptap/extension-bold': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4239 - '@tiptap/extension-bullet-list': 3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 4240 - '@tiptap/extension-code': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4241 - '@tiptap/extension-code-block': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4242 - '@tiptap/extension-document': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4243 - '@tiptap/extension-dropcursor': 3.16.0(@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 4244 - '@tiptap/extension-gapcursor': 3.16.0(@tiptap/extensions@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 4245 - '@tiptap/extension-hard-break': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4246 - '@tiptap/extension-heading': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4247 - '@tiptap/extension-horizontal-rule': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4248 - '@tiptap/extension-italic': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4249 - '@tiptap/extension-link': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4250 - '@tiptap/extension-list': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4251 - '@tiptap/extension-list-item': 3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 4252 - '@tiptap/extension-list-keymap': 3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 4253 - '@tiptap/extension-ordered-list': 3.16.0(@tiptap/extension-list@3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0)) 4254 - '@tiptap/extension-paragraph': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4255 - '@tiptap/extension-strike': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4256 - '@tiptap/extension-text': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4257 - '@tiptap/extension-underline': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) 4258 - '@tiptap/extensions': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) 4259 - '@tiptap/pm': 3.16.0 4368 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4369 + '@tiptap/extension-blockquote': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4370 + '@tiptap/extension-bold': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4371 + '@tiptap/extension-bullet-list': 3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 4372 + '@tiptap/extension-code': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4373 + '@tiptap/extension-code-block': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4374 + '@tiptap/extension-document': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4375 + '@tiptap/extension-dropcursor': 3.19.0(@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 4376 + '@tiptap/extension-gapcursor': 3.19.0(@tiptap/extensions@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 4377 + '@tiptap/extension-hard-break': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4378 + '@tiptap/extension-heading': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4379 + '@tiptap/extension-horizontal-rule': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4380 + '@tiptap/extension-italic': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4381 + '@tiptap/extension-link': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4382 + '@tiptap/extension-list': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4383 + '@tiptap/extension-list-item': 3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 4384 + '@tiptap/extension-list-keymap': 3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 4385 + '@tiptap/extension-ordered-list': 3.19.0(@tiptap/extension-list@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)) 4386 + '@tiptap/extension-paragraph': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4387 + '@tiptap/extension-strike': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4388 + '@tiptap/extension-text': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4389 + '@tiptap/extension-underline': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) 4390 + '@tiptap/extensions': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 4391 + '@tiptap/pm': 3.19.0 4392 + 4393 + '@tiptap/suggestion@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)': 4394 + dependencies: 4395 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 4396 + '@tiptap/pm': 3.19.0 4260 4397 4261 4398 '@tweenjs/tween.js@23.1.3': {} 4262 4399 ··· 4269 4406 '@types/estree@1.0.8': {} 4270 4407 4271 4408 '@types/geojson@7946.0.16': {} 4409 + 4410 + '@types/hast@3.0.4': 4411 + dependencies: 4412 + '@types/unist': 3.0.3 4272 4413 4273 4414 '@types/json-schema@7.0.15': {} 4274 4415 ··· 4306 4447 4307 4448 '@types/turndown@5.0.6': {} 4308 4449 4450 + '@types/unist@3.0.3': {} 4451 + 4309 4452 '@types/webxr@0.5.24': {} 4310 4453 4311 4454 '@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': ··· 4443 4586 bits-ui@1.8.0(svelte@5.48.0): 4444 4587 dependencies: 4445 4588 '@floating-ui/core': 1.7.3 4446 - '@floating-ui/dom': 1.7.4 4589 + '@floating-ui/dom': 1.7.5 4447 4590 '@internationalized/date': 3.10.1 4448 4591 css.escape: 1.5.1 4449 4592 esm-env: 1.2.2 ··· 4455 4598 bits-ui@2.15.4(@internationalized/date@3.10.1)(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 4456 4599 dependencies: 4457 4600 '@floating-ui/core': 1.7.3 4458 - '@floating-ui/dom': 1.7.4 4601 + '@floating-ui/dom': 1.7.5 4459 4602 '@internationalized/date': 3.10.1 4460 4603 esm-env: 1.2.2 4461 4604 runed: 0.35.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) ··· 4605 4748 4606 4749 devalue@5.6.2: {} 4607 4750 4751 + devlop@1.1.0: 4752 + dependencies: 4753 + dequal: 2.0.3 4754 + 4608 4755 diet-sprite@0.0.1: {} 4609 4756 4610 4757 dom-serializer@2.0.0: ··· 4872 5019 4873 5020 hex-rgb@4.3.0: {} 4874 5021 5022 + highlight.js@11.11.1: {} 5023 + 4875 5024 hls.js@1.6.15: {} 4876 5025 4877 5026 htmlparser2@10.1.0: ··· 5035 5184 loose-envify@1.4.0: 5036 5185 dependencies: 5037 5186 js-tokens: 4.0.0 5187 + 5188 + lowlight@3.3.0: 5189 + dependencies: 5190 + '@types/hast': 3.0.4 5191 + devlop: 1.1.0 5192 + highlight.js: 11.11.1 5038 5193 5039 5194 lz-string@1.5.0: {} 5040 5195 ··· 5653 5808 svelte-sonner@1.0.7(svelte@5.48.0): 5654 5809 dependencies: 5655 5810 runed: 0.28.0(svelte@5.48.0) 5811 + svelte: 5.48.0 5812 + 5813 + svelte-tiptap@3.0.1(@floating-ui/dom@1.7.5)(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/extension-bubble-menu@3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))(@tiptap/extension-floating-menu@3.19.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0)(svelte@5.48.0): 5814 + dependencies: 5815 + '@floating-ui/dom': 1.7.5 5816 + '@tiptap/core': 3.19.0(@tiptap/pm@3.19.0) 5817 + '@tiptap/extension-bubble-menu': 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 5818 + '@tiptap/extension-floating-menu': 3.19.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.19.0(@tiptap/pm@3.19.0))(@tiptap/pm@3.19.0) 5819 + '@tiptap/pm': 3.19.0 5656 5820 svelte: 5.48.0 5657 5821 5658 5822 svelte-toolbelt@0.10.6(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0):
+120
src/lib/components/rich-text-editor/Icon.svelte
··· 1 + <script lang="ts"> 2 + import type { RichTextTypes } from '.'; 3 + 4 + let { 5 + name 6 + }: { 7 + name: RichTextTypes; 8 + } = $props(); 9 + </script> 10 + 11 + {#if name === 'paragraph'} 12 + <svg 13 + xmlns="http://www.w3.org/2000/svg" 14 + viewBox="0 0 24 24" 15 + fill="none" 16 + stroke="currentColor" 17 + stroke-width="2" 18 + stroke-linecap="round" 19 + stroke-linejoin="round" 20 + ><path d="M13 4v16" /><path d="M17 4v16" /><path d="M19 4H9.5a4.5 4.5 0 0 0 0 9H13" /></svg 21 + > 22 + {:else if name === 'heading-1'} 23 + <svg 24 + xmlns="http://www.w3.org/2000/svg" 25 + viewBox="0 0 24 24" 26 + fill="none" 27 + stroke="currentColor" 28 + stroke-width="2" 29 + stroke-linecap="round" 30 + stroke-linejoin="round" 31 + ><path d="M4 12h8" /><path d="M4 18V6" /><path d="M12 18V6" /><path d="m17 12 3-2v8" /></svg 32 + > 33 + {:else if name === 'heading-2'} 34 + <svg 35 + xmlns="http://www.w3.org/2000/svg" 36 + viewBox="0 0 24 24" 37 + fill="none" 38 + stroke="currentColor" 39 + stroke-width="2" 40 + stroke-linecap="round" 41 + stroke-linejoin="round" 42 + ><path d="M4 12h8" /><path d="M4 18V6" /><path d="M12 18V6" /><path 43 + d="M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1" 44 + /></svg 45 + > 46 + {:else if name === 'heading-3'} 47 + <svg 48 + xmlns="http://www.w3.org/2000/svg" 49 + viewBox="0 0 24 24" 50 + fill="none" 51 + stroke="currentColor" 52 + stroke-width="2" 53 + stroke-linecap="round" 54 + stroke-linejoin="round" 55 + ><path d="M4 12h8" /><path d="M4 18V6" /><path d="M12 18V6" /><path 56 + d="M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2" 57 + /><path d="M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2" /></svg 58 + > 59 + {:else if name === 'blockquote'} 60 + <svg 61 + xmlns="http://www.w3.org/2000/svg" 62 + viewBox="0 0 24 24" 63 + fill="none" 64 + stroke="currentColor" 65 + stroke-width="2" 66 + stroke-linecap="round" 67 + stroke-linejoin="round" 68 + ><path d="M17 6H3" /><path d="M21 12H8" /><path d="M21 18H8" /><path d="M3 12v6" /></svg 69 + > 70 + {:else if name === 'code'} 71 + <svg 72 + xmlns="http://www.w3.org/2000/svg" 73 + viewBox="0 0 24 24" 74 + fill="none" 75 + stroke="currentColor" 76 + stroke-width="2" 77 + stroke-linecap="round" 78 + stroke-linejoin="round" 79 + ><polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" /></svg 80 + > 81 + {:else if name === 'bullet-list'} 82 + <svg 83 + xmlns="http://www.w3.org/2000/svg" 84 + viewBox="0 0 24 24" 85 + fill="none" 86 + stroke="currentColor" 87 + stroke-width="2" 88 + stroke-linecap="round" 89 + stroke-linejoin="round" 90 + ><path d="M3 12h.01" /><path d="M3 18h.01" /><path d="M3 6h.01" /><path d="M8 12h13" /><path 91 + d="M8 18h13" 92 + /><path d="M8 6h13" /></svg 93 + > 94 + {:else if name === 'ordered-list'} 95 + <svg 96 + xmlns="http://www.w3.org/2000/svg" 97 + viewBox="0 0 24 24" 98 + fill="none" 99 + stroke="currentColor" 100 + stroke-width="2" 101 + stroke-linecap="round" 102 + stroke-linejoin="round" 103 + ><path d="M10 12h11" /><path d="M10 18h11" /><path d="M10 6h11" /><path d="M4 10h2" /><path 104 + d="M4 6h1v4" 105 + /><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" /></svg 106 + > 107 + {:else if name === 'image'} 108 + <svg 109 + xmlns="http://www.w3.org/2000/svg" 110 + viewBox="0 0 24 24" 111 + fill="none" 112 + stroke="currentColor" 113 + stroke-width="2" 114 + stroke-linecap="round" 115 + stroke-linejoin="round" 116 + ><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path 117 + d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" 118 + /></svg 119 + > 120 + {/if}
+415
src/lib/components/rich-text-editor/RichTextEditor.svelte
··· 1 + <script lang="ts"> 2 + import { onMount, onDestroy } from 'svelte'; 3 + import { Editor, mergeAttributes, type Content } from '@tiptap/core'; 4 + import StarterKit from '@tiptap/starter-kit'; 5 + import Placeholder from '@tiptap/extension-placeholder'; 6 + import Image from '@tiptap/extension-image'; 7 + import { all, createLowlight } from 'lowlight'; 8 + import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'; 9 + import BubbleMenu from '@tiptap/extension-bubble-menu'; 10 + import Underline from '@tiptap/extension-underline'; 11 + import RichTextEditorMenu from './RichTextEditorMenu.svelte'; 12 + import type { RichTextTypes } from '.'; 13 + import RichTextEditorLinkMenu from './RichTextEditorLinkMenu.svelte'; 14 + import Slash, { suggestion } from './slash-menu'; 15 + import Typography from '@tiptap/extension-typography'; 16 + import { Markdown } from '@tiptap/markdown'; 17 + import { RichTextLink } from './RichTextLink'; 18 + 19 + import './code.css'; 20 + import { cn } from '@foxui/core'; 21 + import { ImageUploadNode } from './image-upload/ImageUploadNode'; 22 + import { Transaction } from '@tiptap/pm/state'; 23 + 24 + let { 25 + content = $bindable({}), 26 + placeholder = 'Write or press / for commands', 27 + editor = $bindable(null), 28 + ref = $bindable(null), 29 + class: className, 30 + onupdate, 31 + ontransaction 32 + }: { 33 + content?: Content; 34 + placeholder?: string; 35 + editor?: Editor | null; 36 + ref?: HTMLDivElement | null; 37 + class?: string; 38 + onupdate?: (content: Content, context: { editor: Editor; transaction: Transaction }) => void; 39 + ontransaction?: () => void; 40 + } = $props(); 41 + 42 + const lowlight = createLowlight(all); 43 + 44 + let hasFocus = true; 45 + 46 + let menu: HTMLElement | null = $state(null); 47 + let menuLink: HTMLElement | null = $state(null); 48 + 49 + let selectedType: RichTextTypes = $state('paragraph'); 50 + 51 + let isBold = $state(false); 52 + let isItalic = $state(false); 53 + let isUnderline = $state(false); 54 + let isStrikethrough = $state(false); 55 + let isLink = $state(false); 56 + let isImage = $state(false); 57 + 58 + const CustomImage = Image.extend({ 59 + // addAttributes(this) { 60 + // return { 61 + // inline: true, 62 + // allowBase64: true, 63 + // HTMLAttributes: {}, 64 + // uploadImageHandler: { default: undefined } 65 + // }; 66 + // }, 67 + renderHTML({ HTMLAttributes }) { 68 + const attrs = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { 69 + uploadImageHandler: undefined 70 + }); 71 + const isLocal = attrs['data-local'] === 'true'; 72 + 73 + // if (isLocal) { 74 + // // For local images, wrap in a container with a label 75 + // return [ 76 + // 'div', 77 + // { class: 'image-container' }, 78 + // ['img', { ...attrs, class: `${attrs.class || ''} local-image` }], 79 + // ['span', { class: 'local-image-label' }, 'Local preview'] 80 + // ]; 81 + // } 82 + 83 + console.log('attrs', attrs); 84 + 85 + // For regular images, just return the img tag 86 + return ['img', attrs]; 87 + } 88 + }); 89 + 90 + onMount(() => { 91 + if (!ref) return; 92 + 93 + let extensions = [ 94 + StarterKit.configure({ 95 + dropcursor: { 96 + class: 'text-accent-500/30 rounded-2xl', 97 + width: 2 98 + }, 99 + codeBlock: false, 100 + heading: { 101 + levels: [1, 2, 3] 102 + } 103 + }), 104 + Placeholder.configure({ 105 + placeholder: ({ node }) => { 106 + // only show in paragraphs 107 + if (node.type.name === 'paragraph' || node.type.name === 'heading') { 108 + return placeholder; 109 + } 110 + return ''; 111 + } 112 + }), 113 + CustomImage.configure({ 114 + HTMLAttributes: { 115 + class: 'max-w-full object-contain relative rounded-2xl' 116 + }, 117 + allowBase64: true 118 + }), 119 + CodeBlockLowlight.configure({ 120 + lowlight, 121 + defaultLanguage: 'js' 122 + }), 123 + BubbleMenu.configure({ 124 + element: menu, 125 + shouldShow: ({ editor }) => { 126 + // dont show if image selected or no selection or is code block 127 + return ( 128 + !editor.isActive('image') && 129 + !editor.view.state.selection.empty && 130 + !editor.isActive('codeBlock') && 131 + !editor.isActive('link') && 132 + !editor.isActive('imageUpload') 133 + ); 134 + }, 135 + pluginKey: 'bubble-menu-marks' 136 + }), 137 + BubbleMenu.configure({ 138 + element: menuLink, 139 + shouldShow: ({ editor }) => { 140 + // only show if link is selected 141 + return editor.isActive('link') && !editor.view.state.selection.empty; 142 + }, 143 + pluginKey: 'bubble-menu-links' 144 + }), 145 + Underline.configure({}), 146 + RichTextLink.configure({ 147 + openOnClick: false, 148 + autolink: true, 149 + defaultProtocol: 'https' 150 + }), 151 + Slash.configure({ 152 + suggestion: suggestion({ 153 + char: '/', 154 + pluginKey: 'slash', 155 + switchTo, 156 + processImageFile 157 + }) 158 + }), 159 + Typography.configure(), 160 + Markdown.configure(), 161 + ImageUploadNode.configure({}) 162 + ]; 163 + 164 + editor = new Editor({ 165 + element: ref, 166 + extensions, 167 + editorProps: { 168 + attributes: { 169 + class: 'outline-none' 170 + } 171 + }, 172 + onUpdate: (ctx) => { 173 + content = ctx.editor.getJSON(); 174 + onupdate?.(content, ctx); 175 + }, 176 + onFocus: () => { 177 + hasFocus = true; 178 + }, 179 + onBlur: () => { 180 + hasFocus = false; 181 + }, 182 + onTransaction: (ctx) => { 183 + isBold = ctx.editor.isActive('bold'); 184 + isItalic = ctx.editor.isActive('italic'); 185 + isUnderline = ctx.editor.isActive('underline'); 186 + isStrikethrough = ctx.editor.isActive('strike'); 187 + isLink = ctx.editor.isActive('link'); 188 + isImage = ctx.editor.isActive('image'); 189 + 190 + if (ctx.editor.isActive('heading', { level: 1 })) { 191 + selectedType = 'heading-1'; 192 + } else if (ctx.editor.isActive('heading', { level: 2 })) { 193 + selectedType = 'heading-2'; 194 + } else if (ctx.editor.isActive('heading', { level: 3 })) { 195 + selectedType = 'heading-3'; 196 + } else if (ctx.editor.isActive('blockquote')) { 197 + selectedType = 'blockquote'; 198 + } else if (ctx.editor.isActive('code')) { 199 + selectedType = 'code'; 200 + } else if (ctx.editor.isActive('bulletList')) { 201 + selectedType = 'bullet-list'; 202 + } else if (ctx.editor.isActive('orderedList')) { 203 + selectedType = 'ordered-list'; 204 + } else { 205 + selectedType = 'paragraph'; 206 + } 207 + ontransaction?.(); 208 + }, 209 + content 210 + }); 211 + }); 212 + 213 + // Flag to track whether a file is being dragged over the drop area 214 + let isDragOver = $state(false); 215 + 216 + // Store local image files for later upload 217 + let localImages: Map<string, File> = $state(new Map()); 218 + 219 + // Track which image URLs in the editor are local previews 220 + let localImageUrls: Set<string> = $state(new Set()); 221 + 222 + // Process image file to create a local preview 223 + async function processImageFile(file: File) { 224 + if (!editor) { 225 + console.warn('Tiptap editor not initialized'); 226 + return; 227 + } 228 + 229 + try { 230 + const localUrl = URL.createObjectURL(file); 231 + 232 + localImages.set(localUrl, file); 233 + localImageUrls.add(localUrl); 234 + 235 + //editor.commands.setImageUploadNode(); 236 + editor 237 + .chain() 238 + .focus() 239 + .setImageUploadNode({ 240 + preview: localUrl 241 + }) 242 + .run(); 243 + 244 + // wait 2 seconds 245 + // await new Promise((resolve) => setTimeout(resolve, 500)); 246 + 247 + // content = editor.getJSON(); 248 + 249 + // console.log('replacing image url in content'); 250 + // replaceImageUrlInContent(content, localUrl, 'https://picsum.photos/200/300'); 251 + // editor.commands.setContent(content); 252 + } catch (error) { 253 + console.error('Error creating image preview:', error); 254 + } 255 + } 256 + 257 + const handlePaste = (event: ClipboardEvent) => { 258 + const items = event.clipboardData?.items; 259 + if (!items) return; 260 + // Check for image data in clipboard 261 + for (const item of Array.from(items)) { 262 + if (!item.type.startsWith('image/')) continue; 263 + const file = item.getAsFile(); 264 + if (!file) continue; 265 + event.preventDefault(); 266 + processImageFile(file); 267 + return; 268 + } 269 + }; 270 + 271 + function handleDragOver(event: DragEvent) { 272 + event.preventDefault(); 273 + event.stopPropagation(); 274 + isDragOver = true; 275 + } 276 + function handleDragLeave(event: DragEvent) { 277 + event.preventDefault(); 278 + event.stopPropagation(); 279 + isDragOver = false; 280 + } 281 + function handleDrop(event: DragEvent) { 282 + event.preventDefault(); 283 + event.stopPropagation(); 284 + isDragOver = false; 285 + if (!event.dataTransfer?.files?.length) return; 286 + const file = event.dataTransfer.files[0]; 287 + if (file?.type.startsWith('image/')) { 288 + processImageFile(file); 289 + } 290 + } 291 + 292 + onDestroy(() => { 293 + for (const localUrl of localImageUrls) { 294 + URL.revokeObjectURL(localUrl); 295 + } 296 + 297 + editor?.destroy(); 298 + }); 299 + 300 + let link = $state(''); 301 + 302 + let linkInput: HTMLInputElement | null = $state(null); 303 + 304 + function clickedLink() { 305 + if (isLink) { 306 + //tiptap?.chain().focus().unsetLink().run(); 307 + // get current link 308 + link = editor?.getAttributes('link').href; 309 + 310 + setTimeout(() => { 311 + linkInput?.focus(); 312 + }, 100); 313 + } else { 314 + link = ''; 315 + // set link 316 + editor?.chain().focus().setLink({ href: link }).run(); 317 + 318 + setTimeout(() => { 319 + linkInput?.focus(); 320 + }, 100); 321 + } 322 + } 323 + 324 + function switchTo(value: RichTextTypes) { 325 + editor?.chain().focus().setParagraph().run(); 326 + 327 + if (value === 'heading-1') { 328 + editor?.chain().focus().setNode('heading', { level: 1 }).run(); 329 + } else if (value === 'heading-2') { 330 + editor?.chain().focus().setNode('heading', { level: 2 }).run(); 331 + } else if (value === 'heading-3') { 332 + editor?.chain().focus().setNode('heading', { level: 3 }).run(); 333 + } else if (value === 'blockquote') { 334 + editor?.chain().focus().setBlockquote().run(); 335 + } else if (value === 'code') { 336 + editor?.chain().focus().setCodeBlock().run(); 337 + } else if (value === 'bullet-list') { 338 + editor?.chain().focus().toggleBulletList().run(); 339 + } else if (value === 'ordered-list') { 340 + editor?.chain().focus().toggleOrderedList().run(); 341 + } 342 + } 343 + </script> 344 + 345 + <div 346 + bind:this={ref} 347 + class={cn('relative flex-1', className)} 348 + onpaste={handlePaste} 349 + ondragover={handleDragOver} 350 + ondragleave={handleDragLeave} 351 + ondrop={handleDrop} 352 + role="region" 353 + ></div> 354 + 355 + <RichTextEditorMenu 356 + bind:ref={menu} 357 + {editor} 358 + {isBold} 359 + {isItalic} 360 + {isUnderline} 361 + {isStrikethrough} 362 + {isLink} 363 + {isImage} 364 + {clickedLink} 365 + {processImageFile} 366 + {switchTo} 367 + bind:selectedType 368 + /> 369 + 370 + <RichTextEditorLinkMenu bind:ref={menuLink} {editor} bind:link bind:linkInput /> 371 + 372 + <style> 373 + :global(.tiptap) { 374 + :first-child { 375 + margin-top: 0; 376 + } 377 + 378 + :global(img) { 379 + display: block; 380 + height: auto; 381 + margin: 1.5rem 0; 382 + max-width: 100%; 383 + 384 + &.ProseMirror-selectednode { 385 + outline: 3px solid var(--color-accent-500); 386 + } 387 + } 388 + 389 + :global(div[data-type='image-upload']) { 390 + &.ProseMirror-selectednode { 391 + outline: 3px solid var(--color-accent-500); 392 + } 393 + } 394 + 395 + :global(blockquote p:first-of-type::before) { 396 + content: none; 397 + } 398 + 399 + :global(blockquote p:last-of-type::after) { 400 + content: none; 401 + } 402 + 403 + :global(blockquote p) { 404 + font-style: normal; 405 + } 406 + } 407 + 408 + :global(.tiptap .is-empty::before) { 409 + color: var(--color-base-500); 410 + content: attr(data-placeholder); 411 + float: left; 412 + height: 0; 413 + pointer-events: none; 414 + } 415 + </style>
+111
src/lib/components/rich-text-editor/RichTextEditorLinkMenu.svelte
··· 1 + <script lang="ts"> 2 + import { Button, Input } from '@foxui/core'; 3 + import type { Editor } from '@tiptap/core'; 4 + 5 + let { 6 + editor, 7 + link = $bindable(''), 8 + ref = $bindable(null), 9 + linkInput = $bindable(null) 10 + }: { 11 + editor: Editor | null; 12 + link: string; 13 + ref: HTMLElement | null; 14 + linkInput: HTMLInputElement | null; 15 + } = $props(); 16 + 17 + function processLink(link: string) { 18 + return link.includes(':') ? link : `http://${link}`; 19 + } 20 + </script> 21 + 22 + <div 23 + bind:this={ref} 24 + style="visibility: hidden; opacity: 0;" 25 + class="menu bg-base-50 dark:bg-base-900 relative w-fit rounded-2xl px-1 py-1 shadow-lg backdrop-blur-sm" 26 + > 27 + <div class="flex items-center gap-1"> 28 + <Input 29 + bind:ref={linkInput} 30 + sizeVariant="sm" 31 + bind:value={link} 32 + placeholder="Enter link" 33 + onblur={() => { 34 + if (link === '') { 35 + editor?.chain().focus().extendMarkRange('link').unsetLink().run(); 36 + } else { 37 + editor 38 + ?.chain() 39 + .focus() 40 + .extendMarkRange('link') 41 + .setLink({ href: processLink(link) }) 42 + .run(); 43 + } 44 + }} 45 + onkeydown={(e: KeyboardEvent) => { 46 + if (e.key === 'Enter') { 47 + if (link === '') { 48 + editor?.chain().focus().extendMarkRange('link').unsetLink().run(); 49 + } else { 50 + editor 51 + ?.chain() 52 + .focus() 53 + .extendMarkRange('link') 54 + .setLink({ href: processLink(link) }) 55 + .run(); 56 + } 57 + } 58 + }} 59 + /> 60 + <Button 61 + size="iconSm" 62 + onclick={() => { 63 + if (link === '') { 64 + editor?.chain().focus().extendMarkRange('link').unsetLink().run(); 65 + } else { 66 + editor 67 + ?.chain() 68 + .focus() 69 + .extendMarkRange('link') 70 + .setLink({ href: processLink(link) }) 71 + .run(); 72 + } 73 + }} 74 + > 75 + <svg 76 + xmlns="http://www.w3.org/2000/svg" 77 + fill="none" 78 + viewBox="0 0 24 24" 79 + stroke-width="1.5" 80 + stroke="currentColor" 81 + class="size-6" 82 + > 83 + <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /> 84 + </svg> 85 + 86 + <span class="sr-only">save link</span> 87 + </Button> 88 + <Button 89 + size="iconSm" 90 + onclick={() => { 91 + editor?.chain().focus().extendMarkRange('link').unsetLink().run(); 92 + }} 93 + variant="ghost" 94 + > 95 + <svg 96 + xmlns="http://www.w3.org/2000/svg" 97 + fill="none" 98 + viewBox="0 0 24 24" 99 + stroke-width="1.5" 100 + stroke="currentColor" 101 + > 102 + <path 103 + stroke-linecap="round" 104 + stroke-linejoin="round" 105 + d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" 106 + /> 107 + </svg> 108 + <span class="sr-only">remove link</span> 109 + </Button> 110 + </div> 111 + </div>
+182
src/lib/components/rich-text-editor/RichTextEditorMenu.svelte
··· 1 + <script lang="ts"> 2 + import { cn, Toggle, toggleVariants, Tooltip } from '@foxui/core'; 3 + import type { Editor } from '@tiptap/core'; 4 + import Select from './Select.svelte'; 5 + import type { RichTextTypes } from '.'; 6 + 7 + let { 8 + editor, 9 + isBold, 10 + isImage, 11 + isItalic, 12 + isUnderline, 13 + isStrikethrough, 14 + isLink, 15 + clickedLink, 16 + selectedType = $bindable('paragraph'), 17 + ref = $bindable(null), 18 + processImageFile, 19 + switchTo 20 + }: { 21 + editor: Editor | null; 22 + isBold: boolean; 23 + isImage: boolean; 24 + isItalic: boolean; 25 + isUnderline: boolean; 26 + isStrikethrough: boolean; 27 + isLink: boolean; 28 + clickedLink: () => void; 29 + selectedType: RichTextTypes; 30 + ref: HTMLElement | null; 31 + processImageFile: (file: File, input: HTMLInputElement) => void; 32 + switchTo: (value: RichTextTypes) => void; 33 + } = $props(); 34 + 35 + function handleFileProcess(event: Event) { 36 + const input = event.target as HTMLInputElement; 37 + if (!input.files?.length) return; 38 + const file = input.files[0]; 39 + if (!file || !file.type.startsWith('image/')) return; 40 + processImageFile(file, input); 41 + } 42 + 43 + let fileInput = $state<HTMLInputElement | null>(null); 44 + </script> 45 + 46 + <div 47 + bind:this={ref} 48 + style="visibility: hidden; opacity: 0;" 49 + class="bg-base-50 dark:bg-base-900 border-base-500/20 dark:border-base-700/20 relative w-fit rounded-2xl border px-1 py-1 shadow-lg backdrop-blur-sm" 50 + > 51 + <Select 52 + onValueChange={(value) => { 53 + switchTo(value as RichTextTypes); 54 + }} 55 + type="single" 56 + items={[ 57 + { value: 'paragraph', label: 'Text' }, 58 + { value: 'heading-1', label: 'Heading 1' }, 59 + { value: 'heading-2', label: 'Heading 2' }, 60 + { value: 'heading-3', label: 'Heading 3' }, 61 + { value: 'blockquote', label: 'Blockquote' }, 62 + { value: 'code', label: 'Code Block' }, 63 + { value: 'bullet-list', label: 'Bullet List' }, 64 + { value: 'ordered-list', label: 'Ordered List' } 65 + ]} 66 + bind:value={selectedType} 67 + /> 68 + <!-- <Tooltip withContext text="Bold" delayDuration={0}> 69 + {#snippet child({ props })} --> 70 + <Toggle 71 + size="sm" 72 + onclick={() => editor?.chain().focus().toggleBold().run()} 73 + bind:pressed={() => isBold, (bold) => {}} 74 + > 75 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 76 + <path 77 + fill-rule="evenodd" 78 + d="M5.246 3.744a.75.75 0 0 1 .75-.75h7.125a4.875 4.875 0 0 1 3.346 8.422 5.25 5.25 0 0 1-2.97 9.58h-7.5a.75.75 0 0 1-.75-.75V3.744Zm7.125 6.75a2.625 2.625 0 0 0 0-5.25H8.246v5.25h4.125Zm-4.125 2.251v6h4.5a3 3 0 0 0 0-6h-4.5Z" 79 + clip-rule="evenodd" 80 + /> 81 + </svg> 82 + 83 + <span class="sr-only">Bold</span> 84 + </Toggle> 85 + <!-- {/snippet} 86 + </Tooltip> --> 87 + <Toggle 88 + size="sm" 89 + onclick={() => editor?.chain().focus().toggleItalic().run()} 90 + bind:pressed={() => isItalic, (italic) => {}} 91 + > 92 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 93 + <path 94 + fill-rule="evenodd" 95 + d="M10.497 3.744a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-3.275l-5.357 15.002h2.632a.75.75 0 1 1 0 1.5h-7.5a.75.75 0 1 1 0-1.5h3.275l5.357-15.002h-2.632a.75.75 0 0 1-.75-.75Z" 96 + clip-rule="evenodd" 97 + /> 98 + </svg> 99 + 100 + <span class="sr-only">Italic</span> 101 + </Toggle> 102 + 103 + <Toggle 104 + size="sm" 105 + onclick={() => editor?.chain().focus().toggleUnderline().run()} 106 + bind:pressed={() => isUnderline, (underline) => {}} 107 + > 108 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 109 + <path 110 + fill-rule="evenodd" 111 + d="M5.995 2.994a.75.75 0 0 1 .75.75v7.5a5.25 5.25 0 1 0 10.5 0v-7.5a.75.75 0 0 1 1.5 0v7.5a6.75 6.75 0 1 1-13.5 0v-7.5a.75.75 0 0 1 .75-.75Zm-3 17.252a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5h-16.5a.75.75 0 0 1-.75-.75Z" 112 + clip-rule="evenodd" 113 + /> 114 + </svg> 115 + 116 + <span class="sr-only">Underline</span> 117 + </Toggle> 118 + 119 + <Toggle 120 + size="sm" 121 + onclick={() => editor?.chain().focus().toggleStrike().run()} 122 + bind:pressed={() => isStrikethrough, (strikethrough) => {}} 123 + > 124 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 125 + <path 126 + fill-rule="evenodd" 127 + d="M9.657 4.728c-1.086.385-1.766 1.057-1.979 1.85-.214.8.046 1.733.81 2.616.746.862 1.93 1.612 3.388 2.003.07.019.14.037.21.053h8.163a.75.75 0 0 1 0 1.5h-8.24a.66.66 0 0 1-.02 0H3.75a.75.75 0 0 1 0-1.5h4.78a7.108 7.108 0 0 1-1.175-1.074C6.372 9.042 5.849 7.61 6.229 6.19c.377-1.408 1.528-2.38 2.927-2.876 1.402-.497 3.127-.55 4.855-.086A8.937 8.937 0 0 1 16.94 4.6a.75.75 0 0 1-.881 1.215 7.437 7.437 0 0 0-2.436-1.14c-1.473-.394-2.885-.331-3.966.052Zm6.533 9.632a.75.75 0 0 1 1.03.25c.592.974.846 2.094.55 3.2-.378 1.408-1.529 2.38-2.927 2.876-1.402.497-3.127.55-4.855.087-1.712-.46-3.168-1.354-4.134-2.47a.75.75 0 0 1 1.134-.982c.746.862 1.93 1.612 3.388 2.003 1.473.394 2.884.331 3.966-.052 1.085-.384 1.766-1.056 1.978-1.85.169-.628.046-1.33-.381-2.032a.75.75 0 0 1 .25-1.03Z" 128 + clip-rule="evenodd" 129 + /> 130 + </svg> 131 + 132 + <span class="sr-only">Strikethrough</span> 133 + </Toggle> 134 + 135 + <Toggle 136 + size="sm" 137 + onclick={() => { 138 + clickedLink(); 139 + }} 140 + bind:pressed={() => isLink, (link) => {}} 141 + > 142 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 143 + <path 144 + fill-rule="evenodd" 145 + d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z" 146 + clip-rule="evenodd" 147 + /> 148 + </svg> 149 + 150 + <span class="sr-only">Link</span> 151 + </Toggle> 152 + 153 + <!-- <Toggle 154 + size="sm" 155 + onclick={() => { 156 + fileInput?.click(); 157 + }} 158 + bind:pressed={() => isImage, (image) => {}} 159 + > 160 + <svg 161 + xmlns="http://www.w3.org/2000/svg" 162 + viewBox="0 0 24 24" 163 + fill="none" 164 + stroke="currentColor" 165 + stroke-width="2" 166 + stroke-linecap="round" 167 + stroke-linejoin="round" 168 + ><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path 169 + d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" 170 + /></svg 171 + > 172 + </Toggle> --> 173 + 174 + <input 175 + type="file" 176 + accept="image/*" 177 + class="hidden" 178 + onchange={handleFileProcess} 179 + tabindex="-1" 180 + bind:this={fileInput} 181 + /> 182 + </div>
+126
src/lib/components/rich-text-editor/RichTextLink.ts
··· 1 + // from https://github.com/Doist/typist/blob/main/src/extensions/rich-text/rich-text-link.ts 2 + import { InputRule, markInputRule, markPasteRule, PasteRule } from '@tiptap/core'; 3 + import { Link } from '@tiptap/extension-link'; 4 + 5 + import type { LinkOptions } from '@tiptap/extension-link'; 6 + 7 + /** 8 + * The input regex for Markdown links with title support, and multiple quotation marks (required 9 + * in case the `Typography` extension is being included). 10 + */ 11 + const inputRegex = /(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)$/i; 12 + 13 + /** 14 + * The paste regex for Markdown links with title support, and multiple quotation marks (required 15 + * in case the `Typography` extension is being included). 16 + */ 17 + const pasteRegex = /(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)/gi; 18 + 19 + /** 20 + * Input rule built specifically for the `Link` extension, which ignores the auto-linked URL in 21 + * parentheses (e.g., `(https://doist.dev)`). 22 + * 23 + * @see https://github.com/ueberdosis/tiptap/discussions/1865 24 + */ 25 + function linkInputRule(config: Parameters<typeof markInputRule>[0]) { 26 + const defaultMarkInputRule = markInputRule(config); 27 + 28 + return new InputRule({ 29 + find: config.find, 30 + handler(props) { 31 + const { tr } = props.state; 32 + 33 + defaultMarkInputRule.handler(props); 34 + tr.setMeta('preventAutolink', true); 35 + } 36 + }); 37 + } 38 + 39 + /** 40 + * Paste rule built specifically for the `Link` extension, which ignores the auto-linked URL in 41 + * parentheses (e.g., `(https://doist.dev)`). This extension was inspired from the multiple 42 + * implementations found in a Tiptap discussion at GitHub. 43 + * 44 + * @see https://github.com/ueberdosis/tiptap/discussions/1865 45 + */ 46 + function linkPasteRule(config: Parameters<typeof markPasteRule>[0]) { 47 + const defaultMarkPasteRule = markPasteRule(config); 48 + 49 + return new PasteRule({ 50 + find: config.find, 51 + handler(props) { 52 + const { tr } = props.state; 53 + 54 + defaultMarkPasteRule.handler(props); 55 + tr.setMeta('preventAutolink', true); 56 + } 57 + }); 58 + } 59 + 60 + /** 61 + * The options available to customize the `RichTextLink` extension. 62 + */ 63 + type RichTextLinkOptions = LinkOptions; 64 + 65 + /** 66 + * Custom extension that extends the built-in `Link` extension to add additional input/paste rules 67 + * for converting the Markdown link syntax (i.e. `[Doist](https://doist.com)`) into links, and also 68 + * adds support for the `title` attribute. 69 + */ 70 + const RichTextLink = Link.extend<RichTextLinkOptions>({ 71 + inclusive: false, 72 + addOptions() { 73 + return { 74 + ...this.parent?.(), 75 + openOnClick: 'whenNotEditable' as const 76 + } as RichTextLinkOptions; 77 + }, 78 + addAttributes() { 79 + return { 80 + ...this.parent?.(), 81 + title: { 82 + default: null 83 + } 84 + }; 85 + }, 86 + addInputRules() { 87 + return [ 88 + linkInputRule({ 89 + find: inputRegex, 90 + type: this.type, 91 + 92 + // We need to use `pop()` to remove the last capture groups from the match to 93 + // satisfy Tiptap's `markPasteRule` expectation of having the content as the last 94 + // capture group in the match (this makes the attribute order important) 95 + getAttributes(match) { 96 + return { 97 + title: match.pop()?.trim(), 98 + href: match.pop()?.trim() 99 + }; 100 + } 101 + }) 102 + ]; 103 + }, 104 + addPasteRules() { 105 + return [ 106 + linkPasteRule({ 107 + find: pasteRegex, 108 + type: this.type, 109 + 110 + // We need to use `pop()` to remove the last capture groups from the match to 111 + // satisfy Tiptap's `markInputRule` expectation of having the content as the last 112 + // capture group in the match (this makes the attribute order important) 113 + getAttributes(match) { 114 + return { 115 + title: match.pop()?.trim(), 116 + href: match.pop()?.trim() 117 + }; 118 + } 119 + }) 120 + ]; 121 + } 122 + }); 123 + 124 + export { RichTextLink }; 125 + 126 + export type { RichTextLinkOptions };
+72
src/lib/components/rich-text-editor/Select.svelte
··· 1 + <script lang="ts"> 2 + import { Button, cn, toggleVariants } from '@foxui/core'; 3 + import { Select, type WithoutChildren } from 'bits-ui'; 4 + import Icon from './Icon.svelte'; 5 + 6 + type Props = WithoutChildren<Select.RootProps> & { 7 + placeholder?: string; 8 + items: { value: string; label: string; disabled?: boolean }[]; 9 + contentProps?: WithoutChildren<Select.ContentProps>; 10 + }; 11 + 12 + let { value = $bindable(), items, contentProps, placeholder, ...restProps }: Props = $props(); 13 + </script> 14 + 15 + <Select.Root bind:value={value as never} {...restProps}> 16 + <Select.Trigger> 17 + {#snippet child({ props })} 18 + <button {...props} class={cn(toggleVariants({ size: 'sm' }), 'gap-1')}> 19 + {#if value} 20 + <Icon name={value as any} /> 21 + {:else} 22 + <span class="size-3.5">?</span> 23 + {/if} 24 + 25 + <svg 26 + xmlns="http://www.w3.org/2000/svg" 27 + fill="none" 28 + viewBox="0 0 24 24" 29 + stroke-width="1.5" 30 + stroke="currentColor" 31 + class="!size-2.5" 32 + > 33 + <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" /> 34 + </svg> 35 + </button> 36 + {/snippet} 37 + </Select.Trigger> 38 + <Select.Portal> 39 + <Select.Content 40 + {...contentProps} 41 + class={cn( 42 + 'bg-base-50/50 border-base-500/20 overflow-hidden rounded-2xl border shadow-lg backdrop-blur-xl', 43 + 'dark:bg-base-900/50 dark:border-base-500/10', 44 + 'motion-safe:animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 45 + contentProps?.class 46 + )} 47 + sideOffset={6} 48 + > 49 + <Select.ScrollUpButton>up</Select.ScrollUpButton> 50 + <Select.Viewport class="divide-base-300/30 dark:divide-base-800 divide-y text-sm"> 51 + {#each items as { value, label, disabled } (value)} 52 + <Select.Item {value} {label} {disabled}> 53 + {#snippet children({ selected })} 54 + <div 55 + class={cn( 56 + 'text-base-900 dark:text-base-200 group relative isolate flex min-w-28 cursor-pointer items-center gap-3 px-3 py-2 font-medium [&_svg]:size-3.5', 57 + selected 58 + ? 'text-accent-700 dark:text-accent-400 bg-accent-500/10' 59 + : 'hover:bg-accent-500/10' 60 + )} 61 + > 62 + <Icon name={value as any} /> 63 + {label} 64 + </div> 65 + {/snippet} 66 + </Select.Item> 67 + {/each} 68 + </Select.Viewport> 69 + <Select.ScrollDownButton>down</Select.ScrollDownButton> 70 + </Select.Content> 71 + </Select.Portal> 72 + </Select.Root>
+175
src/lib/components/rich-text-editor/code.css
··· 1 + /* GitHub Light theme for code blocks */ 2 + .tiptap { 3 + :first-child { 4 + margin-top: 0; 5 + } 6 + 7 + pre { 8 + background: #f6f8fa; 9 + border-radius: 1rem; 10 + color: #1f2328; 11 + font-family: 'JetBrainsMono', monospace; 12 + border: 1px solid #d1d9e0; 13 + margin: 1.5rem 0; 14 + padding: 0.75rem 1rem; 15 + 16 + code { 17 + background: none; 18 + color: inherit; 19 + font-size: 0.8rem; 20 + padding: 0; 21 + } 22 + 23 + .hljs-comment, 24 + .hljs-quote { 25 + color: #59636e; 26 + font-style: italic; 27 + } 28 + 29 + .hljs-keyword, 30 + .hljs-selector-tag { 31 + color: #cf222e; 32 + } 33 + 34 + .hljs-tag, 35 + .hljs-name { 36 + color: #116329; 37 + } 38 + 39 + .hljs-attribute { 40 + color: #0550ae; 41 + } 42 + 43 + .hljs-variable, 44 + .hljs-template-variable, 45 + .hljs-regexp, 46 + .hljs-link { 47 + color: #0550ae; 48 + } 49 + 50 + .hljs-number, 51 + .hljs-literal { 52 + color: #0550ae; 53 + } 54 + 55 + .hljs-string, 56 + .hljs-symbol, 57 + .hljs-bullet { 58 + color: #0a3069; 59 + } 60 + 61 + .hljs-title, 62 + .hljs-section { 63 + color: #0550ae; 64 + font-weight: 600; 65 + } 66 + 67 + .hljs-type, 68 + .hljs-built_in, 69 + .hljs-builtin-name, 70 + .hljs-params { 71 + color: #953800; 72 + } 73 + 74 + .hljs-meta { 75 + color: #0550ae; 76 + } 77 + 78 + .hljs-selector-id, 79 + .hljs-selector-class { 80 + color: #0550ae; 81 + } 82 + 83 + .hljs-emphasis { 84 + font-style: italic; 85 + } 86 + 87 + .hljs-strong { 88 + font-weight: 700; 89 + } 90 + } 91 + } 92 + 93 + /* GitHub Dark theme for code blocks */ 94 + .dark .tiptap { 95 + pre { 96 + background: #161b22; 97 + color: #e6edf3; 98 + border: 1px solid #30363d; 99 + 100 + code { 101 + background: none; 102 + color: inherit; 103 + font-size: 0.8rem; 104 + padding: 0; 105 + } 106 + 107 + .hljs-comment, 108 + .hljs-quote { 109 + color: #8b949e; 110 + font-style: italic; 111 + } 112 + 113 + .hljs-keyword, 114 + .hljs-selector-tag { 115 + color: #ff7b72; 116 + } 117 + 118 + .hljs-tag, 119 + .hljs-name { 120 + color: #7ee787; 121 + } 122 + 123 + .hljs-attribute { 124 + color: #79c0ff; 125 + } 126 + 127 + .hljs-variable, 128 + .hljs-template-variable, 129 + .hljs-regexp, 130 + .hljs-link { 131 + color: #79c0ff; 132 + } 133 + 134 + .hljs-number, 135 + .hljs-literal { 136 + color: #79c0ff; 137 + } 138 + 139 + .hljs-string, 140 + .hljs-symbol, 141 + .hljs-bullet { 142 + color: #a5d6ff; 143 + } 144 + 145 + .hljs-title, 146 + .hljs-section { 147 + color: #d2a8ff; 148 + font-weight: 600; 149 + } 150 + 151 + .hljs-type, 152 + .hljs-built_in, 153 + .hljs-builtin-name, 154 + .hljs-params { 155 + color: #ffa657; 156 + } 157 + 158 + .hljs-meta { 159 + color: #79c0ff; 160 + } 161 + 162 + .hljs-selector-id, 163 + .hljs-selector-class { 164 + color: #79c0ff; 165 + } 166 + 167 + .hljs-emphasis { 168 + font-style: italic; 169 + } 170 + 171 + .hljs-strong { 172 + font-weight: 700; 173 + } 174 + } 175 + }
+28
src/lib/components/rich-text-editor/image-upload/ImageUploadComponent.svelte
··· 1 + <script lang="ts"> 2 + import type { NodeViewProps } from '@tiptap/core'; 3 + import { onMount } from 'svelte'; 4 + import { NodeViewWrapper } from 'svelte-tiptap'; 5 + 6 + let props: NodeViewProps = $props(); 7 + 8 + onMount(() => { 9 + const pos = props.getPos(); 10 + if (pos == null) return; 11 + 12 + props.deleteNode(); 13 + props.editor 14 + .chain() 15 + .focus() 16 + .insertContentAt(pos, [ 17 + { 18 + type: 'image', 19 + attrs: { src: props.node.attrs.preview, alt: 'image', title: 'image' } 20 + } 21 + ]) 22 + .run(); 23 + }); 24 + </script> 25 + 26 + <NodeViewWrapper> 27 + <img src={props.node.attrs.preview} alt="Upload preview" /> 28 + </NodeViewWrapper>
+115
src/lib/components/rich-text-editor/image-upload/ImageUploadNode.ts
··· 1 + import { Node, mergeAttributes } from '@tiptap/core'; 2 + import { SvelteNodeViewRenderer } from 'svelte-tiptap'; 3 + 4 + import ImageUploadComponent from './ImageUploadComponent.svelte'; 5 + 6 + export type UploadFunction = ( 7 + file: File, 8 + onProgress?: (event: { progress: number }) => void, 9 + abortSignal?: AbortSignal 10 + ) => Promise<string>; 11 + 12 + export interface ImageUploadNodeOptions { 13 + /** 14 + * Acceptable file types for upload. 15 + * @default 'image/*' 16 + */ 17 + accept?: string; 18 + /** 19 + * Maximum number of files that can be uploaded. 20 + * @default 1 21 + */ 22 + limit?: number; 23 + /** 24 + * Maximum file size in bytes (0 for unlimited). 25 + * @default 0 26 + */ 27 + maxSize?: number; 28 + 29 + /** 30 + * Preview image URL. 31 + */ 32 + preview?: string; 33 + /** 34 + * Function to handle the upload process. 35 + */ 36 + upload?: UploadFunction; 37 + /** 38 + * Callback for upload errors. 39 + */ 40 + onError?: (error: Error) => void; 41 + /** 42 + * Callback for successful uploads. 43 + */ 44 + onSuccess?: (url: string) => void; 45 + } 46 + 47 + declare module '@tiptap/core' { 48 + interface Commands<ReturnType> { 49 + imageUpload: { 50 + setImageUploadNode: (options?: ImageUploadNodeOptions) => ReturnType; 51 + }; 52 + } 53 + } 54 + 55 + export const ImageUploadNode = Node.create<ImageUploadNodeOptions>({ 56 + name: 'imageUpload', 57 + group: 'block', 58 + atom: true, 59 + draggable: true, 60 + selectable: false, 61 + inline: false, 62 + 63 + addAttributes() { 64 + return { 65 + accept: { 66 + default: this.options.accept 67 + }, 68 + limit: { 69 + default: this.options.limit 70 + }, 71 + maxSize: { 72 + default: this.options.maxSize 73 + }, 74 + preview: { 75 + default: this.options.preview 76 + } 77 + }; 78 + }, 79 + 80 + addOptions() { 81 + return { 82 + accept: 'image/*', 83 + limit: 1, 84 + maxSize: 0, 85 + upload: undefined, 86 + onError: undefined, 87 + onSuccess: undefined 88 + }; 89 + }, 90 + 91 + addCommands() { 92 + return { 93 + setImageUploadNode: 94 + (options = {}) => 95 + ({ commands }) => { 96 + return commands.insertContent({ 97 + type: this.name, 98 + attrs: options 99 + }); 100 + } 101 + }; 102 + }, 103 + 104 + parseHTML() { 105 + return [{ tag: 'div[data-type="image-upload"]' }]; 106 + }, 107 + 108 + renderHTML({ HTMLAttributes }) { 109 + return ['div', mergeAttributes({ 'data-type': 'image-upload' }, HTMLAttributes)]; 110 + }, 111 + 112 + addNodeView() { 113 + return SvelteNodeViewRenderer(ImageUploadComponent); 114 + } 115 + });
+11
src/lib/components/rich-text-editor/index.ts
··· 1 + export { default as RichTextEditor } from './RichTextEditor.svelte'; 2 + 3 + export type RichTextTypes = 4 + | 'paragraph' 5 + | 'heading-1' 6 + | 'heading-2' 7 + | 'heading-3' 8 + | 'blockquote' 9 + | 'code' 10 + | 'bullet-list' 11 + | 'ordered-list';
+87
src/lib/components/rich-text-editor/slash-menu/SuggestionSelect.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '@foxui/core'; 3 + import type { Editor, Range } from '@tiptap/core'; 4 + import Icon from '../Icon.svelte'; 5 + import type { RichTextTypes } from '..'; 6 + 7 + type Props = { 8 + items: { 9 + value: RichTextTypes; 10 + label: string; 11 + command: ({ editor, range }: { editor: Editor; range: Range }) => void; 12 + }[]; 13 + range: Range; 14 + editor: Editor; 15 + active?: number; 16 + }; 17 + 18 + let { items, range, editor, active = 0 }: Props = $props(); 19 + 20 + let activeIndex = $state(0); 21 + 22 + export function setItems(value: any[]) { 23 + items = value; 24 + } 25 + 26 + export function setRange(value: Range) { 27 + range = value; 28 + } 29 + 30 + export function onKeyDown(event: KeyboardEvent) { 31 + if (event.repeat) { 32 + return false; 33 + } 34 + switch (event.key) { 35 + case 'ArrowUp': { 36 + if (activeIndex <= 0) { 37 + activeIndex = items.length - 1; 38 + } else { 39 + activeIndex--; 40 + } 41 + return true; 42 + } 43 + case 'ArrowDown': { 44 + if (activeIndex >= items.length - 1) { 45 + activeIndex = 0; 46 + } else { 47 + activeIndex++; 48 + } 49 + return true; 50 + } 51 + case 'Enter': { 52 + const selected = items[activeIndex]; 53 + 54 + if (selected) { 55 + selected.command({ editor, range }); 56 + return true; 57 + } 58 + } 59 + } 60 + 61 + return false; 62 + } 63 + </script> 64 + 65 + <menu 66 + class={cn( 67 + 'bg-base-50/50 border-base-500/20 overflow-hidden rounded-2xl border shadow-lg backdrop-blur-xl', 68 + 'dark:bg-base-900/50 dark:border-base-500/10', 69 + 'motion-safe:animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 70 + 'divide-base-300/30 dark:divide-base-800 divide-y text-sm' 71 + )} 72 + > 73 + {#each items as item, index (item.value)} 74 + <button 75 + onclick={() => item.command({ editor, range })} 76 + class={cn( 77 + 'text-base-900 dark:text-base-200 group relative isolate flex w-full min-w-28 cursor-pointer items-center gap-3 px-3 py-2 font-medium [&_svg]:size-3.5', 78 + activeIndex === index 79 + ? 'text-accent-700 dark:text-accent-400 bg-accent-500/10' 80 + : 'hover:bg-accent-500/10' 81 + )} 82 + > 83 + <Icon name={item.value} /> 84 + {item.label} 85 + </button> 86 + {/each} 87 + </menu>
+199
src/lib/components/rich-text-editor/slash-menu/index.ts
··· 1 + import { Extension } from '@tiptap/core'; 2 + import Suggestion from '@tiptap/suggestion'; 3 + import type { Editor, Range } from '@tiptap/core'; 4 + import { PluginKey } from '@tiptap/pm/state'; 5 + import type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'; 6 + import SuggestionSelect from './SuggestionSelect.svelte'; 7 + import { mount, unmount } from 'svelte'; 8 + import { computePosition, flip, shift, offset } from '@floating-ui/dom'; 9 + import type { RichTextTypes } from '..'; 10 + 11 + export default Extension.create({ 12 + name: 'slash', 13 + 14 + addOptions() { 15 + return { 16 + suggestion: { 17 + char: '/', 18 + 19 + command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => { 20 + props.command({ editor, range }); 21 + } 22 + } 23 + }; 24 + }, 25 + 26 + addProseMirrorPlugins() { 27 + return [ 28 + Suggestion({ 29 + editor: this.editor, 30 + ...this.options.suggestion 31 + }) 32 + ]; 33 + } 34 + }); 35 + 36 + export function suggestion({ 37 + char, 38 + pluginKey, 39 + switchTo, 40 + processImageFile 41 + }: { 42 + char: string; 43 + pluginKey: string; 44 + switchTo: (value: RichTextTypes) => void; 45 + processImageFile: (file: File) => void; 46 + }) { 47 + return { 48 + char, 49 + pluginKey: new PluginKey(pluginKey), 50 + 51 + items: ({ query }: { query: string }) => { 52 + return [ 53 + { 54 + value: 'paragraph', 55 + label: 'Paragraph', 56 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 57 + editor.chain().focus().deleteRange(range).run(); 58 + switchTo('paragraph'); 59 + } 60 + }, 61 + { 62 + value: 'heading-1', 63 + label: 'Heading 1', 64 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 65 + editor.chain().focus().deleteRange(range).run(); 66 + switchTo('heading-1'); 67 + } 68 + }, 69 + { 70 + value: 'heading-2', 71 + label: 'Heading 2', 72 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 73 + editor.chain().focus().deleteRange(range).run(); 74 + switchTo('heading-2'); 75 + } 76 + }, 77 + { 78 + value: 'heading-3', 79 + label: 'Heading 3', 80 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 81 + editor.chain().focus().deleteRange(range).run(); 82 + switchTo('heading-3'); 83 + } 84 + }, 85 + { 86 + value: 'blockquote', 87 + label: 'Blockquote', 88 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 89 + editor.chain().focus().deleteRange(range).run(); 90 + switchTo('blockquote'); 91 + } 92 + }, 93 + { 94 + value: 'code', 95 + label: 'Code Block', 96 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 97 + editor.chain().focus().deleteRange(range).run(); 98 + switchTo('code'); 99 + } 100 + }, 101 + { 102 + value: 'bullet-list', 103 + label: 'Bullet List', 104 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 105 + editor.chain().focus().deleteRange(range).run(); 106 + switchTo('bullet-list'); 107 + } 108 + }, 109 + { 110 + value: 'ordered-list', 111 + label: 'Ordered List', 112 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 113 + editor.chain().focus().deleteRange(range).run(); 114 + switchTo('ordered-list'); 115 + } 116 + }, 117 + { 118 + value: 'image', 119 + label: 'Add Image', 120 + command: ({ editor, range }: { editor: Editor; range: Range }) => { 121 + editor.chain().focus().deleteRange(range).run(); 122 + 123 + const fileInput = document.createElement('input'); 124 + fileInput.type = 'file'; 125 + fileInput.click(); 126 + fileInput.addEventListener('change', (event) => { 127 + const input = event.target as HTMLInputElement; 128 + if (!input.files?.length) return; 129 + const file = input.files[0]; 130 + if (!file?.type.startsWith('image/')) return; 131 + processImageFile(file); 132 + 133 + input.remove(); 134 + }); 135 + } 136 + } 137 + ].filter((item) => item.label.toLowerCase().includes(query.toLowerCase())); 138 + }, 139 + 140 + render: () => { 141 + let component: ReturnType<typeof SuggestionSelect>; 142 + let floatingEl: HTMLElement; 143 + 144 + function updatePosition(clientRect: (() => DOMRect | null) | null | undefined) { 145 + if (!clientRect || !floatingEl) return; 146 + const rect = clientRect(); 147 + if (!rect) return; 148 + 149 + // Create a virtual reference element for floating-ui 150 + const virtualRef = { 151 + getBoundingClientRect: () => rect 152 + }; 153 + 154 + computePosition(virtualRef, floatingEl, { 155 + placement: 'bottom-start', 156 + middleware: [offset(8), flip(), shift({ padding: 8 })] 157 + }).then(({ x, y }) => { 158 + Object.assign(floatingEl.style, { 159 + left: `${x}px`, 160 + top: `${y}px` 161 + }); 162 + }); 163 + } 164 + 165 + return { 166 + onStart: (props: SuggestionProps) => { 167 + floatingEl = document.createElement('div'); 168 + floatingEl.style.position = 'absolute'; 169 + floatingEl.style.zIndex = '50'; 170 + document.body.appendChild(floatingEl); 171 + 172 + component = mount(SuggestionSelect, { 173 + target: floatingEl, 174 + props 175 + }); 176 + 177 + updatePosition(props.clientRect); 178 + }, 179 + onUpdate: (props: SuggestionProps) => { 180 + component.setItems(props.items); 181 + component.setRange(props.range); 182 + updatePosition(props.clientRect); 183 + }, 184 + onKeyDown: (props: SuggestionKeyDownProps) => { 185 + if (props.event.key === 'Escape') { 186 + floatingEl.style.display = 'none'; 187 + return true; 188 + } 189 + 190 + return component.onKeyDown(props.event); 191 + }, 192 + onExit: () => { 193 + unmount(component); 194 + floatingEl.remove(); 195 + } 196 + }; 197 + } 198 + }; 199 + }
+5 -1
src/routes/[[actor=actor]]/blog/+page.server.ts
··· 43 43 const site = record.value.site as string; 44 44 if (!site) continue; 45 45 46 - if (site.startsWith('at://')) { 46 + const rkey = record.uri.split('/').pop(); 47 + 48 + if (site === `at://${did}/site.standard.publication/blento.self`) { 49 + record.value.href = `./blog/${rkey}`; 50 + } else if (site.startsWith('at://')) { 47 51 if (!publications[site]) { 48 52 const siteParts = parseUri(site); 49 53 if (!siteParts) continue;
+3 -3
src/routes/[[actor=actor]]/blog/+page.svelte
··· 72 72 {#if posts.length === 0} 73 73 <p class="text-base-500 dark:text-base-400 py-12 text-center">No blog posts found.</p> 74 74 {:else} 75 - <div class="divide-base-200 dark:divide-base-800 divide-y"> 75 + <div class="divide-base-100 dark:divide-base-900 divide-y"> 76 76 {#each posts as post (post.rkey)} 77 77 {@const coverUrl = getCoverUrl(post.coverImage)} 78 78 <a 79 79 href={post.href} 80 - target="_blank" 81 - rel="noopener noreferrer" 80 + target={post.href.startsWith('./') ? undefined : '_blank'} 81 + rel={post.href.startsWith('./') ? undefined : 'noopener noreferrer'} 82 82 class="group flex items-start gap-4 py-6" 83 83 > 84 84 <div class="min-w-0 flex-1">
+171
src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte
··· 3 3 import { Avatar as FoxAvatar } from '@foxui/core'; 4 4 import { marked } from 'marked'; 5 5 import { sanitize } from '$lib/sanitize'; 6 + import { all, createLowlight } from 'lowlight'; 7 + 8 + const lowlight = createLowlight(all); 9 + 10 + function hastToHtml(node: any): string { 11 + if (node.type === 'text') return escapeHtml(node.value); 12 + if (node.type === 'element') { 13 + const cls = node.properties?.className?.join(' '); 14 + const children = (node.children || []).map(hastToHtml).join(''); 15 + return cls ? `<span class="${cls}">${children}</span>` : `<span>${children}</span>`; 16 + } 17 + if (node.type === 'root' || node.children) { 18 + return (node.children || []).map(hastToHtml).join(''); 19 + } 20 + return ''; 21 + } 22 + 23 + function escapeHtml(str: string): string { 24 + return str 25 + .replace(/&/g, '&amp;') 26 + .replace(/</g, '&lt;') 27 + .replace(/>/g, '&gt;') 28 + .replace(/"/g, '&quot;'); 29 + } 6 30 7 31 let { data } = $props(); 8 32 ··· 54 78 const renderer = new marked.Renderer(); 55 79 renderer.link = ({ href, title, text }) => 56 80 `<a target="_blank" rel="noopener noreferrer" href="${href}" title="${title ?? ''}">${text}</a>`; 81 + renderer.code = ({ text, lang }) => { 82 + let highlighted: string; 83 + try { 84 + const tree = 85 + lang && lowlight.registered(lang) 86 + ? lowlight.highlight(lang, text) 87 + : lowlight.highlightAuto(text); 88 + highlighted = hastToHtml(tree); 89 + } catch { 90 + highlighted = escapeHtml(text); 91 + } 92 + return `<pre><code class="hljs${lang ? ` language-${lang}` : ''}">${highlighted}</code></pre>`; 93 + }; 57 94 </script> 58 95 59 96 <svelte:head> ··· 236 273 </footer> 237 274 </div> 238 275 </div> 276 + 277 + <style> 278 + :global(.prose pre) { 279 + background: #f6f8fa; 280 + border-radius: 1rem; 281 + color: #1f2328; 282 + font-family: 'JetBrainsMono', monospace; 283 + border: 1px solid #d1d9e0; 284 + } 285 + 286 + :global(.prose pre code) { 287 + background: none; 288 + color: inherit; 289 + font-size: 0.8rem; 290 + padding: 0; 291 + } 292 + 293 + :global(.prose pre .hljs-comment), 294 + :global(.prose pre .hljs-quote) { 295 + color: #59636e; 296 + font-style: italic; 297 + } 298 + 299 + :global(.prose pre .hljs-keyword), 300 + :global(.prose pre .hljs-selector-tag) { 301 + color: #cf222e; 302 + } 303 + 304 + :global(.prose pre .hljs-tag), 305 + :global(.prose pre .hljs-name) { 306 + color: #116329; 307 + } 308 + 309 + :global(.prose pre .hljs-attribute) { 310 + color: #0550ae; 311 + } 312 + 313 + :global(.prose pre .hljs-variable), 314 + :global(.prose pre .hljs-template-variable), 315 + :global(.prose pre .hljs-regexp), 316 + :global(.prose pre .hljs-link) { 317 + color: #0550ae; 318 + } 319 + 320 + :global(.prose pre .hljs-number), 321 + :global(.prose pre .hljs-literal) { 322 + color: #0550ae; 323 + } 324 + 325 + :global(.prose pre .hljs-string), 326 + :global(.prose pre .hljs-symbol), 327 + :global(.prose pre .hljs-bullet) { 328 + color: #0a3069; 329 + } 330 + 331 + :global(.prose pre .hljs-title), 332 + :global(.prose pre .hljs-section) { 333 + color: #0550ae; 334 + font-weight: 600; 335 + } 336 + 337 + :global(.prose pre .hljs-type), 338 + :global(.prose pre .hljs-built_in), 339 + :global(.prose pre .hljs-builtin-name), 340 + :global(.prose pre .hljs-params) { 341 + color: #953800; 342 + } 343 + 344 + :global(.prose pre .hljs-meta) { 345 + color: #0550ae; 346 + } 347 + 348 + /* Dark mode */ 349 + :global(.dark .prose pre) { 350 + background: #161b22; 351 + color: #e6edf3; 352 + border-color: #30363d; 353 + } 354 + 355 + :global(.dark .prose pre .hljs-comment), 356 + :global(.dark .prose pre .hljs-quote) { 357 + color: #8b949e; 358 + font-style: italic; 359 + } 360 + 361 + :global(.dark .prose pre .hljs-keyword), 362 + :global(.dark .prose pre .hljs-selector-tag) { 363 + color: #ff7b72; 364 + } 365 + 366 + :global(.dark .prose pre .hljs-tag), 367 + :global(.dark .prose pre .hljs-name) { 368 + color: #7ee787; 369 + } 370 + 371 + :global(.dark .prose pre .hljs-attribute) { 372 + color: #79c0ff; 373 + } 374 + 375 + :global(.dark .prose pre .hljs-variable), 376 + :global(.dark .prose pre .hljs-template-variable), 377 + :global(.dark .prose pre .hljs-regexp), 378 + :global(.dark .prose pre .hljs-link) { 379 + color: #79c0ff; 380 + } 381 + 382 + :global(.dark .prose pre .hljs-number), 383 + :global(.dark .prose pre .hljs-literal) { 384 + color: #79c0ff; 385 + } 386 + 387 + :global(.dark .prose pre .hljs-string), 388 + :global(.dark .prose pre .hljs-symbol), 389 + :global(.dark .prose pre .hljs-bullet) { 390 + color: #a5d6ff; 391 + } 392 + 393 + :global(.dark .prose pre .hljs-title), 394 + :global(.dark .prose pre .hljs-section) { 395 + color: #d2a8ff; 396 + font-weight: 600; 397 + } 398 + 399 + :global(.dark .prose pre .hljs-type), 400 + :global(.dark .prose pre .hljs-built_in), 401 + :global(.dark .prose pre .hljs-builtin-name), 402 + :global(.dark .prose pre .hljs-params) { 403 + color: #ffa657; 404 + } 405 + 406 + :global(.dark .prose pre .hljs-meta) { 407 + color: #79c0ff; 408 + } 409 + </style>
+86 -24
src/routes/[[actor=actor]]/blog/new/+page.svelte
··· 3 3 import { loginModalState } from '$lib/atproto/UI/LoginModal.svelte'; 4 4 import { uploadBlob, createTID } from '$lib/atproto/methods'; 5 5 import { compressImage } from '$lib/atproto/image-helper'; 6 - import { Button } from '@foxui/core'; 6 + import { Badge, Button } from '@foxui/core'; 7 7 import { goto } from '$app/navigation'; 8 8 import { onMount, onDestroy } from 'svelte'; 9 9 import { SvelteMap } from 'svelte/reactivity'; ··· 25 25 let draftRestored = $state(false); 26 26 27 27 let fileInput: HTMLInputElement | undefined = $state(); 28 + let titleEl: HTMLTextAreaElement | undefined = $state(); 29 + let descriptionEl: HTMLTextAreaElement | undefined = $state(); 30 + let draggingOver = $state(false); 31 + 32 + function handleDrop(e: DragEvent) { 33 + e.preventDefault(); 34 + draggingOver = false; 35 + const file = e.dataTransfer?.files?.[0]; 36 + if (!file || !file.type.startsWith('image/')) return; 37 + coverFile = file; 38 + if (coverPreview) URL.revokeObjectURL(coverPreview); 39 + coverPreview = URL.createObjectURL(file); 40 + 41 + const key = crypto.randomUUID(); 42 + blobToKeyMap.set(coverPreview, key); 43 + keyToBlobMap.set(key, coverPreview); 44 + putImage(key, file, file.name); 45 + scheduleSaveDraft(); 46 + } 28 47 29 48 // blob URL <-> IndexedDB key mappings 30 49 const blobToKeyMap = new SvelteMap<string, string>(); ··· 242 261 let markdown = editorInstance?.getMarkdown() ?? ''; 243 262 244 263 // Upload all blob:// images and replace with CDN URLs 245 - const blobUrlRegex = /!\[([^\]]*)\]\((blob:[^)]+)\)/g; 264 + const blobUrlRegex = /!\[([^\]]*)\]\((blob:[^\s)]+)(?:\s+"[^"]*")?\)/g; 246 265 const matches = [...markdown.matchAll(blobUrlRegex)]; 266 + const imageBlobs: unknown[] = []; 247 267 248 268 for (const match of matches) { 249 269 const blobUrl = match[2]; ··· 254 274 const compressed = await compressImage(file); 255 275 const blobRef = await uploadBlob({ blob: compressed.blob }); 256 276 if (blobRef) { 277 + imageBlobs.push(blobRef); 257 278 const cdnUrl = `https://cdn.bsky.app/img/feed_fullsize/plain/${user.did}/${blobRef.ref.$link}@jpeg`; 258 279 markdown = markdown.replaceAll(blobUrl, cdnUrl); 259 280 } ··· 267 288 const record: Record<string, unknown> = { 268 289 $type: 'site.standard.document', 269 290 title: title.trim(), 270 - content: { $type: 'app.blento.markdown', value: markdown }, 291 + content: { 292 + $type: 'app.blento.markdown', 293 + value: markdown, 294 + images: imageBlobs.length > 0 ? imageBlobs : undefined 295 + }, 271 296 site: `at://${user.did}/site.standard.publication/blento.self`, 272 297 path: `/blog/${rkey}`, 273 298 publishedAt: new Date().toISOString() ··· 311 336 scheduleSaveDraft(); 312 337 } 313 338 339 + function autoResize(el: HTMLTextAreaElement) { 340 + const resize = () => { 341 + el.style.height = 'auto'; 342 + el.style.height = el.scrollHeight + 'px'; 343 + }; 344 + resize(); 345 + return { update: resize }; 346 + } 347 + 314 348 function onTitleInput() { 315 349 scheduleSaveDraft(); 316 350 } ··· 354 388 </div> 355 389 {:else} 356 390 <!-- Draft badge --> 357 - {#if hasDraft} 358 - <div class="mb-6 flex items-center gap-3"> 359 - <span 360 - class="rounded-full bg-amber-100 px-3 py-1 text-xs font-medium text-amber-800 dark:bg-amber-900/30 dark:text-amber-300" 361 - > 362 - Draft 363 - </span> 391 + <div class="mb-6 flex items-center gap-3"> 392 + <Badge>Local draft</Badge> 393 + {#if hasDraft} 364 394 <button 365 395 type="button" 366 396 onclick={discardDraft} 367 - class="text-base-400 dark:text-base-500 text-xs transition-colors hover:text-red-500 dark:hover:text-red-400" 397 + class="text-base-400 dark:text-base-500 cursor-pointer text-xs transition-colors hover:text-red-500 dark:hover:text-red-400" 368 398 > 369 399 Discard draft 370 400 </button> 371 - </div> 372 - {/if} 401 + {/if} 402 + </div> 373 403 374 404 <!-- Cover image — full-width like the blog post view --> 375 405 <input ··· 410 440 <button 411 441 type="button" 412 442 onclick={() => fileInput?.click()} 413 - class="border-base-300 dark:border-base-700 hover:border-base-400 dark:hover:border-base-600 text-base-400 dark:text-base-500 mb-8 flex aspect-video w-full flex-col items-center justify-center rounded-2xl border-2 border-dashed transition-colors" 443 + ondragover={(e) => { 444 + e.preventDefault(); 445 + draggingOver = true; 446 + }} 447 + ondragleave={() => (draggingOver = false)} 448 + ondrop={handleDrop} 449 + class="mb-8 flex h-20 w-full cursor-pointer items-center gap-3 rounded-xl border-2 border-dashed px-5 transition-colors {draggingOver 450 + ? 'border-accent-400 bg-accent-50 dark:border-accent-500 dark:bg-accent-950' 451 + : 'border-base-300 dark:border-base-700 hover:border-base-400 dark:hover:border-base-600'}" 414 452 > 415 453 <svg 416 454 xmlns="http://www.w3.org/2000/svg" ··· 418 456 viewBox="0 0 24 24" 419 457 stroke-width="1.5" 420 458 stroke="currentColor" 421 - class="mb-2 size-8" 459 + class="text-base-400 dark:text-base-500 size-6 shrink-0" 422 460 > 423 461 <path 424 462 stroke-linecap="round" ··· 426 464 d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0 0 22.5 18.75V5.25A2.25 2.25 0 0 0 20.25 3H3.75A2.25 2.25 0 0 0 1.5 5.25v13.5A2.25 2.25 0 0 0 3.75 21Z" 427 465 /> 428 466 </svg> 429 - <span class="text-sm">Add cover image</span> 467 + <span class="text-base-400 dark:text-base-500 text-sm"> 468 + {draggingOver ? 'Drop image here' : 'Add cover image or drag & drop'} 469 + </span> 430 470 </button> 431 471 {/if} 432 472 433 473 <!-- Title & description — styled like the blog post header --> 434 474 <header class="mb-8"> 435 - <input 436 - type="text" 475 + <textarea 476 + bind:this={titleEl} 437 477 bind:value={title} 438 - oninput={onTitleInput} 478 + oninput={(e) => { 479 + onTitleInput(); 480 + autoResize(e.currentTarget); 481 + }} 482 + onkeydown={(e) => { 483 + if (e.key === 'Enter' && !e.shiftKey) { 484 + e.preventDefault(); 485 + descriptionEl?.focus(); 486 + } 487 + }} 488 + use:autoResize 439 489 placeholder="Post title" 440 - class="text-base-900 dark:text-base-50 placeholder:text-base-300 dark:placeholder:text-base-700 mb-4 w-full border-none bg-transparent text-3xl leading-tight font-bold outline-none sm:text-4xl" 441 - /> 490 + rows={1} 491 + class="text-base-900 dark:text-base-50 placeholder:text-base-500 mb-4 w-full resize-none border-0 border-none bg-transparent text-3xl leading-tight font-bold outline-none focus:border-0 focus:ring-0 focus:outline-0 sm:text-4xl" 492 + ></textarea> 442 493 <textarea 494 + bind:this={descriptionEl} 443 495 bind:value={description} 444 - oninput={onDescriptionInput} 496 + oninput={(e) => { 497 + onDescriptionInput(); 498 + autoResize(e.currentTarget); 499 + }} 500 + onkeydown={(e) => { 501 + if (e.key === 'Enter' && !e.shiftKey) { 502 + e.preventDefault(); 503 + editorInstance?.commands.focus(); 504 + } 505 + }} 506 + use:autoResize 445 507 placeholder="A short description (optional)" 446 508 rows={1} 447 - class="text-base-500 dark:text-base-400 placeholder:text-base-300 dark:placeholder:text-base-700 w-full resize-none border-none bg-transparent text-sm leading-relaxed outline-none" 509 + class="text-base-600 dark:text-base-400 placeholder:text-base-400 dark:placeholder:text-base-600 w-full resize-none border-0 border-none bg-transparent text-sm leading-relaxed outline-none focus:border-0 focus:ring-0 focus:outline-0" 448 510 ></textarea> 449 511 </header> 450 512 451 513 <!-- Rich text editor — styled like the blog post content area --> 452 514 <article 453 - class="prose dark:prose-invert prose-base prose-neutral prose-a:text-accent-600 dark:prose-a:text-accent-400 prose-img:rounded-xl mb-12 max-w-none" 515 + class="prose dark:prose-invert prose-base prose-neutral prose-a:text-accent-600 dark:prose-a:text-accent-400 prose-img:rounded-xl mb-12 max-w-none px-4" 454 516 > 455 517 <RichTextEditor 456 518 bind:editor={editorInstance}