forked from pdsls.dev/pdsls
this repo has no description

Compare changes

Choose any two refs to compare.

+1 -1
.nvmrc
··· 1 - 24.4.1 1 + 24
+11
README.md
··· 2 2 3 3 Navigate and manage [atproto](https://atproto.com/) repositories and the records they contain, as well as watching the relay in real time (firehose + jetstream support). 4 4 5 + ### Hacking 6 + 7 + You will need `node` and `pnpm` to get started: 8 + 9 + ``` 10 + pnpm i # install deps 11 + pnpm dev # or pnpm run start, runs vite 12 + pnpm build # runs vite build 13 + pnpm serve # runs vite preview 14 + ``` 15 + 5 16 ### Credits 6 17 7 18 [atcute](https://github.com/mary-ext/atcute) - atproto SDK\
-1
index.html
··· 9 9 <meta property="og:url" content="https://pdsls.dev" /> 10 10 <meta property="og:description" content="Browse and manage atproto repositories" /> 11 11 <meta property="description" content="Browse and manage atproto repositories" /> 12 - <title>PDSls</title> 13 12 <script> 14 13 if ( 15 14 localStorage.theme === "dark" ||
+8 -7
package.json
··· 9 9 "serve": "vite preview" 10 10 }, 11 11 "devDependencies": { 12 - "@iconify-json/lucide": "^1.2.59", 12 + "@iconify-json/lucide": "^1.2.62", 13 13 "@iconify-json/lucide-lab": "^1.2.3", 14 14 "prettier": "^3.6.2", 15 15 "prettier-plugin-tailwindcss": "^0.6.14", 16 - "typescript": "^5.8.3", 17 - "unocss": "66.3.3", 18 - "vite": "^7.0.6", 16 + "typescript": "^5.9.2", 17 + "unocss": "66.4.2", 18 + "vite": "^7.1.1", 19 19 "vite-plugin-solid": "^2.11.8" 20 20 }, 21 21 "dependencies": { ··· 26 26 "@atcute/cid": "^2.2.3", 27 27 "@atcute/client": "^4.0.3", 28 28 "@atcute/crypto": "^2.2.3", 29 + "@atcute/did-plc": "^0.1.6", 29 30 "@atcute/identity": "^1.0.3", 30 31 "@atcute/identity-resolver": "^1.1.3", 31 32 "@atcute/lexicon-doc": "^1.0.3", ··· 34 35 "@atcute/tid": "^1.0.2", 35 36 "@atcute/uint8array": "^1.0.3", 36 37 "@mary/exif-rm": "jsr:^0.2.2", 37 - "@skyware/firehose": "^0.5.1", 38 + "@skyware/firehose": "^0.5.2", 38 39 "@solidjs/meta": "^0.29.4", 39 40 "@solidjs/router": "^0.15.3", 40 - "hls.js": "^1.6.7", 41 + "hls.js": "^1.6.9", 41 42 "monaco-editor": "^0.52.2", 42 - "solid-js": "^1.9.7" 43 + "solid-js": "^1.9.8" 43 44 }, 44 45 "packageManager": "pnpm@10.12.2+sha512.a32540185b964ee30bb4e979e405adc6af59226b438ee4cc19f9e8773667a66d302f5bfee60a39d3cac69e35e4b96e708a71dd002b7e9359c4112a1722ac323f", 45 46 "pnpm": {
+274 -371
pnpm-lock.yaml
··· 29 29 '@atcute/crypto': 30 30 specifier: ^2.2.3 31 31 version: 2.2.3 32 + '@atcute/did-plc': 33 + specifier: ^0.1.6 34 + version: 0.1.6 32 35 '@atcute/identity': 33 36 specifier: ^1.0.3 34 37 version: 1.0.3 ··· 54 57 specifier: jsr:^0.2.2 55 58 version: '@jsr/mary__exif-rm@0.2.2' 56 59 '@skyware/firehose': 57 - specifier: ^0.5.1 58 - version: 0.5.1 60 + specifier: ^0.5.2 61 + version: 0.5.2 59 62 '@solidjs/meta': 60 63 specifier: ^0.29.4 61 - version: 0.29.4(solid-js@1.9.7) 64 + version: 0.29.4(solid-js@1.9.8) 62 65 '@solidjs/router': 63 66 specifier: ^0.15.3 64 - version: 0.15.3(solid-js@1.9.7) 67 + version: 0.15.3(solid-js@1.9.8) 65 68 hls.js: 66 - specifier: ^1.6.7 67 - version: 1.6.7 69 + specifier: ^1.6.9 70 + version: 1.6.9 68 71 monaco-editor: 69 72 specifier: ^0.52.2 70 73 version: 0.52.2 71 74 solid-js: 72 - specifier: ^1.9.7 73 - version: 1.9.7 75 + specifier: ^1.9.8 76 + version: 1.9.8 74 77 devDependencies: 75 78 '@iconify-json/lucide': 76 - specifier: ^1.2.59 77 - version: 1.2.59 79 + specifier: ^1.2.62 80 + version: 1.2.62 78 81 '@iconify-json/lucide-lab': 79 82 specifier: ^1.2.3 80 83 version: 1.2.3 ··· 85 88 specifier: ^0.6.14 86 89 version: 0.6.14(prettier@3.6.2) 87 90 typescript: 88 - specifier: ^5.8.3 89 - version: 5.8.3 91 + specifier: ^5.9.2 92 + version: 5.9.2 90 93 unocss: 91 - specifier: 66.3.3 92 - version: 66.3.3(postcss@8.5.6)(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3)) 94 + specifier: 66.4.2 95 + version: 66.4.2(postcss@8.5.6)(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 93 96 vite: 94 - specifier: ^7.0.6 95 - version: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 97 + specifier: ^7.1.1 98 + version: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 96 99 vite-plugin-solid: 97 100 specifier: ^2.11.8 98 - version: 2.11.8(solid-js@1.9.7)(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 101 + version: 2.11.8(solid-js@1.9.8)(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 99 102 100 103 packages: 101 104 ··· 106 109 '@antfu/install-pkg@1.1.0': 107 110 resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} 108 111 109 - '@antfu/utils@8.1.1': 110 - resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} 112 + '@antfu/utils@9.2.0': 113 + resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==} 111 114 112 115 '@atcute/atproto@3.1.1': 113 116 resolution: {integrity: sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg==} ··· 130 133 '@atcute/crypto@2.2.3': 131 134 resolution: {integrity: sha512-jJI/8WDK6rKvpoUKi0C9Q7pjRRrHGGAagRxnFvpBM5ycZT9eABz7p309LmRKBCWLasmCs/qee8WK4dqOA2e7Dw==} 132 135 136 + '@atcute/did-plc@0.1.6': 137 + resolution: {integrity: sha512-CaKZpl3UHHUczE4Co7gNi2CR3TPmQgBM0xEkKJJ6Vk4Lu9d+i9GcZQY/VBjmZntfIxHFJgZNdEkMk30lCUVpyw==} 138 + 133 139 '@atcute/identity-resolver@1.1.3': 134 140 resolution: {integrity: sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==} 135 141 peerDependencies: ··· 243 249 resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} 244 250 engines: {node: '>=6.9.0'} 245 251 246 - '@badrap/valita@0.4.5': 247 - resolution: {integrity: sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ==} 252 + '@badrap/valita@0.4.6': 253 + resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==} 248 254 engines: {node: '>= 18'} 249 255 250 256 '@esbuild/aix-ppc64@0.23.1': ··· 550 556 '@iconify-json/lucide-lab@1.2.3': 551 557 resolution: {integrity: sha512-N+8vnVt4IY/6FZi81f6nh5VhJSMYrs5KTVsT2Z/E0Wn7Lu4jJKO5fOfiTVX1YWVI4FFwQ1zVXPFb8kLAwskrjA==} 552 558 553 - '@iconify-json/lucide@1.2.59': 554 - resolution: {integrity: sha512-qHVs++9sGUxSNf8nJ0U/0UxHyVaut9TV4V7dc3i4K9jqxL/eg/sDpgKsX3+GrsWP3IwE8cARRgQHoLEb3Eru2Q==} 559 + '@iconify-json/lucide@1.2.62': 560 + resolution: {integrity: sha512-K0KfhvP5YQZ2KraOgCm6jJbwwzQCVocvXcdMpDou5uLa48QnLBRW/dQ8VDGmxHTGpwF9EqLlvnUSinH2i6xs3Q==} 555 561 556 562 '@iconify/types@2.0.0': 557 563 resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} 558 564 559 - '@iconify/utils@2.3.0': 560 - resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} 565 + '@iconify/utils@3.0.0': 566 + resolution: {integrity: sha512-Bjf0HTRAB59thKK9QFvyLEXE9S793IqxqJEhNQEboh+IjOXj0nDtOIFh63oz+Y6X/ye4UWpxne5sVQ2W250iSA==} 561 567 562 568 '@jridgewell/gen-mapping@0.3.12': 563 569 resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} ··· 581 587 '@polka/url@1.0.0-next.29': 582 588 resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} 583 589 584 - '@quansync/fs@0.1.3': 585 - resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} 586 - engines: {node: '>=20.0.0'} 590 + '@quansync/fs@0.1.4': 591 + resolution: {integrity: sha512-vy/41FCdnIalPTQCb2Wl0ic1caMdzGus4ktDp+gpZesQNydXcx8nhh8qB3qMPbGkictOTaXgXEUUfQEm8DQYoA==} 587 592 588 593 '@rollup/rollup-android-arm-eabi@4.46.2': 589 594 resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} ··· 685 690 cpu: [x64] 686 691 os: [win32] 687 692 688 - '@skyware/firehose@0.5.1': 689 - resolution: {integrity: sha512-7fcTQtXCbD2t5ls/tvMeYuI8dHTiUuZvrMdD1Mq+ZyMPpjcQdv2OsmflTrldt5XY+kOgoaThImi7QEo07B3o2Q==} 693 + '@skyware/firehose@0.5.2': 694 + resolution: {integrity: sha512-Ayg/cF0BkakBNQVA51ClDka0+nC96WiARNrGElMQxfqbwao0PBaCXkunfr8qS4DWS3TqLnR6hA9mvm1vAYlxJQ==} 690 695 691 696 '@solidjs/meta@0.29.4': 692 697 resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==} ··· 707 712 '@types/babel__template@7.4.4': 708 713 resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} 709 714 710 - '@types/babel__traverse@7.20.7': 711 - resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} 715 + '@types/babel__traverse@7.28.0': 716 + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} 712 717 713 718 '@types/estree@1.0.8': 714 719 resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} ··· 716 721 '@types/node@22.13.1': 717 722 resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} 718 723 719 - '@unocss/astro@66.3.3': 720 - resolution: {integrity: sha512-q26EfadSMmEXZpWDKsJF9anBCfhYDmWljVpDZ2Wo8K48IbZMUXrWfiAiUc6ijE/A/rADfHk8bp3a3GE01t3I9A==} 724 + '@unocss/astro@66.4.2': 725 + resolution: {integrity: sha512-En3AKHwkiPxtZT95vkVrNiRYrB+DFVCikew6/dMMCWDWVKK0+5tEVUTzR1ak3+YnzAXl0NpWj8D4zHb0PxOs/A==} 721 726 peerDependencies: 722 727 vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 723 728 peerDependenciesMeta: 724 729 vite: 725 730 optional: true 726 731 727 - '@unocss/cli@66.3.3': 728 - resolution: {integrity: sha512-U0HoDcwi/DetqP5zDT3dfxG94pC3TI0PfxmpdTfPY7xEylIdLbV89fb70CvJVysDSQJIuw6TYwqS1ZlHoYNKTA==} 732 + '@unocss/cli@66.4.2': 733 + resolution: {integrity: sha512-WsXzrB0SHbSt2nOHtD5QM91VN8j38+wObqyGcoIhtBSugqzsc+t7AdPkxV/ZaYgtPAz87bR0WFEVKcbiBRnmJw==} 729 734 engines: {node: '>=14'} 730 735 hasBin: true 731 736 732 - '@unocss/config@66.3.3': 733 - resolution: {integrity: sha512-D/UxnAmkabapqWU4tF85dWWhNfCUyNutWmd4AD2VsQRZOykufJedLV74r3Z3XhoPJn4IGr3BKZm5/rflf5viDg==} 737 + '@unocss/config@66.4.2': 738 + resolution: {integrity: sha512-plji1gNGSzlWjuV2Uh0q6Dt5ZlNkOKCHpgxekW9J458WghGAMBeXgB9uNpWg6flilqP1g0GJQv+XvJcSkYRGpQ==} 734 739 engines: {node: '>=14'} 735 740 736 - '@unocss/core@66.3.3': 737 - resolution: {integrity: sha512-6WFLd92TJelVQARtCGaF+EgEoHKIVe43gkGXVoWILu0HUDRWdhv+cpcyX0RTJV22Y976AxeneU7/zmhAh+CXNg==} 741 + '@unocss/core@66.4.2': 742 + resolution: {integrity: sha512-cYgMQrLhB9nRekv5c+yPDDa+5dzlMkA2UMQRil0s5D9Lb5n7NsCMcr6+nfxkcSYVLy92SbwDV45c6T7vIxFTOA==} 738 743 739 - '@unocss/extractor-arbitrary-variants@66.3.3': 740 - resolution: {integrity: sha512-TXzjH6FcITQ8V2x7ETHgVOlAHf3ll/ysxL+W4fMROm8jP/o7jvsg36tRfOwU0sDGo/qoCPux82ix9e6/JW0oqQ==} 744 + '@unocss/extractor-arbitrary-variants@66.4.2': 745 + resolution: {integrity: sha512-T/eSeodfAp7HaWnQGqVLOsW4PbKUAvuybNRyvFWThMneM2qo+dOo3kFnA5my9ULAmRSFsAlyB1DnupD3qv5Klg==} 741 746 742 - '@unocss/inspector@66.3.3': 743 - resolution: {integrity: sha512-NsK1WRWez2Mzk4+ophtBdXel8nGaPkIDa9lYSFMdKLF/1jNW23txeEL8CsD6/CK8K0BsR11rhLKhUrzyrjfBSQ==} 747 + '@unocss/inspector@66.4.2': 748 + resolution: {integrity: sha512-ugcJK8r2ypM4eIdgetVn8RhfKrbA3AF3OQ/RohK5PPk2UPDAScqabzYpfdNW4eYQsBOZOgoiqWtnfc8weqo8LQ==} 744 749 745 - '@unocss/postcss@66.3.3': 746 - resolution: {integrity: sha512-VKq+BtfPIZbLeAeZFprtKZJAyFBOqA8qpQm+vmWBiBia70JzkwfF2SMNIHiGt022yRo9ZmjnI9uRTxSzqXUsUQ==} 750 + '@unocss/postcss@66.4.2': 751 + resolution: {integrity: sha512-tu4lnh6K27pIAuaQHlFlhXin8korwC0r1kQl00YMmF3THiX7orXkTP6xWGcQwnkbx4uQz1dw+tBimYxeaAMrhA==} 747 752 engines: {node: '>=14'} 748 753 peerDependencies: 749 754 postcss: ^8.4.21 750 755 751 - '@unocss/preset-attributify@66.3.3': 752 - resolution: {integrity: sha512-22+0Cqqu09q+xHfZ3Wk8Coxe5m6PmpgWz4U5xrEC8056UfG3Q1KEqoCxy2wySJIq8SqxQ30Nlll7oMa31B8Krw==} 756 + '@unocss/preset-attributify@66.4.2': 757 + resolution: {integrity: sha512-DwFJJkkawmHpjo3pGQE8FyoPsvhbxh+QMvvaAdYpo+iZ5HRkeDml9SOj7u6SGTcmbNyI+QR61s0KM8fxx6HcVQ==} 753 758 754 - '@unocss/preset-icons@66.3.3': 755 - resolution: {integrity: sha512-Bmhiev05BN/horlgnyZ8gzQWZKd7oVpUBWD66X7U/dgkLdO6B5GIIsdO5Fi7JLeMDmyXm6vlYk0YQhiTbx8l9w==} 759 + '@unocss/preset-icons@66.4.2': 760 + resolution: {integrity: sha512-qJx9gmesrvrmoTe9Mqoidihad8hm2MSD4QAezhfDSAyllioJOgyT0Bev/IEWAbehe9jtqYIh8v1oCerBPbGn6Q==} 756 761 757 - '@unocss/preset-mini@66.3.3': 758 - resolution: {integrity: sha512-pz8rgvHRYS/6fsZNtG7iArLzwANnLy5GkHY/lbuqkWhO2S2Nf7kpJCbR/uV/XeuFsLnYcZW3NLOmelfvZvJamA==} 762 + '@unocss/preset-mini@66.4.2': 763 + resolution: {integrity: sha512-Ry+5hM+XLmT8HrEb182mUfcZuyrZ8xR+TBe72DBcliJ1DhOV3K67TCxwQucfb0zHbGV71HNWdPmHsLKxPDgweQ==} 759 764 760 - '@unocss/preset-tagify@66.3.3': 761 - resolution: {integrity: sha512-L1Ez7Y4uBaW+wiv1BOQygpfhseSt3EZ53jqkl7fxl1EKVsJy6SuZgJxlXEHUYp9xYdSp6EHq2CfL8UevaR+loA==} 765 + '@unocss/preset-tagify@66.4.2': 766 + resolution: {integrity: sha512-dECS09LqWJY4sYpgPUH2OAUftWU/tiZPR2XDRoTngeGU37GxSN+1sWtSmB7vwDm3C7opsdVUN20he8F1LUNubw==} 762 767 763 - '@unocss/preset-typography@66.3.3': 764 - resolution: {integrity: sha512-aQXiGCObvWD9grfUpm0d5nzN+Cpvag0rHP39UjUKb0xSTzY09VzwDrua4kWVO5wJLNK6/L70osyhEgmC3qToxA==} 768 + '@unocss/preset-typography@66.4.2': 769 + resolution: {integrity: sha512-ZOKRuR5+V0r30QTVq04/6ZoIw75me3V25v2dU2YWJXIzwpMKmQ9TUN/M1yeiEUFfXjOaruWX6Ad6CvAw2MlCew==} 765 770 766 - '@unocss/preset-uno@66.3.3': 767 - resolution: {integrity: sha512-Tiho4LidpuMHrB19GHTU6XrL0A5eFELHk9ebQ/3WeTy+K/9a6Hn5zsHJe5UCtOsEcUdKB33oZx0hXUp93hb/YQ==} 771 + '@unocss/preset-uno@66.4.2': 772 + resolution: {integrity: sha512-1MFtPivGcpqRQFWdjtP40Enop1y3XDb3tlZXoMQUX0IGLG8HJOT+lfQx/Xl9t73ShJ8aAJ/l6qTxC43ZGNACzA==} 768 773 769 - '@unocss/preset-web-fonts@66.3.3': 770 - resolution: {integrity: sha512-ysKZeC7TXxRiqnNL9GxZFGMKFAHXrcaqozPaEOIJ40dvzbJt8IMLyFndZkcFMcgDCV0pFh/y37mGxxxARO9+pQ==} 774 + '@unocss/preset-web-fonts@66.4.2': 775 + resolution: {integrity: sha512-4FYmleeRoM8r2DqGl6dfIjnX57tepcfZCvVfeCqYnk7475Yddmv1OYkoMjkWMnkK9MzdSxsFwHMU6CIUTmFTzQ==} 771 776 772 - '@unocss/preset-wind3@66.3.3': 773 - resolution: {integrity: sha512-iXmjvPqvmPTo4z7epQDqHxzlGRsbLJEgfETqTrRJeagvFG7Gs+ajS8cQhbf6wL01dSRHjvhVXi3MsIvqfHHXOw==} 777 + '@unocss/preset-wind3@66.4.2': 778 + resolution: {integrity: sha512-0Aye/PaT08M/cQhPnGKn93iEVoRJbym0/1eomMvXoL+8oc7DVry35ws06r5CLu5h1sXI6UmS6sejoePFlSkLJQ==} 774 779 775 - '@unocss/preset-wind4@66.3.3': 776 - resolution: {integrity: sha512-JSJTXVJel6kX+u4Ktt6JGnukYWYhKxmjgORTwclUpokRHgEoD+xsh0Rz4YGJ1fWSnzNslNQhWP9yDRByVPHWwA==} 780 + '@unocss/preset-wind4@66.4.2': 781 + resolution: {integrity: sha512-F4RZsDqIpnSevD9hY353+Tw5gxpJuHA5HwdKjLnC/TnT9VKKVmV7qUEZ6M0jEuAk1kz2x3/ngnQ9Ftw+C2L84A==} 777 782 778 - '@unocss/preset-wind@66.3.3': 779 - resolution: {integrity: sha512-3Mxl/TDPcv8nNKdFe3WKdlXE6de+lCaaizEH86BILW3ZeyPU9aKzWcZIoxohla0a6zMxDQ2+Gf+7EwaOvpqo7Q==} 783 + '@unocss/preset-wind@66.4.2': 784 + resolution: {integrity: sha512-z/rFYFINNqmBtl3Dh+7UCKpPnPkxM7IIUGszMnvdntky9uhLauJ11dt/Puir73sM2cAfywfgvnHyZ00m0pg7rA==} 780 785 781 - '@unocss/reset@66.3.3': 782 - resolution: {integrity: sha512-VIeR/mIcCL89/1uA1KM1QCYca4aeIGqEHMTJL1nCD4v+7wk6XhNXhsp5gMIHo+V804SUSmATWaeHTiKpiFu7AQ==} 786 + '@unocss/reset@66.4.2': 787 + resolution: {integrity: sha512-s3Kq4Q6a/d3/jYe6HTCfXUx7zYAYufetId5n66DZHzQxpeu6CoBS83+b37STTKsw27SOgV28cPJlJtZ6/D6Bhw==} 783 788 784 - '@unocss/rule-utils@66.3.3': 785 - resolution: {integrity: sha512-QKgVGV5nRRnK44/reUKFLAc5UGyl98vz3hrfk8JI8pVza58vmQWTdAB2rIpNJ5a5j+EkWfDOUlGQaOrIeYGLdg==} 789 + '@unocss/rule-utils@66.4.2': 790 + resolution: {integrity: sha512-7z3IuajwXhy2cx3E0IGOFXIiuKC79/jzm4Tt56TC68nXLh/etlH0fKhxVwkZ/HbcQRpVwWyDRNcbh29pmA3DwQ==} 786 791 engines: {node: '>=14'} 787 792 788 - '@unocss/transformer-attributify-jsx@66.3.3': 789 - resolution: {integrity: sha512-ENNYFk5wrI4jlxn0tWGeR9QGxflAfZue3X2ABg0KSVOiYyIOsrHqtdoiLYkuCA9idRlBZPQxePJKcPWt1r/tYA==} 793 + '@unocss/transformer-attributify-jsx@66.4.2': 794 + resolution: {integrity: sha512-de6LzoyW1tkdOftlCrj6z8wEb4j6l1sqmOU1nYKkYHw7luLFGxRUELC7iujlI9KmylbM02bcKfLETAfJy/je2w==} 790 795 791 - '@unocss/transformer-compile-class@66.3.3': 792 - resolution: {integrity: sha512-VTEFuwp3iajGWyEFwmO5LRvOjgZM1TK+4rX5Q79xyTAPkLAKgOa03Ne8+kU8oG0TQEa4mXVw6ul9McM7UBJh1w==} 796 + '@unocss/transformer-compile-class@66.4.2': 797 + resolution: {integrity: sha512-+oiIrV8c3T7qiJdICr6YsEWik5sjbWirXF0mlpcBvZu2HyV559hvHjzuWKr/fl7xYYZKDL9FvddbqWo3DOXh3Q==} 793 798 794 - '@unocss/transformer-directives@66.3.3': 795 - resolution: {integrity: sha512-11T7fmYk/XZcqFDn4qiIvs04mJhUtAoha5Y99bVE+L3byWa6BT4jb5aSAKk+24q5aynwgB++4RgfQxarj69WTw==} 799 + '@unocss/transformer-directives@66.4.2': 800 + resolution: {integrity: sha512-7m/dTrCUkBkZeSRKPxPEo65Rav239orQSLq6sztwZhoA4x/6H8r58xCkAK0qC9VEalyerpCpyarU3sKN4+ehNg==} 796 801 797 - '@unocss/transformer-variant-group@66.3.3': 798 - resolution: {integrity: sha512-uhK81pbJfXJFYaXxOoIFVEG8/Kx1iaAkTwRB6c+WNUfl9GiKyYQcrI7bETgCPPbg230Z68jVICBgBATeLJ31vQ==} 802 + '@unocss/transformer-variant-group@66.4.2': 803 + resolution: {integrity: sha512-SbPDbZUrhQyL4CpvnpvUfrr1DFq8AKf8ofPGbMJDm5S2TInQ34vFaIrhNroGR0szntMZRH5Zlkq6LtVUKDRs5g==} 799 804 800 - '@unocss/vite@66.3.3': 801 - resolution: {integrity: sha512-uu3smeEW6q36ri6vydRx2GiTGF5O/J80Fr4GLmLiwfpt2YnPHraO7XHVR5/mwG2Oz5Kov0uGvxVsdgxZABKRgw==} 805 + '@unocss/vite@66.4.2': 806 + resolution: {integrity: sha512-7eON9iPF3qWzuI+M6u0kq7K3y9nEbimZlLj01nGoqrgSGxEsyJpP01QQQsmT7FPRiZzRMJv7BiKMEyDQSuRRCA==} 802 807 peerDependencies: 803 808 vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 804 809 805 - '@vue/compiler-core@3.5.13': 806 - resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} 807 - 808 - '@vue/compiler-dom@3.5.13': 809 - resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} 810 - 811 - '@vue/compiler-sfc@3.5.13': 812 - resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} 813 - 814 - '@vue/compiler-ssr@3.5.13': 815 - resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} 816 - 817 - '@vue/reactivity@3.5.13': 818 - resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} 819 - 820 - '@vue/runtime-core@3.5.13': 821 - resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} 822 - 823 - '@vue/runtime-dom@3.5.13': 824 - resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} 825 - 826 - '@vue/server-renderer@3.5.13': 827 - resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} 828 - peerDependencies: 829 - vue: 3.5.13 830 - 831 - '@vue/shared@3.5.13': 832 - resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} 833 - 834 810 acorn@8.15.0: 835 811 resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 836 812 engines: {node: '>=0.4.0'} ··· 840 816 resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 841 817 engines: {node: '>= 8'} 842 818 843 - babel-plugin-jsx-dom-expressions@0.39.8: 844 - resolution: {integrity: sha512-/MVOIIjonylDXnrWmG23ZX82m9mtKATsVHB7zYlPfDR9Vdd/NBE48if+wv27bSkBtyO7EPMUlcUc4J63QwuACQ==} 819 + babel-plugin-jsx-dom-expressions@0.40.1: 820 + resolution: {integrity: sha512-b4iHuirqK7RgaMzB2Lsl7MqrlDgQtVRSSazyrmx7wB3T759ggGjod5Rkok5MfHjQXhR7tRPmdwoeGPqBnW2KfA==} 845 821 peerDependencies: 846 822 '@babel/core': ^7.20.12 847 823 848 - babel-preset-solid@1.9.6: 849 - resolution: {integrity: sha512-HXTK9f93QxoH8dYn1M2mJdOlWgMsR88Lg/ul6QCZGkNTktjTE5HAf93YxQumHoCudLEtZrU1cFCMFOVho6GqFg==} 824 + babel-preset-solid@1.9.8: 825 + resolution: {integrity: sha512-Tz2ZoKCPITeV+cANGeIA6pxHBLeEtX7hwk04tEh3xSWVqHMf2FqFwVz0RBxCLlBehpKfY1scDiuijBkmyVpqrQ==} 850 826 peerDependencies: 851 827 '@babel/core': ^7.0.0 828 + solid-js: ^1.9.8 829 + peerDependenciesMeta: 830 + solid-js: 831 + optional: true 852 832 853 833 binary-extensions@2.3.0: 854 834 resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} ··· 858 838 resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 859 839 engines: {node: '>=8'} 860 840 861 - browserslist@4.25.1: 862 - resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} 841 + browserslist@4.25.2: 842 + resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==} 863 843 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 864 844 hasBin: true 865 845 ··· 867 847 resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 868 848 engines: {node: '>=8'} 869 849 870 - caniuse-lite@1.0.30001731: 871 - resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} 850 + caniuse-lite@1.0.30001733: 851 + resolution: {integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==} 872 852 873 853 chokidar@3.6.0: 874 854 resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} ··· 915 895 duplexer@0.1.2: 916 896 resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} 917 897 918 - electron-to-chromium@1.5.192: 919 - resolution: {integrity: sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==} 920 - 921 - entities@4.5.0: 922 - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 923 - engines: {node: '>=0.12'} 898 + electron-to-chromium@1.5.199: 899 + resolution: {integrity: sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==} 924 900 925 901 entities@6.0.1: 926 902 resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} ··· 943 919 esm-env@1.2.2: 944 920 resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 945 921 946 - estree-walker@2.0.2: 947 - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 948 - 949 922 exsolve@1.0.7: 950 923 resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} 951 924 ··· 985 958 resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} 986 959 engines: {node: '>=10'} 987 960 988 - hls.js@1.6.7: 989 - resolution: {integrity: sha512-QW2fnwDGKGc9DwQUGLbmMOz8G48UZK7PVNJPcOUql1b8jubKx4/eMHNP5mGqr6tYlJNDG1g10Lx2U/qPzL6zwQ==} 961 + hls.js@1.6.9: 962 + resolution: {integrity: sha512-q7qPrri6GRwjcNd7EkFCmhiJ6PBIxeUsdxKbquBkQZpg9jAnp6zSAeN9eEWFlOB09J8JfzAQGoXL5ZEAltjO9g==} 990 963 991 964 html-entities@2.3.3: 992 965 resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} ··· 1075 1048 engines: {node: ^18 || >=20} 1076 1049 hasBin: true 1077 1050 1078 - node-fetch-native@1.6.6: 1079 - resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} 1051 + node-fetch-native@1.6.7: 1052 + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} 1080 1053 1081 1054 node-releases@2.0.19: 1082 1055 resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} ··· 1220 1193 resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} 1221 1194 engines: {node: '>=18'} 1222 1195 1223 - solid-js@1.9.7: 1224 - resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==} 1196 + solid-js@1.9.8: 1197 + resolution: {integrity: sha512-zF9Whfqk+s8wWuyDKnE7ekl+dJburjdZq54O6X1k4XChA57uZ5FOauYAa0s4I44XkBOM3CZmPrZC0DGjH9fKjQ==} 1225 1198 1226 1199 solid-refresh@0.6.3: 1227 1200 resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} ··· 1252 1225 engines: {node: '>=18.0.0'} 1253 1226 hasBin: true 1254 1227 1255 - typescript@5.8.3: 1256 - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1228 + typescript@5.9.2: 1229 + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} 1257 1230 engines: {node: '>=14.17'} 1258 1231 hasBin: true 1259 1232 ··· 1266 1239 undici-types@6.20.0: 1267 1240 resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 1268 1241 1269 - unocss@66.3.3: 1270 - resolution: {integrity: sha512-HSB+K4/EbouwYmxpPU52cg0exua7PUr2IAJZBV3iai6tPdMcJ0c8jXaw7G+2L+ffruVFTcS0e2kE4OrR8BKDLg==} 1242 + unocss@66.4.2: 1243 + resolution: {integrity: sha512-PsZ+4XF/ekiParR7PZEM7AchvHJ78EIfOXlqTPflTOXCYgZ77kG9NaIaIf4lHRevY+rRTyrHrjxdg1Ern2j8qw==} 1271 1244 engines: {node: '>=14'} 1272 1245 peerDependencies: 1273 - '@unocss/webpack': 66.3.3 1246 + '@unocss/webpack': 66.4.2 1274 1247 vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 1275 1248 peerDependenciesMeta: 1276 1249 '@unocss/webpack': ··· 1278 1251 vite: 1279 1252 optional: true 1280 1253 1281 - unplugin-utils@0.2.4: 1282 - resolution: {integrity: sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==} 1254 + unplugin-utils@0.2.5: 1255 + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} 1283 1256 engines: {node: '>=18.12.0'} 1284 1257 1285 1258 update-browserslist-db@1.1.3: ··· 1301 1274 '@testing-library/jest-dom': 1302 1275 optional: true 1303 1276 1304 - vite@7.0.6: 1305 - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} 1277 + vite@7.1.1: 1278 + resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} 1306 1279 engines: {node: ^20.19.0 || >=22.12.0} 1307 1280 hasBin: true 1308 1281 peerDependencies: ··· 1349 1322 vite: 1350 1323 optional: true 1351 1324 1352 - vue-flow-layout@0.1.1: 1353 - resolution: {integrity: sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==} 1354 - peerDependencies: 1355 - vue: ^3.4.37 1356 - 1357 - vue@3.5.13: 1358 - resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} 1359 - peerDependencies: 1360 - typescript: '*' 1361 - peerDependenciesMeta: 1362 - typescript: 1363 - optional: true 1325 + vue-flow-layout@0.2.0: 1326 + resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==} 1364 1327 1365 1328 yallist@3.1.1: 1366 1329 resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} ··· 1381 1344 package-manager-detector: 1.3.0 1382 1345 tinyexec: 1.0.1 1383 1346 1384 - '@antfu/utils@8.1.1': {} 1347 + '@antfu/utils@9.2.0': {} 1385 1348 1386 1349 '@atcute/atproto@3.1.1': 1387 1350 dependencies: ··· 1422 1385 '@atcute/uint8array': 1.0.3 1423 1386 '@noble/secp256k1': 2.3.0 1424 1387 1388 + '@atcute/did-plc@0.1.6': 1389 + dependencies: 1390 + '@atcute/cbor': 2.2.5 1391 + '@atcute/cid': 2.2.3 1392 + '@atcute/crypto': 2.2.3 1393 + '@atcute/identity': 1.0.3 1394 + '@atcute/lexicons': 1.1.0 1395 + '@atcute/multibase': 1.1.4 1396 + '@atcute/uint8array': 1.0.3 1397 + '@badrap/valita': 0.4.6 1398 + 1425 1399 '@atcute/identity-resolver@1.1.3(@atcute/identity@1.0.3)': 1426 1400 dependencies: 1427 1401 '@atcute/identity': 1.0.3 1428 1402 '@atcute/lexicons': 1.1.0 1429 1403 '@atcute/util-fetch': 1.0.1 1430 - '@badrap/valita': 0.4.5 1404 + '@badrap/valita': 0.4.6 1431 1405 1432 1406 '@atcute/identity@1.0.3': 1433 1407 dependencies: 1434 1408 '@atcute/lexicons': 1.1.0 1435 - '@badrap/valita': 0.4.5 1409 + '@badrap/valita': 0.4.6 1436 1410 1437 1411 '@atcute/lexicon-doc@1.0.3': 1438 1412 dependencies: 1439 - '@badrap/valita': 0.4.5 1413 + '@badrap/valita': 0.4.6 1440 1414 1441 1415 '@atcute/lexicons@1.1.0': 1442 1416 dependencies: ··· 1461 1435 1462 1436 '@atcute/util-fetch@1.0.1': 1463 1437 dependencies: 1464 - '@badrap/valita': 0.4.5 1438 + '@badrap/valita': 0.4.6 1465 1439 1466 1440 '@atcute/varint@1.0.2': {} 1467 1441 ··· 1505 1479 dependencies: 1506 1480 '@babel/compat-data': 7.28.0 1507 1481 '@babel/helper-validator-option': 7.27.1 1508 - browserslist: 4.25.1 1482 + browserslist: 4.25.2 1509 1483 lru-cache: 5.1.1 1510 1484 semver: 6.3.1 1511 1485 ··· 1576 1550 '@babel/helper-string-parser': 7.27.1 1577 1551 '@babel/helper-validator-identifier': 7.27.1 1578 1552 1579 - '@badrap/valita@0.4.5': {} 1553 + '@badrap/valita@0.4.6': {} 1580 1554 1581 1555 '@esbuild/aix-ppc64@0.23.1': 1582 1556 optional: true ··· 1732 1706 dependencies: 1733 1707 '@iconify/types': 2.0.0 1734 1708 1735 - '@iconify-json/lucide@1.2.59': 1709 + '@iconify-json/lucide@1.2.62': 1736 1710 dependencies: 1737 1711 '@iconify/types': 2.0.0 1738 1712 1739 1713 '@iconify/types@2.0.0': {} 1740 1714 1741 - '@iconify/utils@2.3.0': 1715 + '@iconify/utils@3.0.0': 1742 1716 dependencies: 1743 1717 '@antfu/install-pkg': 1.1.0 1744 - '@antfu/utils': 8.1.1 1718 + '@antfu/utils': 9.2.0 1745 1719 '@iconify/types': 2.0.0 1746 1720 debug: 4.4.1 1747 1721 globals: 15.15.0 ··· 1771 1745 1772 1746 '@polka/url@1.0.0-next.29': {} 1773 1747 1774 - '@quansync/fs@0.1.3': 1748 + '@quansync/fs@0.1.4': 1775 1749 dependencies: 1776 1750 quansync: 0.2.10 1777 1751 ··· 1835 1809 '@rollup/rollup-win32-x64-msvc@4.46.2': 1836 1810 optional: true 1837 1811 1838 - '@skyware/firehose@0.5.1': 1812 + '@skyware/firehose@0.5.2': 1839 1813 dependencies: 1840 1814 '@atcute/car': 3.1.1 1841 1815 '@atcute/cbor': 2.2.5 1842 1816 nanoevents: 9.1.0 1843 1817 1844 - '@solidjs/meta@0.29.4(solid-js@1.9.7)': 1818 + '@solidjs/meta@0.29.4(solid-js@1.9.8)': 1845 1819 dependencies: 1846 - solid-js: 1.9.7 1820 + solid-js: 1.9.8 1847 1821 1848 - '@solidjs/router@0.15.3(solid-js@1.9.7)': 1822 + '@solidjs/router@0.15.3(solid-js@1.9.8)': 1849 1823 dependencies: 1850 - solid-js: 1.9.7 1824 + solid-js: 1.9.8 1851 1825 1852 1826 '@types/babel__core@7.20.5': 1853 1827 dependencies: ··· 1855 1829 '@babel/types': 7.28.2 1856 1830 '@types/babel__generator': 7.27.0 1857 1831 '@types/babel__template': 7.4.4 1858 - '@types/babel__traverse': 7.20.7 1832 + '@types/babel__traverse': 7.28.0 1859 1833 1860 1834 '@types/babel__generator@7.27.0': 1861 1835 dependencies: ··· 1866 1840 '@babel/parser': 7.28.0 1867 1841 '@babel/types': 7.28.2 1868 1842 1869 - '@types/babel__traverse@7.20.7': 1843 + '@types/babel__traverse@7.28.0': 1870 1844 dependencies: 1871 1845 '@babel/types': 7.28.2 1872 1846 ··· 1877 1851 undici-types: 6.20.0 1878 1852 optional: true 1879 1853 1880 - '@unocss/astro@66.3.3(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3))': 1854 + '@unocss/astro@66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))': 1881 1855 dependencies: 1882 - '@unocss/core': 66.3.3 1883 - '@unocss/reset': 66.3.3 1884 - '@unocss/vite': 66.3.3(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3)) 1856 + '@unocss/core': 66.4.2 1857 + '@unocss/reset': 66.4.2 1858 + '@unocss/vite': 66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 1885 1859 optionalDependencies: 1886 - vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 1887 - transitivePeerDependencies: 1888 - - vue 1860 + vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 1889 1861 1890 - '@unocss/cli@66.3.3': 1862 + '@unocss/cli@66.4.2': 1891 1863 dependencies: 1892 1864 '@ampproject/remapping': 2.3.0 1893 - '@unocss/config': 66.3.3 1894 - '@unocss/core': 66.3.3 1895 - '@unocss/preset-uno': 66.3.3 1865 + '@unocss/config': 66.4.2 1866 + '@unocss/core': 66.4.2 1867 + '@unocss/preset-uno': 66.4.2 1896 1868 cac: 6.7.14 1897 1869 chokidar: 3.6.0 1898 1870 colorette: 2.0.20 ··· 1901 1873 pathe: 2.0.3 1902 1874 perfect-debounce: 1.0.0 1903 1875 tinyglobby: 0.2.14 1904 - unplugin-utils: 0.2.4 1876 + unplugin-utils: 0.2.5 1905 1877 1906 - '@unocss/config@66.3.3': 1878 + '@unocss/config@66.4.2': 1907 1879 dependencies: 1908 - '@unocss/core': 66.3.3 1880 + '@unocss/core': 66.4.2 1909 1881 unconfig: 7.3.2 1910 1882 1911 - '@unocss/core@66.3.3': {} 1883 + '@unocss/core@66.4.2': {} 1912 1884 1913 - '@unocss/extractor-arbitrary-variants@66.3.3': 1885 + '@unocss/extractor-arbitrary-variants@66.4.2': 1914 1886 dependencies: 1915 - '@unocss/core': 66.3.3 1887 + '@unocss/core': 66.4.2 1916 1888 1917 - '@unocss/inspector@66.3.3(vue@3.5.13(typescript@5.8.3))': 1889 + '@unocss/inspector@66.4.2': 1918 1890 dependencies: 1919 - '@unocss/core': 66.3.3 1920 - '@unocss/rule-utils': 66.3.3 1891 + '@unocss/core': 66.4.2 1892 + '@unocss/rule-utils': 66.4.2 1921 1893 colorette: 2.0.20 1922 1894 gzip-size: 6.0.0 1923 1895 sirv: 3.0.1 1924 - vue-flow-layout: 0.1.1(vue@3.5.13(typescript@5.8.3)) 1925 - transitivePeerDependencies: 1926 - - vue 1896 + vue-flow-layout: 0.2.0 1927 1897 1928 - '@unocss/postcss@66.3.3(postcss@8.5.6)': 1898 + '@unocss/postcss@66.4.2(postcss@8.5.6)': 1929 1899 dependencies: 1930 - '@unocss/config': 66.3.3 1931 - '@unocss/core': 66.3.3 1932 - '@unocss/rule-utils': 66.3.3 1900 + '@unocss/config': 66.4.2 1901 + '@unocss/core': 66.4.2 1902 + '@unocss/rule-utils': 66.4.2 1933 1903 css-tree: 3.1.0 1934 1904 postcss: 8.5.6 1935 1905 tinyglobby: 0.2.14 1936 1906 1937 - '@unocss/preset-attributify@66.3.3': 1907 + '@unocss/preset-attributify@66.4.2': 1938 1908 dependencies: 1939 - '@unocss/core': 66.3.3 1909 + '@unocss/core': 66.4.2 1940 1910 1941 - '@unocss/preset-icons@66.3.3': 1911 + '@unocss/preset-icons@66.4.2': 1942 1912 dependencies: 1943 - '@iconify/utils': 2.3.0 1944 - '@unocss/core': 66.3.3 1913 + '@iconify/utils': 3.0.0 1914 + '@unocss/core': 66.4.2 1945 1915 ofetch: 1.4.1 1946 1916 transitivePeerDependencies: 1947 1917 - supports-color 1948 1918 1949 - '@unocss/preset-mini@66.3.3': 1919 + '@unocss/preset-mini@66.4.2': 1950 1920 dependencies: 1951 - '@unocss/core': 66.3.3 1952 - '@unocss/extractor-arbitrary-variants': 66.3.3 1953 - '@unocss/rule-utils': 66.3.3 1921 + '@unocss/core': 66.4.2 1922 + '@unocss/extractor-arbitrary-variants': 66.4.2 1923 + '@unocss/rule-utils': 66.4.2 1954 1924 1955 - '@unocss/preset-tagify@66.3.3': 1925 + '@unocss/preset-tagify@66.4.2': 1956 1926 dependencies: 1957 - '@unocss/core': 66.3.3 1927 + '@unocss/core': 66.4.2 1958 1928 1959 - '@unocss/preset-typography@66.3.3': 1929 + '@unocss/preset-typography@66.4.2': 1960 1930 dependencies: 1961 - '@unocss/core': 66.3.3 1962 - '@unocss/preset-mini': 66.3.3 1963 - '@unocss/rule-utils': 66.3.3 1931 + '@unocss/core': 66.4.2 1932 + '@unocss/preset-mini': 66.4.2 1933 + '@unocss/rule-utils': 66.4.2 1964 1934 1965 - '@unocss/preset-uno@66.3.3': 1935 + '@unocss/preset-uno@66.4.2': 1966 1936 dependencies: 1967 - '@unocss/core': 66.3.3 1968 - '@unocss/preset-wind3': 66.3.3 1937 + '@unocss/core': 66.4.2 1938 + '@unocss/preset-wind3': 66.4.2 1969 1939 1970 - '@unocss/preset-web-fonts@66.3.3': 1940 + '@unocss/preset-web-fonts@66.4.2': 1971 1941 dependencies: 1972 - '@unocss/core': 66.3.3 1942 + '@unocss/core': 66.4.2 1973 1943 ofetch: 1.4.1 1974 1944 1975 - '@unocss/preset-wind3@66.3.3': 1945 + '@unocss/preset-wind3@66.4.2': 1976 1946 dependencies: 1977 - '@unocss/core': 66.3.3 1978 - '@unocss/preset-mini': 66.3.3 1979 - '@unocss/rule-utils': 66.3.3 1947 + '@unocss/core': 66.4.2 1948 + '@unocss/preset-mini': 66.4.2 1949 + '@unocss/rule-utils': 66.4.2 1980 1950 1981 - '@unocss/preset-wind4@66.3.3': 1951 + '@unocss/preset-wind4@66.4.2': 1982 1952 dependencies: 1983 - '@unocss/core': 66.3.3 1984 - '@unocss/extractor-arbitrary-variants': 66.3.3 1985 - '@unocss/rule-utils': 66.3.3 1953 + '@unocss/core': 66.4.2 1954 + '@unocss/extractor-arbitrary-variants': 66.4.2 1955 + '@unocss/rule-utils': 66.4.2 1986 1956 1987 - '@unocss/preset-wind@66.3.3': 1957 + '@unocss/preset-wind@66.4.2': 1988 1958 dependencies: 1989 - '@unocss/core': 66.3.3 1990 - '@unocss/preset-wind3': 66.3.3 1959 + '@unocss/core': 66.4.2 1960 + '@unocss/preset-wind3': 66.4.2 1991 1961 1992 - '@unocss/reset@66.3.3': {} 1962 + '@unocss/reset@66.4.2': {} 1993 1963 1994 - '@unocss/rule-utils@66.3.3': 1964 + '@unocss/rule-utils@66.4.2': 1995 1965 dependencies: 1996 - '@unocss/core': 66.3.3 1966 + '@unocss/core': 66.4.2 1997 1967 magic-string: 0.30.17 1998 1968 1999 - '@unocss/transformer-attributify-jsx@66.3.3': 1969 + '@unocss/transformer-attributify-jsx@66.4.2': 2000 1970 dependencies: 2001 - '@unocss/core': 66.3.3 1971 + '@babel/parser': 7.28.0 1972 + '@babel/traverse': 7.28.0 1973 + '@unocss/core': 66.4.2 1974 + transitivePeerDependencies: 1975 + - supports-color 2002 1976 2003 - '@unocss/transformer-compile-class@66.3.3': 1977 + '@unocss/transformer-compile-class@66.4.2': 2004 1978 dependencies: 2005 - '@unocss/core': 66.3.3 1979 + '@unocss/core': 66.4.2 2006 1980 2007 - '@unocss/transformer-directives@66.3.3': 1981 + '@unocss/transformer-directives@66.4.2': 2008 1982 dependencies: 2009 - '@unocss/core': 66.3.3 2010 - '@unocss/rule-utils': 66.3.3 1983 + '@unocss/core': 66.4.2 1984 + '@unocss/rule-utils': 66.4.2 2011 1985 css-tree: 3.1.0 2012 1986 2013 - '@unocss/transformer-variant-group@66.3.3': 1987 + '@unocss/transformer-variant-group@66.4.2': 2014 1988 dependencies: 2015 - '@unocss/core': 66.3.3 1989 + '@unocss/core': 66.4.2 2016 1990 2017 - '@unocss/vite@66.3.3(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3))': 1991 + '@unocss/vite@66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))': 2018 1992 dependencies: 2019 1993 '@ampproject/remapping': 2.3.0 2020 - '@unocss/config': 66.3.3 2021 - '@unocss/core': 66.3.3 2022 - '@unocss/inspector': 66.3.3(vue@3.5.13(typescript@5.8.3)) 1994 + '@unocss/config': 66.4.2 1995 + '@unocss/core': 66.4.2 1996 + '@unocss/inspector': 66.4.2 2023 1997 chokidar: 3.6.0 2024 1998 magic-string: 0.30.17 2025 1999 pathe: 2.0.3 2026 2000 tinyglobby: 0.2.14 2027 - unplugin-utils: 0.2.4 2028 - vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2029 - transitivePeerDependencies: 2030 - - vue 2031 - 2032 - '@vue/compiler-core@3.5.13': 2033 - dependencies: 2034 - '@babel/parser': 7.28.0 2035 - '@vue/shared': 3.5.13 2036 - entities: 4.5.0 2037 - estree-walker: 2.0.2 2038 - source-map-js: 1.2.1 2039 - 2040 - '@vue/compiler-dom@3.5.13': 2041 - dependencies: 2042 - '@vue/compiler-core': 3.5.13 2043 - '@vue/shared': 3.5.13 2044 - 2045 - '@vue/compiler-sfc@3.5.13': 2046 - dependencies: 2047 - '@babel/parser': 7.28.0 2048 - '@vue/compiler-core': 3.5.13 2049 - '@vue/compiler-dom': 3.5.13 2050 - '@vue/compiler-ssr': 3.5.13 2051 - '@vue/shared': 3.5.13 2052 - estree-walker: 2.0.2 2053 - magic-string: 0.30.17 2054 - postcss: 8.5.6 2055 - source-map-js: 1.2.1 2056 - 2057 - '@vue/compiler-ssr@3.5.13': 2058 - dependencies: 2059 - '@vue/compiler-dom': 3.5.13 2060 - '@vue/shared': 3.5.13 2061 - 2062 - '@vue/reactivity@3.5.13': 2063 - dependencies: 2064 - '@vue/shared': 3.5.13 2065 - 2066 - '@vue/runtime-core@3.5.13': 2067 - dependencies: 2068 - '@vue/reactivity': 3.5.13 2069 - '@vue/shared': 3.5.13 2070 - 2071 - '@vue/runtime-dom@3.5.13': 2072 - dependencies: 2073 - '@vue/reactivity': 3.5.13 2074 - '@vue/runtime-core': 3.5.13 2075 - '@vue/shared': 3.5.13 2076 - csstype: 3.1.3 2077 - 2078 - '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.3))': 2079 - dependencies: 2080 - '@vue/compiler-ssr': 3.5.13 2081 - '@vue/shared': 3.5.13 2082 - vue: 3.5.13(typescript@5.8.3) 2083 - 2084 - '@vue/shared@3.5.13': {} 2001 + unplugin-utils: 0.2.5 2002 + vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2085 2003 2086 2004 acorn@8.15.0: {} 2087 2005 ··· 2090 2008 normalize-path: 3.0.0 2091 2009 picomatch: 2.3.1 2092 2010 2093 - babel-plugin-jsx-dom-expressions@0.39.8(@babel/core@7.28.0): 2011 + babel-plugin-jsx-dom-expressions@0.40.1(@babel/core@7.28.0): 2094 2012 dependencies: 2095 2013 '@babel/core': 7.28.0 2096 2014 '@babel/helper-module-imports': 7.18.6 ··· 2100 2018 parse5: 7.3.0 2101 2019 validate-html-nesting: 1.2.3 2102 2020 2103 - babel-preset-solid@1.9.6(@babel/core@7.28.0): 2021 + babel-preset-solid@1.9.8(@babel/core@7.28.0)(solid-js@1.9.8): 2104 2022 dependencies: 2105 2023 '@babel/core': 7.28.0 2106 - babel-plugin-jsx-dom-expressions: 0.39.8(@babel/core@7.28.0) 2024 + babel-plugin-jsx-dom-expressions: 0.40.1(@babel/core@7.28.0) 2025 + optionalDependencies: 2026 + solid-js: 1.9.8 2107 2027 2108 2028 binary-extensions@2.3.0: {} 2109 2029 ··· 2111 2031 dependencies: 2112 2032 fill-range: 7.1.1 2113 2033 2114 - browserslist@4.25.1: 2034 + browserslist@4.25.2: 2115 2035 dependencies: 2116 - caniuse-lite: 1.0.30001731 2117 - electron-to-chromium: 1.5.192 2036 + caniuse-lite: 1.0.30001733 2037 + electron-to-chromium: 1.5.199 2118 2038 node-releases: 2.0.19 2119 - update-browserslist-db: 1.1.3(browserslist@4.25.1) 2039 + update-browserslist-db: 1.1.3(browserslist@4.25.2) 2120 2040 2121 2041 cac@6.7.14: {} 2122 2042 2123 - caniuse-lite@1.0.30001731: {} 2043 + caniuse-lite@1.0.30001733: {} 2124 2044 2125 2045 chokidar@3.6.0: 2126 2046 dependencies: ··· 2161 2081 2162 2082 duplexer@0.1.2: {} 2163 2083 2164 - electron-to-chromium@1.5.192: {} 2165 - 2166 - entities@4.5.0: {} 2084 + electron-to-chromium@1.5.199: {} 2167 2085 2168 2086 entities@6.0.1: {} 2169 2087 ··· 2228 2146 2229 2147 esm-env@1.2.2: {} 2230 2148 2231 - estree-walker@2.0.2: {} 2232 - 2233 2149 exsolve@1.0.7: {} 2234 2150 2235 2151 fdir@6.4.6(picomatch@4.0.3): ··· 2260 2176 dependencies: 2261 2177 duplexer: 0.1.2 2262 2178 2263 - hls.js@1.6.7: {} 2179 + hls.js@1.6.9: {} 2264 2180 2265 2181 html-entities@2.3.3: {} 2266 2182 ··· 2327 2243 2328 2244 nanoid@5.1.5: {} 2329 2245 2330 - node-fetch-native@1.6.6: {} 2246 + node-fetch-native@1.6.7: {} 2331 2247 2332 2248 node-releases@2.0.19: {} 2333 2249 ··· 2336 2252 ofetch@1.4.1: 2337 2253 dependencies: 2338 2254 destr: 2.0.5 2339 - node-fetch-native: 1.6.6 2255 + node-fetch-native: 1.6.7 2340 2256 ufo: 1.6.1 2341 2257 2342 2258 package-manager-detector@1.3.0: {} ··· 2428 2344 mrmime: 2.0.1 2429 2345 totalist: 3.0.1 2430 2346 2431 - solid-js@1.9.7: 2347 + solid-js@1.9.8: 2432 2348 dependencies: 2433 2349 csstype: 3.1.3 2434 2350 seroval: 1.3.2 2435 2351 seroval-plugins: 1.3.2(seroval@1.3.2) 2436 2352 2437 - solid-refresh@0.6.3(solid-js@1.9.7): 2353 + solid-refresh@0.6.3(solid-js@1.9.8): 2438 2354 dependencies: 2439 2355 '@babel/generator': 7.28.0 2440 2356 '@babel/helper-module-imports': 7.27.1 2441 2357 '@babel/types': 7.28.2 2442 - solid-js: 1.9.7 2358 + solid-js: 1.9.8 2443 2359 transitivePeerDependencies: 2444 2360 - supports-color 2445 2361 ··· 2466 2382 fsevents: 2.3.3 2467 2383 optional: true 2468 2384 2469 - typescript@5.8.3: {} 2385 + typescript@5.9.2: {} 2470 2386 2471 2387 ufo@1.6.1: {} 2472 2388 2473 2389 unconfig@7.3.2: 2474 2390 dependencies: 2475 - '@quansync/fs': 0.1.3 2391 + '@quansync/fs': 0.1.4 2476 2392 defu: 6.1.4 2477 2393 jiti: 2.5.1 2478 2394 quansync: 0.2.10 ··· 2480 2396 undici-types@6.20.0: 2481 2397 optional: true 2482 2398 2483 - unocss@66.3.3(postcss@8.5.6)(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3)): 2399 + unocss@66.4.2(postcss@8.5.6)(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)): 2484 2400 dependencies: 2485 - '@unocss/astro': 66.3.3(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3)) 2486 - '@unocss/cli': 66.3.3 2487 - '@unocss/core': 66.3.3 2488 - '@unocss/postcss': 66.3.3(postcss@8.5.6) 2489 - '@unocss/preset-attributify': 66.3.3 2490 - '@unocss/preset-icons': 66.3.3 2491 - '@unocss/preset-mini': 66.3.3 2492 - '@unocss/preset-tagify': 66.3.3 2493 - '@unocss/preset-typography': 66.3.3 2494 - '@unocss/preset-uno': 66.3.3 2495 - '@unocss/preset-web-fonts': 66.3.3 2496 - '@unocss/preset-wind': 66.3.3 2497 - '@unocss/preset-wind3': 66.3.3 2498 - '@unocss/preset-wind4': 66.3.3 2499 - '@unocss/transformer-attributify-jsx': 66.3.3 2500 - '@unocss/transformer-compile-class': 66.3.3 2501 - '@unocss/transformer-directives': 66.3.3 2502 - '@unocss/transformer-variant-group': 66.3.3 2503 - '@unocss/vite': 66.3.3(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))(vue@3.5.13(typescript@5.8.3)) 2401 + '@unocss/astro': 66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 2402 + '@unocss/cli': 66.4.2 2403 + '@unocss/core': 66.4.2 2404 + '@unocss/postcss': 66.4.2(postcss@8.5.6) 2405 + '@unocss/preset-attributify': 66.4.2 2406 + '@unocss/preset-icons': 66.4.2 2407 + '@unocss/preset-mini': 66.4.2 2408 + '@unocss/preset-tagify': 66.4.2 2409 + '@unocss/preset-typography': 66.4.2 2410 + '@unocss/preset-uno': 66.4.2 2411 + '@unocss/preset-web-fonts': 66.4.2 2412 + '@unocss/preset-wind': 66.4.2 2413 + '@unocss/preset-wind3': 66.4.2 2414 + '@unocss/preset-wind4': 66.4.2 2415 + '@unocss/transformer-attributify-jsx': 66.4.2 2416 + '@unocss/transformer-compile-class': 66.4.2 2417 + '@unocss/transformer-directives': 66.4.2 2418 + '@unocss/transformer-variant-group': 66.4.2 2419 + '@unocss/vite': 66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 2504 2420 optionalDependencies: 2505 - vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2421 + vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2506 2422 transitivePeerDependencies: 2507 2423 - postcss 2508 2424 - supports-color 2509 - - vue 2510 2425 2511 - unplugin-utils@0.2.4: 2426 + unplugin-utils@0.2.5: 2512 2427 dependencies: 2513 2428 pathe: 2.0.3 2514 2429 picomatch: 4.0.3 2515 2430 2516 - update-browserslist-db@1.1.3(browserslist@4.25.1): 2431 + update-browserslist-db@1.1.3(browserslist@4.25.2): 2517 2432 dependencies: 2518 - browserslist: 4.25.1 2433 + browserslist: 4.25.2 2519 2434 escalade: 3.2.0 2520 2435 picocolors: 1.1.1 2521 2436 2522 2437 validate-html-nesting@1.2.3: {} 2523 2438 2524 - vite-plugin-solid@2.11.8(solid-js@1.9.7)(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)): 2439 + vite-plugin-solid@2.11.8(solid-js@1.9.8)(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)): 2525 2440 dependencies: 2526 2441 '@babel/core': 7.28.0 2527 2442 '@types/babel__core': 7.20.5 2528 - babel-preset-solid: 1.9.6(@babel/core@7.28.0) 2443 + babel-preset-solid: 1.9.8(@babel/core@7.28.0)(solid-js@1.9.8) 2529 2444 merge-anything: 5.1.7 2530 - solid-js: 1.9.7 2531 - solid-refresh: 0.6.3(solid-js@1.9.7) 2532 - vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2533 - vitefu: 1.1.1(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 2445 + solid-js: 1.9.8 2446 + solid-refresh: 0.6.3(solid-js@1.9.8) 2447 + vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2448 + vitefu: 1.1.1(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)) 2534 2449 transitivePeerDependencies: 2535 2450 - supports-color 2536 2451 2537 - vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2): 2452 + vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2): 2538 2453 dependencies: 2539 2454 esbuild: 0.25.8 2540 2455 fdir: 6.4.6(picomatch@4.0.3) ··· 2548 2463 jiti: 2.5.1 2549 2464 tsx: 4.19.2 2550 2465 2551 - vitefu@1.1.1(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)): 2466 + vitefu@1.1.1(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)): 2552 2467 optionalDependencies: 2553 - vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2554 - 2555 - vue-flow-layout@0.1.1(vue@3.5.13(typescript@5.8.3)): 2556 - dependencies: 2557 - vue: 3.5.13(typescript@5.8.3) 2468 + vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2) 2558 2469 2559 - vue@3.5.13(typescript@5.8.3): 2560 - dependencies: 2561 - '@vue/compiler-dom': 3.5.13 2562 - '@vue/compiler-sfc': 3.5.13 2563 - '@vue/runtime-dom': 3.5.13 2564 - '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.3)) 2565 - '@vue/shared': 3.5.13 2566 - optionalDependencies: 2567 - typescript: 5.8.3 2470 + vue-flow-layout@0.2.0: {} 2568 2471 2569 2472 yallist@3.1.1: {} 2570 2473
+16 -16
src/components/account.tsx
··· 1 1 import { createSignal, onMount, For, Show } from "solid-js"; 2 2 import Tooltip from "./tooltip.jsx"; 3 3 import { deleteStoredSession, getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 4 - import { agent, Login, loginState, setLoginState } from "./login.jsx"; 4 + import { agent, Login, retrieveSession, setAgent } from "./login.jsx"; 5 5 import { Did } from "@atcute/lexicons"; 6 6 import { resolveDidDoc } from "../utils/api.js"; 7 7 import { createStore } from "solid-js/store"; ··· 14 14 const [avatar, setAvatar] = createSignal<string>(); 15 15 16 16 onMount(async () => { 17 + await retrieveSession(); 18 + 17 19 const storedSessions = localStorage.getItem("atcute-oauth:sessions"); 18 20 if (storedSessions) { 19 21 const sessionDids = Object.keys(JSON.parse(storedSessions)) as Did[]; ··· 33 35 if (repo) setAvatar(await getAvatar(repo as Did)); 34 36 }); 35 37 36 - const resumeSession = (did: Did) => { 38 + const resumeSession = async (did: Did) => { 37 39 localStorage.setItem("lastSignedIn", did); 38 - window.location.href = "/"; 40 + retrieveSession(); 41 + setAvatar(await getAvatar(did)); 39 42 }; 40 43 41 44 const removeSession = async (did: Did) => { 42 - const currentSession = agent?.sub; 45 + const currentSession = agent()?.sub; 43 46 try { 44 47 const session = await getSession(did, { allowStale: true }); 45 48 const agent = new OAuthUserAgent(session); ··· 48 51 deleteStoredSession(did); 49 52 } 50 53 setSessions(did, undefined); 51 - if (currentSession === did) { 52 - setLoginState(false); 53 - window.location.reload; 54 - } 54 + if (currentSession === did) setAgent(undefined); 55 55 }; 56 56 57 57 const getAvatar = async (did: Did) => { ··· 68 68 return ( 69 69 <> 70 70 <Modal open={openManager()} onClose={() => setOpenManager(false)}> 71 - <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900 backdrop-blur-xs left-50% absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 71 + <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 w-21rem dark:shadow-dark-900/80 backdrop-blur-xs left-50% absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 72 72 <h3 class="mb-2 font-bold">Manage accounts</h3> 73 - <div class="border-b-0.5 mb-2 max-h-[20rem] overflow-y-auto border-neutral-500 pb-2 md:max-h-[25rem]"> 73 + <div class="mb-3 max-h-[20rem] overflow-y-auto md:max-h-[25rem]"> 74 74 <For each={Object.keys(sessions)}> 75 75 {(did) => ( 76 76 <div class="group/select flex w-full items-center justify-between gap-x-2"> 77 77 <button 78 - class="flex max-w-[32ch] basis-full items-center justify-between truncate rounded bg-transparent px-1 text-left group-hover/select:bg-zinc-100 dark:group-hover/select:bg-neutral-600" 78 + class="flex basis-full items-center justify-between gap-1 truncate rounded bg-transparent px-1 text-left group-hover/select:bg-zinc-100 dark:group-hover/select:bg-neutral-600" 79 79 onclick={() => resumeSession(did as Did)} 80 80 > 81 - {sessions[did]?.length ? sessions[did] : did} 82 - <Show when={did === agent?.sub}> 83 - <div class="i-lucide-check" /> 81 + <span class="truncate">{sessions[did]?.length ? sessions[did] : did}</span> 82 + <Show when={did === agent()?.sub}> 83 + <div class="i-lucide-check shrink-0" /> 84 84 </Show> 85 85 </button> 86 86 <button onclick={() => removeSession(did as Did)}> ··· 95 95 </Modal> 96 96 <button onclick={() => setOpenManager(true)}> 97 97 <Tooltip text="Accounts"> 98 - {loginState() && avatar() ? 99 - <img src={avatar()} class="dark:shadow-dark-900 size-5 rounded-full shadow-sm" /> 98 + {agent() && avatar() ? 99 + <img src={avatar()} class="dark:shadow-dark-900/80 size-5 rounded-full shadow-sm" /> 100 100 : <div class="i-lucide-circle-user-round text-xl" />} 101 101 </Tooltip> 102 102 </button>
+3 -3
src/components/backlinks.tsx
··· 30 30 const filteredLinks = createMemo(() => linksBySource(links)); 31 31 32 32 return ( 33 - <div class="break-anywhere flex w-full flex-col"> 33 + <div class="break-anywhere flex w-full flex-col gap-1"> 34 34 <For each={filteredLinks().links}> 35 35 {({ collection, path, counts }) => ( 36 36 <div class="text-sm"> 37 37 <p> 38 38 <span title="Collection containing linking records">{collection}</span> 39 - <span class="text-cyan-500">@</span> 39 + <span class="text-neutral-400">@</span> 40 40 <span title="Record path where the link is found">{path.slice(1)}</span> 41 41 </p> 42 42 <div class="pl-2"> ··· 161 161 <button 162 162 type="button" 163 163 onclick={() => setMore(true)} 164 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 164 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 165 165 > 166 166 Load More 167 167 </button>
+26 -41
src/components/create.tsx
··· 5 5 import * as monaco from "monaco-editor"; 6 6 import { theme } from "../components/settings.jsx"; 7 7 import Tooltip from "./tooltip.jsx"; 8 - import { useParams } from "@solidjs/router"; 8 + import { useNavigate, useParams } from "@solidjs/router"; 9 9 import { remove } from "@mary/exif-rm"; 10 10 import { TextInput } from "./text-input.jsx"; 11 11 import { Modal } from "./modal.jsx"; 12 12 13 13 export const RecordEditor = (props: { create: boolean; record?: any }) => { 14 + const navigate = useNavigate(); 14 15 const params = useParams(); 15 16 const [openDialog, setOpenDialog] = createSignal(false); 16 17 const [notice, setNotice] = createSignal(""); ··· 18 19 let model: monaco.editor.IModel; 19 20 let formRef!: HTMLFormElement; 20 21 21 - const placeholder = (date: string) => { 22 + const placeholder = () => { 22 23 return { 23 24 $type: "app.bsky.feed.post", 24 25 text: "This post was sent from PDSls", ··· 31 32 }, 32 33 }, 33 34 langs: ["en"], 34 - createdAt: date, 35 + createdAt: new Date().toISOString(), 35 36 }; 36 37 }; 37 38 38 39 const createRecord = async (formData: FormData) => { 39 - const rpc = new Client({ handler: agent }); 40 + const rpc = new Client({ handler: agent()! }); 40 41 const collection = formData.get("collection"); 41 42 const rkey = formData.get("rkey"); 42 43 const validate = formData.get("validate")?.toString(); ··· 49 50 } 50 51 const res = await rpc.post("com.atproto.repo.createRecord", { 51 52 input: { 52 - repo: agent.sub, 53 + repo: agent()!.sub, 53 54 collection: collection ? collection.toString() : record.$type, 54 55 rkey: rkey?.toString().length ? rkey?.toString() : undefined, 55 56 record: record, ··· 64 65 return; 65 66 } 66 67 setOpenDialog(false); 67 - window.location.href = `/${res.data.uri}`; 68 + navigate(`/${res.data.uri}`); 68 69 }; 69 70 70 71 const editRecord = async (formData: FormData) => { ··· 74 75 : formData.get("validate")?.toString() === "false" ? false 75 76 : undefined; 76 77 if (!record) return; 77 - const rpc = new Client({ handler: agent }); 78 + const rpc = new Client({ handler: agent()! }); 78 79 try { 79 80 const editedRecord = JSON.parse(record.toString()); 80 81 if (formData.get("recreate")) { 81 82 const res = await rpc.post("com.atproto.repo.applyWrites", { 82 83 input: { 83 - repo: agent.sub, 84 + repo: agent()!.sub, 84 85 validate: validate, 85 86 writes: [ 86 87 { ··· 104 105 } else { 105 106 const res = await rpc.post("com.atproto.repo.putRecord", { 106 107 input: { 107 - repo: agent.sub, 108 + repo: agent()!.sub, 108 109 collection: params.collection as `${string}.${string}.${string}`, 109 110 rkey: params.rkey, 110 111 record: editedRecord, ··· 140 141 if (exifRemoved !== null) blob = new Blob([exifRemoved], { type: blob.type }); 141 142 } 142 143 143 - const rpc = new Client({ handler: agent }); 144 + const rpc = new Client({ handler: agent()! }); 144 145 setUploading(true); 145 146 const res = await rpc.post("com.atproto.repo.uploadBlob", { 146 147 input: blob, ··· 163 164 const createModel = () => { 164 165 if (!model) 165 166 model = monaco.editor.createModel( 166 - JSON.stringify( 167 - props.create ? placeholder(new Date().toISOString()) : props.record, 168 - null, 169 - 2, 170 - ), 167 + JSON.stringify(props.create ? placeholder() : props.record, null, 2), 171 168 "json", 172 169 ); 173 170 }; ··· 175 172 return ( 176 173 <> 177 174 <Modal open={openDialog()} onClose={() => setOpenDialog(false)}> 178 - <div class="w-21rem sm:w-xl lg:w-50rem starting:opacity-0 dark:bg-dark-800/70 left-50% backdrop-blur-xs border-0.5 dark:shadow-dark-900 absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-2 text-slate-900 shadow-md transition-opacity duration-300 sm:p-4 dark:border-neutral-700 dark:text-slate-100"> 175 + <div class="w-21rem sm:w-xl lg:w-48rem starting:opacity-0 dark:bg-dark-800/70 left-50% backdrop-blur-xs border-0.5 dark:shadow-dark-900/80 absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-2 text-slate-900 shadow-md transition-opacity duration-300 sm:p-4 dark:border-neutral-700 dark:text-slate-100"> 179 176 <div class="mb-2 flex w-full justify-between"> 180 177 <h3 class="font-bold">{props.create ? "Creating" : "Editing"} record</h3> 181 178 <div ··· 211 208 <select 212 209 name="validate" 213 210 id="validate" 214 - class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-1 py-1 shadow-sm focus:outline-blue-500" 211 + class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 rounded-lg bg-white px-1 py-1 shadow-sm focus:outline-slate-900 dark:focus:outline-slate-100" 215 212 > 216 213 <option value="unset">Unset</option> 217 214 <option value="true">True</option> ··· 220 217 </div> 221 218 <div class="flex items-center gap-2"> 222 219 <Show when={!uploading()}> 223 - <div class="dark:hover:bg-dark-100 dark:bg-dark-300 focus-within:outline-1.5 dark:shadow-dark-900 flex rounded-lg bg-white text-xs font-bold shadow-sm focus-within:outline-blue-500 hover:bg-zinc-100"> 220 + <div class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 flex rounded-lg bg-white text-xs font-bold shadow-sm hover:bg-zinc-100"> 224 221 <input type="file" id="blob" hidden onChange={() => uploadBlob()} /> 225 222 <label class="flex items-center gap-1 px-2 py-1.5" for="blob"> 226 223 <div class="i-lucide-upload text-sm" /> ··· 270 267 createRecord(new FormData(formRef)) 271 268 : editRecord(new FormData(formRef)) 272 269 } 273 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-100 focus:outline-blue-500 sm:text-sm" 270 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-100 sm:text-sm" 274 271 > 275 272 {props.create ? "Create" : "Edit"} 276 273 </button> ··· 279 276 </form> 280 277 </div> 281 278 </Modal> 282 - <Show when={props.create}> 283 - <button 284 - onclick={() => { 285 - createModel(); 286 - setOpenDialog(true); 287 - }} 288 - > 289 - <Tooltip text="Create record" children={<div class="i-lucide-square-pen text-xl" />} /> 290 - </button> 291 - </Show> 292 - <Show when={!props.create}> 293 - <button 294 - onclick={() => { 295 - createModel(); 296 - setOpenDialog(true); 297 - }} 298 - > 299 - <Tooltip text="Edit"> 300 - <div class="i-lucide-pencil text-xl" /> 301 - </Tooltip> 302 - </button> 303 - </Show> 279 + <button 280 + onclick={() => { 281 + createModel(); 282 + setOpenDialog(true); 283 + }} 284 + > 285 + <Tooltip text={`${props.create ? "Create" : "Edit"} record`}> 286 + <div class={`${props.create ? "i-lucide-square-pen" : "i-lucide-pencil"} text-xl`} /> 287 + </Tooltip> 288 + </button> 304 289 </> 305 290 ); 306 291 };
+6 -1
src/components/editor.tsx
··· 3 3 import * as monaco from "monaco-editor"; 4 4 import { onMount } from "solid-js"; 5 5 6 + const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 1; 7 + 6 8 self.MonacoEnvironment = { 7 9 getWorker(_, label) { 8 10 if (label === "json") return new jsonWorker(); ··· 22 24 model: props.model, 23 25 wordWrap: "on", 24 26 automaticLayout: true, 27 + fontFamily: "Roboto Mono", 28 + lineNumbers: isTouchDevice ? "off" : "on", 29 + fontSize: 12, 25 30 }); 26 31 }); 27 32 28 - return <div ref={editorDiv} class="h-20rem sm:h-24rem dark:shadow-dark-900 shadow-sm"></div>; 33 + return <div ref={editorDiv} class="h-20rem sm:h-24rem dark:shadow-dark-900/80 shadow-sm"></div>; 29 34 }; 30 35 31 36 export { Editor, editor };
+8 -10
src/components/login.tsx
··· 21 21 }, 22 22 }); 23 23 24 - export const [loginState, setLoginState] = createSignal(false); 25 - let agent: OAuthUserAgent; 24 + export const [agent, setAgent] = createSignal<OAuthUserAgent | undefined>(); 26 25 27 26 const Login = () => { 28 27 const [notice, setNotice] = createSignal(""); ··· 58 57 59 58 return ( 60 59 <form class="flex flex-col gap-y-1" onsubmit={(e) => e.preventDefault()}> 61 - <label for="handle">Add new account</label> 62 - <div class="flex items-center justify-between gap-2"> 60 + <div class="flex items-center gap-2"> 61 + <label for="handle"> 62 + <div class="i-lucide-user-round-plus text-lg" /> 63 + </label> 63 64 <TextInput 64 65 id="handle" 65 66 placeholder="user.bsky.social" ··· 70 71 <div class="i-lucide-log-in text-lg" /> 71 72 </button> 72 73 </div> 73 - <div class="max-w-20rem">{notice()}</div> 74 + <div>{notice()}</div> 74 75 </form> 75 76 ); 76 77 }; ··· 104 105 105 106 const session = await init().catch(() => {}); 106 107 107 - if (session) { 108 - agent = new OAuthUserAgent(session); 109 - setLoginState(true); 110 - } 108 + if (session) setAgent(new OAuthUserAgent(session)); 111 109 }; 112 110 113 - export { Login, retrieveSession, agent }; 111 + export { Login, retrieveSession };
+33 -43
src/components/navbar.tsx
··· 186 186 </div> 187 187 </Show> 188 188 <Show when={props.params.rkey}> 189 - <div class="mt-1 flex items-center justify-between"> 190 - <div class="flex items-center gap-2"> 191 - <Tooltip text="Record"> 192 - <button onclick={() => addToClipboard(props.params.rkey)}> 193 - <div class="i-lucide-braces text-lg" /> 194 - </button> 195 - </Tooltip> 196 - <div class="flex gap-1"> 197 - <span>{props.params.rkey}</span> 198 - <Show when={validRecord()}> 199 - <Tooltip text="Valid record"> 200 - <div class="i-lucide-lock-keyhole" /> 201 - </Tooltip> 202 - </Show> 203 - <Show when={validRecord() === false}> 204 - <Tooltip text="Invalid record"> 205 - <div class="i-lucide-lock-keyhole-open text-red-500 dark:text-red-400" /> 206 - </Tooltip> 207 - </Show> 208 - <Show when={validRecord() === undefined}> 209 - <Tooltip text="Validating"> 210 - <div class="i-lucide-loader-circle animate-spin" /> 211 - </Tooltip> 212 - </Show> 213 - <Show when={validSchema()}> 214 - <Tooltip text="Valid schema"> 215 - <div class="i-lucide-file-check" /> 216 - </Tooltip> 217 - </Show> 218 - <Show when={validSchema() === false}> 219 - <Tooltip text="Invalid schema"> 220 - <div class="i-lucide-file-x text-red-500 dark:text-red-400" /> 221 - </Tooltip> 222 - </Show> 223 - </div> 224 - </div> 225 - <Tooltip text="Record on PDS"> 226 - <a 227 - href={`https://${pds()}/xrpc/com.atproto.repo.getRecord?repo=${props.params.repo}&collection=${props.params.collection}&rkey=${props.params.rkey}`} 228 - target="_blank" 229 - > 230 - <div class="i-lucide-external-link text-lg" /> 231 - </a> 189 + <div class="mt-1 flex items-center gap-2"> 190 + <Tooltip text="Record"> 191 + <button onclick={() => addToClipboard(props.params.rkey)}> 192 + <div class="i-lucide-braces text-lg" /> 193 + </button> 232 194 </Tooltip> 195 + <div class="flex gap-1"> 196 + <span>{props.params.rkey}</span> 197 + <Show when={validRecord()}> 198 + <Tooltip text="Valid record"> 199 + <div class="i-lucide-lock-keyhole" /> 200 + </Tooltip> 201 + </Show> 202 + <Show when={validRecord() === false}> 203 + <Tooltip text="Invalid record"> 204 + <div class="i-lucide-lock-keyhole-open text-red-500 dark:text-red-400" /> 205 + </Tooltip> 206 + </Show> 207 + <Show when={validRecord() === undefined}> 208 + <Tooltip text="Validating"> 209 + <div class="i-lucide-loader-circle animate-spin" /> 210 + </Tooltip> 211 + </Show> 212 + <Show when={validSchema()}> 213 + <Tooltip text="Valid schema"> 214 + <div class="i-lucide-file-check" /> 215 + </Tooltip> 216 + </Show> 217 + <Show when={validSchema() === false}> 218 + <Tooltip text="Invalid schema"> 219 + <div class="i-lucide-file-x text-red-500 dark:text-red-400" /> 220 + </Tooltip> 221 + </Show> 222 + </div> 233 223 </div> 234 224 </Show> 235 225 </div>
+12 -27
src/components/search.tsx
··· 1 1 import { resolveHandle } from "../utils/api.js"; 2 - import { A } from "@solidjs/router"; 2 + import { A, useNavigate } from "@solidjs/router"; 3 3 import Tooltip from "./tooltip.jsx"; 4 - import { createSignal, onCleanup, onMount, Show } from "solid-js"; 5 - import { agent, loginState } from "../components/login.jsx"; 4 + import { createSignal, Show } from "solid-js"; 5 + import { agent } from "../components/login.jsx"; 6 6 import { Handle } from "@atcute/lexicons"; 7 7 8 - const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 1; 9 - 10 8 const Search = () => { 9 + const navigate = useNavigate(); 11 10 let searchInput!: HTMLInputElement; 12 11 const [loading, setLoading] = createSignal(false); 13 12 ··· 20 19 !input.startsWith("https://deer.social/") && 21 20 (input.startsWith("https://") || input.startsWith("http://")) 22 21 ) { 23 - window.location.href = `/${input.replace("https://", "").replace("http://", "").replace("/", "")}`; 22 + navigate(`/${input.replace("https://", "").replace("http://", "").replace("/", "")}`); 24 23 return; 25 24 } 26 25 ··· 37 36 did = uri.startsWith("did:") ? actor : await resolveHandle(actor as Handle); 38 37 setLoading(false); 39 38 } catch { 40 - window.location.href = `/${actor}`; 39 + setLoading(false); 40 + navigate(`/${actor}`); 41 41 return; 42 42 } 43 - window.location.href = `/at://${did}${uriParts.length > 1 ? `/${uriParts.slice(1).join("/")}` : ""}`; 44 - }; 45 - 46 - onMount(() => window.addEventListener("keydown", keyEvent)); 47 - onCleanup(() => window.removeEventListener("keydown", keyEvent)); 48 - 49 - const keyEvent = (event: KeyboardEvent) => { 50 - if (event.key == "/" && document.activeElement !== searchInput) { 51 - event.preventDefault(); 52 - searchInput.focus(); 53 - } 54 - if (event.key == "Escape" && document.activeElement === searchInput) { 55 - event.preventDefault(); 56 - searchInput.blur(); 57 - } 43 + navigate(`/at://${did}${uriParts.length > 1 ? `/${uriParts.slice(1).join("/")}` : ""}`); 58 44 }; 59 45 60 46 return ( ··· 65 51 > 66 52 <div class="w-full"> 67 53 <label for="input" class="ml-0.5 text-sm"> 68 - PDS URL or AT URI 54 + PDS URL or AT URI (at:// optional) 69 55 </label> 70 56 </div> 71 57 <div class="flex w-full items-center gap-2"> 72 - <div class="dark:bg-dark-100 focus-within:outline-1.5 dark:shadow-dark-900 flex grow items-center gap-2 rounded-lg bg-white px-2 py-1 shadow-sm focus-within:outline-blue-500"> 58 + <div class="dark:bg-dark-100 focus-within:outline-1.5 dark:shadow-dark-900/80 flex grow items-center gap-2 rounded-lg bg-white px-2 py-1 shadow-sm focus-within:outline-slate-900 dark:focus-within:outline-slate-100"> 73 59 <input 74 60 type="text" 75 61 spellcheck={false} 76 62 ref={searchInput} 77 63 id="input" 78 - placeholder={isTouchDevice ? "" : "Type / to search"} 79 64 class="grow focus:outline-none" 80 65 /> 81 66 <Show when={loading()}> ··· 87 72 </button> 88 73 </Show> 89 74 </div> 90 - <Show when={loginState()}> 75 + <Show when={agent()}> 91 76 <Tooltip 92 77 text="Repository" 93 78 children={ 94 - <A href={`/at://${agent.sub}`} class="flex"> 79 + <A href={`/at://${agent()?.sub}`} class="flex"> 95 80 <div class="i-lucide-house text-xl" /> 96 81 </A> 97 82 }
+8 -5
src/components/settings.tsx
··· 56 56 return ( 57 57 <> 58 58 <Modal open={openSettings()} onClose={() => setOpenSettings(false)}> 59 - <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900 backdrop-blur-xs left-50% absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 59 + <div class="starting:opacity-0 w-21rem dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900/80 backdrop-blur-xs left-50% absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 60 60 <h3 class="border-b-0.5 mb-2 border-neutral-500 pb-2 font-bold">Settings</h3> 61 61 <h4 class="mb-1 font-semibold">Theme</h4> 62 - <div class="w-xs flex gap-2"> 62 + <div class="flex w-full gap-1"> 63 63 <button 64 64 classList={{ 65 - "basis-1/3 py-1 rounded-lg": true, 65 + "basis-1/3 py-1 rounded-full justify-center flex items-center gap-1": true, 66 66 "bg-transparent hover:bg-neutral-100 dark:hover:bg-dark-200": !theme().system, 67 67 "bg-white dark:bg-neutral-600 font-semibold": theme().system, 68 68 }} ··· 74 74 }) 75 75 } 76 76 > 77 + <div class="i-lucide-monitor" /> 77 78 System 78 79 </button> 79 80 <button 80 81 classList={{ 81 - "basis-1/3 py-1 rounded-lg": true, 82 + "basis-1/3 py-1 rounded-full justify-center flex items-center gap-1": true, 82 83 "bg-transparent hover:bg-neutral-100 dark:hover:bg-dark-200": 83 84 theme().color !== "light" || theme().system, 84 85 "bg-white font-semibold": theme().color === "light" && !theme().system, 85 86 }} 86 87 onclick={() => updateTheme({ color: "light", system: false })} 87 88 > 89 + <div class="i-lucide-sun" /> 88 90 Light 89 91 </button> 90 92 <button 91 93 classList={{ 92 - "basis-1/3 py-1 rounded-lg": true, 94 + "basis-1/3 py-1 justify-center rounded-full flex items-center gap-1": true, 93 95 "bg-transparent hover:bg-neutral-100 dark:hover:bg-dark-200": 94 96 theme().color !== "dark" || theme().system, 95 97 "bg-neutral-600 font-semibold": theme().color === "dark" && !theme().system, 96 98 }} 97 99 onclick={() => updateTheme({ color: "dark", system: false })} 98 100 > 101 + <div class="i-lucide-moon" /> 99 102 Dark 100 103 </button> 101 104 </div>
+1 -1
src/components/text-input.tsx
··· 25 25 disabled={props.disabled} 26 26 required={props.required} 27 27 class={ 28 - "dark:bg-dark-100 focus:outline-1.5 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-blue-500 dark:shadow-dark-900 " + 28 + "dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-slate-900 dark:focus:outline-slate-100 " + 29 29 props.class 30 30 } 31 31 onInput={props.onInput}
+13 -16
src/components/tooltip.tsx
··· 2 2 3 3 const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 1; 4 4 5 - const Tooltip = (props: { text: string; children: JSX.Element }) => { 6 - const width = (props.text.length - 1).toString(); 7 - return ( 8 - <div class="group/tooltip relative flex items-center"> 9 - {props.children} 10 - <Show when={!isTouchDevice}> 11 - <span 12 - style={`transform: translate(-50%, 28px); min-width: ${width}ch`} 13 - class={`left-50% border-0.5 dark:shadow-dark-900 pointer-events-none absolute z-10 hidden select-none whitespace-nowrap rounded border-neutral-300 bg-white p-1 text-center font-sans text-xs text-slate-900 shadow-md group-hover/tooltip:inline dark:border-neutral-600 dark:bg-neutral-800 dark:text-slate-100`} 14 - > 15 - {props.text} 16 - </span> 17 - </Show> 18 - </div> 19 - ); 20 - }; 5 + const Tooltip = (props: { text: string; children: JSX.Element }) => ( 6 + <div class="group/tooltip relative flex items-center"> 7 + {props.children} 8 + <Show when={!isTouchDevice}> 9 + <span 10 + style={`transform: translate(-50%, 28px)`} 11 + class={`left-50% border-0.5 dark:shadow-dark-900/80 pointer-events-none absolute z-10 hidden min-w-fit select-none whitespace-nowrap rounded border-neutral-300 bg-white p-1 text-center font-sans text-xs text-slate-900 shadow-md group-hover/tooltip:inline dark:border-neutral-600 dark:bg-neutral-800 dark:text-slate-100`} 12 + > 13 + {props.text} 14 + </span> 15 + </Show> 16 + </div> 17 + ); 21 18 22 19 export default Tooltip;
+20 -19
src/layout.tsx
··· 1 - import { createEffect, ErrorBoundary, onMount, Show, Suspense } from "solid-js"; 2 - import { A, RouteSectionProps, useLocation, useParams } from "@solidjs/router"; 3 - import { agent, loginState, retrieveSession } from "./components/login.jsx"; 1 + import { createEffect, ErrorBoundary, Show, Suspense } from "solid-js"; 2 + import { A, RouteSectionProps, useLocation, useNavigate, useParams } from "@solidjs/router"; 3 + import { agent } from "./components/login.jsx"; 4 4 import { RecordEditor } from "./components/create.jsx"; 5 5 import Tooltip from "./components/tooltip.jsx"; 6 6 import { NavBar } from "./components/navbar.jsx"; 7 7 import { Search } from "./components/search.jsx"; 8 8 import { AccountManager } from "./components/account.jsx"; 9 9 import { resolveHandle } from "./utils/api.js"; 10 - import { Meta, MetaProvider } from "@solidjs/meta"; 10 + import { Meta, MetaProvider, Title } from "@solidjs/meta"; 11 11 import { kawaii, Settings } from "./components/settings.jsx"; 12 12 import { Handle } from "@atcute/lexicons"; 13 13 import { copyNotice } from "./utils/copy.js"; 14 14 15 + const customTitle: Record<string, string> = { 16 + "did:plc:hx53snho72xoj7zqt5uice4u": "wrenls", 17 + }; 18 + 15 19 const Layout = (props: RouteSectionProps<unknown>) => { 16 20 const params = useParams(); 17 21 const location = useLocation(); 18 - 19 - onMount(async () => { 20 - if (location.search.includes("kawaii=true")) localStorage.kawaii = "true"; 21 - await retrieveSession(); 22 - if (loginState() && location.pathname === "/") window.location.href = `/at://${agent.sub}`; 23 - }); 22 + const navigate = useNavigate(); 23 + if (location.search.includes("kawaii=true")) localStorage.kawaii = "true"; 24 24 25 25 createEffect(async () => { 26 26 if (params.repo && !params.repo.startsWith("did:")) { 27 27 const did = await resolveHandle(params.repo as Handle); 28 - window.location.replace(location.pathname.replace(params.repo, did)); 28 + navigate(location.pathname.replace(params.repo, did)); 29 29 } 30 30 }); 31 31 32 32 return ( 33 33 <div id="main" class="m-4 flex flex-col items-center text-slate-900 dark:text-slate-100"> 34 - <Show when={location.pathname !== "/"}> 35 - <MetaProvider> 34 + <MetaProvider> 35 + <Show when={location.pathname !== "/"}> 36 36 <Meta name="robots" content="noindex, nofollow" /> 37 - </MetaProvider> 38 - </Show> 37 + </Show> 38 + <Title>{customTitle[params.repo] ?? "PDSls"}</Title> 39 + </MetaProvider> 39 40 <div class="mb-2 flex w-[21rem] items-center sm:w-[24rem]"> 40 41 <div class="flex basis-1/3 gap-x-2"> 41 42 <A href="/jetstream"> ··· 47 48 </div> 48 49 <div class="flex basis-1/3 items-center justify-center text-center"> 49 50 <A href="/" class="font-mono font-bold hover:underline"> 50 - PDSls 51 + {customTitle[params.repo] ?? "PDSls"} 51 52 </A> 52 - <Show when={location.search.includes("kawaii=true") || kawaii()}> 53 + <Show when={localStorage.kawaii === "true" || kawaii()}> 53 54 <a 54 55 href="https://bsky.app/profile/ninikyuu.bsky.social/post/3l3tq5xwqf22o" 55 56 target="_blank" ··· 64 65 </Show> 65 66 </div> 66 67 <div class="justify-right flex basis-1/3 items-center gap-x-2"> 67 - <Show when={loginState()}> 68 + <Show when={agent()}> 68 69 <RecordEditor create={true} /> 69 70 </Show> 70 71 <Settings /> ··· 92 93 </Show> 93 94 </div> 94 95 <Show when={copyNotice()}> 95 - <div class="dark:bg-dark-100 dark:shadow-dark-900 fixed bottom-8 z-10 flex items-center rounded-md bg-neutral-200 p-2 shadow-md"> 96 + <div class="backdrop-blur-xs border-0.5 dark:shadow-dark-900/80 dark:bg-dark-100/70 fixed bottom-10 z-50 flex items-center rounded-lg border-neutral-300 bg-zinc-100/70 p-2 shadow-md dark:border-neutral-700"> 96 97 <div class="i-lucide-clipboard-check mr-1 text-xl" /> 97 98 Copied to clipboard 98 99 </div>
+416
src/utils/plc-logs.ts
··· 1 + // courtesy of the best ๐Ÿ‡ mary 2 + // https://github.com/mary-ext/boat/blob/trunk/src/views/identity/plc-oplogs.tsx 3 + import { IndexedEntry, Service } from "@atcute/did-plc"; 4 + 5 + export type DiffEntry = 6 + | { 7 + type: "identity_created"; 8 + orig: IndexedEntry; 9 + nullified: boolean; 10 + at: string; 11 + rotationKeys: string[]; 12 + verificationMethods: Record<string, string>; 13 + alsoKnownAs: string[]; 14 + services: Record<string, { type: string; endpoint: string }>; 15 + } 16 + | { 17 + type: "identity_tombstoned"; 18 + orig: IndexedEntry; 19 + nullified: boolean; 20 + at: string; 21 + } 22 + | { 23 + type: "rotation_key_added"; 24 + orig: IndexedEntry; 25 + nullified: boolean; 26 + at: string; 27 + rotation_key: string; 28 + } 29 + | { 30 + type: "rotation_key_removed"; 31 + orig: IndexedEntry; 32 + nullified: boolean; 33 + at: string; 34 + rotation_key: string; 35 + } 36 + | { 37 + type: "verification_method_added"; 38 + orig: IndexedEntry; 39 + nullified: boolean; 40 + at: string; 41 + method_id: string; 42 + method_key: string; 43 + } 44 + | { 45 + type: "verification_method_removed"; 46 + orig: IndexedEntry; 47 + nullified: boolean; 48 + at: string; 49 + method_id: string; 50 + method_key: string; 51 + } 52 + | { 53 + type: "verification_method_changed"; 54 + orig: IndexedEntry; 55 + nullified: boolean; 56 + at: string; 57 + method_id: string; 58 + prev_method_key: string; 59 + next_method_key: string; 60 + } 61 + | { 62 + type: "handle_added"; 63 + orig: IndexedEntry; 64 + nullified: boolean; 65 + at: string; 66 + handle: string; 67 + } 68 + | { 69 + type: "handle_removed"; 70 + orig: IndexedEntry; 71 + nullified: boolean; 72 + at: string; 73 + handle: string; 74 + } 75 + | { 76 + type: "handle_changed"; 77 + orig: IndexedEntry; 78 + nullified: boolean; 79 + at: string; 80 + prev_handle: string; 81 + next_handle: string; 82 + } 83 + | { 84 + type: "service_added"; 85 + orig: IndexedEntry; 86 + nullified: boolean; 87 + at: string; 88 + service_id: string; 89 + service_type: string; 90 + service_endpoint: string; 91 + } 92 + | { 93 + type: "service_removed"; 94 + orig: IndexedEntry; 95 + nullified: boolean; 96 + at: string; 97 + service_id: string; 98 + service_type: string; 99 + service_endpoint: string; 100 + } 101 + | { 102 + type: "service_changed"; 103 + orig: IndexedEntry; 104 + nullified: boolean; 105 + at: string; 106 + service_id: string; 107 + prev_service_type: string; 108 + next_service_type: string; 109 + prev_service_endpoint: string; 110 + next_service_endpoint: string; 111 + }; 112 + 113 + export const createOperationHistory = (entries: IndexedEntry[]): DiffEntry[] => { 114 + const history: DiffEntry[] = []; 115 + 116 + for (let idx = 0, len = entries.length; idx < len; idx++) { 117 + const entry = entries[idx]; 118 + const op = entry.operation; 119 + 120 + if (op.type === "create") { 121 + history.push({ 122 + type: "identity_created", 123 + orig: entry, 124 + nullified: entry.nullified, 125 + at: entry.createdAt, 126 + rotationKeys: [op.recoveryKey, op.signingKey], 127 + verificationMethods: { atproto: op.signingKey }, 128 + alsoKnownAs: [`at://${op.handle}`], 129 + services: { 130 + atproto_pds: { 131 + type: "AtprotoPersonalDataServer", 132 + endpoint: op.service, 133 + }, 134 + }, 135 + }); 136 + } else if (op.type === "plc_operation") { 137 + const prevOp = findLastMatching(entries, (entry) => !entry.nullified, idx - 1)?.operation; 138 + 139 + let oldRotationKeys: string[]; 140 + let oldVerificationMethods: Record<string, string>; 141 + let oldAlsoKnownAs: string[]; 142 + let oldServices: Record<string, Service>; 143 + 144 + if (!prevOp) { 145 + history.push({ 146 + type: "identity_created", 147 + orig: entry, 148 + nullified: entry.nullified, 149 + at: entry.createdAt, 150 + rotationKeys: op.rotationKeys, 151 + verificationMethods: op.verificationMethods, 152 + alsoKnownAs: op.alsoKnownAs, 153 + services: op.services, 154 + }); 155 + 156 + continue; 157 + } else if (prevOp.type === "create") { 158 + oldRotationKeys = [prevOp.recoveryKey, prevOp.signingKey]; 159 + oldVerificationMethods = { atproto: prevOp.signingKey }; 160 + oldAlsoKnownAs = [`at://${prevOp.handle}`]; 161 + oldServices = { 162 + atproto_pds: { 163 + type: "AtprotoPersonalDataServer", 164 + endpoint: prevOp.service, 165 + }, 166 + }; 167 + } else if (prevOp.type === "plc_operation") { 168 + oldRotationKeys = prevOp.rotationKeys; 169 + oldVerificationMethods = prevOp.verificationMethods; 170 + oldAlsoKnownAs = prevOp.alsoKnownAs; 171 + oldServices = prevOp.services; 172 + } else { 173 + continue; 174 + } 175 + 176 + // Check for rotation key changes 177 + { 178 + const additions = difference(op.rotationKeys, oldRotationKeys); 179 + const removals = difference(oldRotationKeys, op.rotationKeys); 180 + 181 + for (const key of additions) { 182 + history.push({ 183 + type: "rotation_key_added", 184 + orig: entry, 185 + nullified: entry.nullified, 186 + at: entry.createdAt, 187 + rotation_key: key, 188 + }); 189 + } 190 + 191 + for (const key of removals) { 192 + history.push({ 193 + type: "rotation_key_removed", 194 + orig: entry, 195 + nullified: entry.nullified, 196 + at: entry.createdAt, 197 + rotation_key: key, 198 + }); 199 + } 200 + } 201 + 202 + // Check for verification method changes 203 + { 204 + for (const id in op.verificationMethods) { 205 + if (!(id in oldVerificationMethods)) { 206 + history.push({ 207 + type: "verification_method_added", 208 + orig: entry, 209 + nullified: entry.nullified, 210 + at: entry.createdAt, 211 + method_id: id, 212 + method_key: op.verificationMethods[id], 213 + }); 214 + } else if (op.verificationMethods[id] !== oldVerificationMethods[id]) { 215 + history.push({ 216 + type: "verification_method_changed", 217 + orig: entry, 218 + nullified: entry.nullified, 219 + at: entry.createdAt, 220 + method_id: id, 221 + prev_method_key: oldVerificationMethods[id], 222 + next_method_key: op.verificationMethods[id], 223 + }); 224 + } 225 + } 226 + 227 + for (const id in oldVerificationMethods) { 228 + if (!(id in op.verificationMethods)) { 229 + history.push({ 230 + type: "verification_method_removed", 231 + orig: entry, 232 + nullified: entry.nullified, 233 + at: entry.createdAt, 234 + method_id: id, 235 + method_key: oldVerificationMethods[id], 236 + }); 237 + } 238 + } 239 + } 240 + 241 + // Check for handle changes 242 + if (op.alsoKnownAs.length === 1 && oldAlsoKnownAs.length === 1) { 243 + if (op.alsoKnownAs[0] !== oldAlsoKnownAs[0]) { 244 + history.push({ 245 + type: "handle_changed", 246 + orig: entry, 247 + nullified: entry.nullified, 248 + at: entry.createdAt, 249 + prev_handle: oldAlsoKnownAs[0], 250 + next_handle: op.alsoKnownAs[0], 251 + }); 252 + } 253 + } else { 254 + const additions = difference(op.alsoKnownAs, oldAlsoKnownAs); 255 + const removals = difference(oldAlsoKnownAs, op.alsoKnownAs); 256 + 257 + for (const handle of additions) { 258 + history.push({ 259 + type: "handle_added", 260 + orig: entry, 261 + nullified: entry.nullified, 262 + at: entry.createdAt, 263 + handle: handle, 264 + }); 265 + } 266 + 267 + for (const handle of removals) { 268 + history.push({ 269 + type: "handle_removed", 270 + orig: entry, 271 + nullified: entry.nullified, 272 + at: entry.createdAt, 273 + handle: handle, 274 + }); 275 + } 276 + } 277 + 278 + // Check for service changes 279 + { 280 + for (const id in op.services) { 281 + if (!(id in oldServices)) { 282 + history.push({ 283 + type: "service_added", 284 + orig: entry, 285 + nullified: entry.nullified, 286 + at: entry.createdAt, 287 + service_id: id, 288 + service_type: op.services[id].type, 289 + service_endpoint: op.services[id].endpoint, 290 + }); 291 + } else if (!dequal(op.services[id], oldServices[id])) { 292 + history.push({ 293 + type: "service_changed", 294 + orig: entry, 295 + nullified: entry.nullified, 296 + at: entry.createdAt, 297 + service_id: id, 298 + prev_service_type: oldServices[id].type, 299 + next_service_type: op.services[id].type, 300 + prev_service_endpoint: oldServices[id].endpoint, 301 + next_service_endpoint: op.services[id].endpoint, 302 + }); 303 + } 304 + } 305 + 306 + for (const id in oldServices) { 307 + if (!(id in op.services)) { 308 + history.push({ 309 + type: "service_removed", 310 + orig: entry, 311 + nullified: entry.nullified, 312 + at: entry.createdAt, 313 + service_id: id, 314 + service_type: oldServices[id].type, 315 + service_endpoint: oldServices[id].endpoint, 316 + }); 317 + } 318 + } 319 + } 320 + } else if (op.type === "plc_tombstone") { 321 + history.push({ 322 + type: "identity_tombstoned", 323 + orig: entry, 324 + nullified: entry.nullified, 325 + at: entry.createdAt, 326 + }); 327 + } 328 + } 329 + 330 + return history; 331 + }; 332 + 333 + function findLastMatching<T, S extends T>( 334 + arr: T[], 335 + predicate: (item: T) => item is S, 336 + start?: number, 337 + ): S | undefined; 338 + function findLastMatching<T>( 339 + arr: T[], 340 + predicate: (item: T) => boolean, 341 + start?: number, 342 + ): T | undefined; 343 + function findLastMatching<T>( 344 + arr: T[], 345 + predicate: (item: T) => boolean, 346 + start: number = arr.length - 1, 347 + ): T | undefined { 348 + for (let i = start, v: any; i >= 0; i--) { 349 + if (predicate((v = arr[i]))) { 350 + return v; 351 + } 352 + } 353 + 354 + return undefined; 355 + } 356 + 357 + function difference<T>(a: readonly T[], b: readonly T[]): T[] { 358 + const set = new Set(b); 359 + return a.filter((value) => !set.has(value)); 360 + } 361 + 362 + const dequal = (a: any, b: any): boolean => { 363 + let ctor: any; 364 + let len: number; 365 + 366 + if (a === b) { 367 + return true; 368 + } 369 + 370 + if (a && b && (ctor = a.constructor) === b.constructor) { 371 + if (ctor === Array) { 372 + if ((len = a.length) === b.length) { 373 + while (len--) { 374 + if (!dequal(a[len], b[len])) { 375 + return false; 376 + } 377 + } 378 + } 379 + 380 + return len === -1; 381 + } else if (!ctor || ctor === Object) { 382 + len = 0; 383 + 384 + for (ctor in a) { 385 + len++; 386 + 387 + if (!(ctor in b) || !dequal(a[ctor], b[ctor])) { 388 + return false; 389 + } 390 + } 391 + 392 + return Object.keys(b).length === len; 393 + } 394 + } 395 + 396 + return a !== a && b !== b; 397 + }; 398 + 399 + export const groupBy = <K, T>(items: T[], keyFn: (item: T, index: number) => K): Map<K, T[]> => { 400 + const map = new Map<K, T[]>(); 401 + 402 + for (let idx = 0, len = items.length; idx < len; idx++) { 403 + const val = items[idx]; 404 + const key = keyFn(val, idx); 405 + 406 + const list = map.get(key); 407 + 408 + if (list !== undefined) { 409 + list.push(val); 410 + } else { 411 + map.set(key, [val]); 412 + } 413 + } 414 + 415 + return map; 416 + };
+1 -1
src/views/blob.tsx
··· 53 53 <button 54 54 type="button" 55 55 onclick={() => refetch()} 56 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 56 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 57 57 > 58 58 Load More 59 59 </button>
+26 -21
src/views/collection.tsx
··· 4 4 import { resolvePDS } from "../utils/api.js"; 5 5 import * as TID from "@atcute/tid"; 6 6 import { JSONType, JSONValue } from "../components/json.jsx"; 7 - import { agent, loginState } from "../components/login.jsx"; 7 + import { agent } from "../components/login.jsx"; 8 8 import { createStore } from "solid-js/store"; 9 9 import Tooltip from "../components/tooltip.jsx"; 10 10 import { localDateFromTimestamp } from "../utils/date.js"; ··· 50 50 <Show when={hover()}> 51 51 <span 52 52 ref={previewRef} 53 - class={`dark:bg-dark-500/70 left-50% max-h-md z-25 backdrop-blur-xs border-0.5 dark:shadow-dark-900 pointer-events-none absolute block w-max max-w-sm -translate-x-1/2 overflow-hidden whitespace-pre-wrap rounded-md border-neutral-300 bg-zinc-100/70 p-2 text-xs shadow-md lg:max-w-lg dark:border-neutral-700 ${isOverflowing(previewHeight()) ? "bottom-7" : "top-7"}`} 53 + class={`dark:bg-dark-500/70 left-50% max-h-md z-25 backdrop-blur-xs border-0.5 dark:shadow-dark-900/80 pointer-events-none absolute block w-max max-w-sm -translate-x-1/2 overflow-hidden whitespace-pre-wrap rounded-md border-neutral-300 bg-zinc-100/70 p-2 text-xs shadow-md lg:max-w-lg dark:border-neutral-700 ${isOverflowing(previewHeight()) ? "bottom-7" : "top-7"}`} 54 54 > 55 55 <JSONValue 56 56 data={props.record.record.value as JSONType} ··· 119 119 }); 120 120 121 121 const BATCHSIZE = 200; 122 - rpc = new Client({ handler: agent }); 122 + rpc = new Client({ handler: agent()! }); 123 123 for (let i = 0; i < writes.length; i += BATCHSIZE) { 124 124 await rpc.post("com.atproto.repo.applyWrites", { 125 125 input: { 126 - repo: agent.sub, 126 + repo: agent()!.sub, 127 127 writes: writes.slice(i, i + BATCHSIZE), 128 128 }, 129 129 }); 130 130 } 131 - window.location.reload(); 131 + setBatchDelete(false); 132 + setRecords([]); 133 + setCursor(undefined); 134 + refetch(); 132 135 }; 133 136 134 137 const handleSelectionClick = (e: MouseEvent, index: number) => { ··· 159 162 <Show when={records.length || response()}> 160 163 <div class="z-5 dark:bg-dark-500/70 backdrop-blur-xs sticky top-0 flex w-screen flex-col items-center justify-center gap-2 bg-zinc-100/70 py-3"> 161 164 <div class="w-21rem sm:w-24rem flex items-center gap-2"> 162 - <Show when={loginState() && agent.sub === did}> 165 + <Show when={agent() && agent()?.sub === did}> 163 166 <div class="flex items-center gap-x-2"> 164 167 <Tooltip 165 168 text={batchDelete() ? "Cancel" : "Delete"} ··· 176 179 }} 177 180 > 178 181 <div 179 - class={`text-lg ${batchDelete() ? "i-lucide-circle-x hover:text-neutral-600 dark:hover:text-neutral-300" : "i-lucide-trash-2"} `} 182 + class={`text-lg ${batchDelete() ? "i-lucide-circle-x" : "i-lucide-trash-2"} `} 180 183 /> 181 184 </button> 182 185 } ··· 186 189 text="Select All" 187 190 children={ 188 191 <button onclick={() => selectAll()}> 189 - <div class="i-lucide-copy-check text-lg text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300" /> 192 + <div class="i-lucide-copy-check text-lg" /> 190 193 </button> 191 194 } 192 195 /> ··· 194 197 text="Confirm" 195 198 children={ 196 199 <button onclick={() => deleteRecords()}> 197 - <div class="i-lucide-trash-2 text-lg text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300" /> 200 + <div class="i-lucide-trash-2 text-lg text-red-500 dark:text-red-400" /> 198 201 </button> 199 202 } 200 203 /> ··· 208 211 /> 209 212 </div> 210 213 <div class="flex items-center gap-x-2"> 211 - <label class="flex select-none items-center gap-x-1"> 212 - <input 213 - type="checkbox" 214 - checked={reverse()} 215 - onchange={async (e) => { 216 - setReverse(e.currentTarget.checked); 214 + <Show when={records.length > 1}> 215 + <button 216 + type="button" 217 + onclick={() => { 218 + setReverse(!reverse()); 217 219 setRecords([]); 218 220 setCursor(undefined); 219 - await fetchRecords(); 221 + refetch(); 220 222 }} 221 - /> 222 - Reverse 223 - </label> 223 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 224 + > 225 + <div class={`${reverse() ? "i-lucide-rotate-ccw" : "i-lucide-rotate-cw"} text-sm`} /> 226 + Reverse 227 + </button> 228 + </Show> 224 229 <div> 225 230 <Show when={batchDelete()}> 226 231 <span>{records.filter((rec) => rec.toDelete).length}</span> ··· 231 236 </span> 232 237 </div> 233 238 <Show when={cursor()}> 234 - <div class="flex h-[2rem] w-[5.5rem] items-center justify-center text-nowrap"> 239 + <div class="flex w-[5rem] items-center justify-center"> 235 240 <Show when={!response.loading}> 236 241 <button 237 242 type="button" 238 243 onclick={() => refetch()} 239 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 244 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 240 245 > 241 246 Load More 242 247 </button>
+6 -1
src/views/home.tsx
··· 21 21 <A href="/firehose" class="text-blue-400 hover:underline"> 22 22 firehose 23 23 </A>{" "} 24 - support. 24 + streaming. 25 25 </p> 26 26 <p> 27 27 <A ··· 70 70 <Tooltip text="GitHub"> 71 71 <A href="https://github.com/notjuliet/pdsls" target="_blank"> 72 72 <div class="i-lucide-github text-xl" /> 73 + </A> 74 + </Tooltip> 75 + <Tooltip text="Tangled"> 76 + <A href="https://tangled.sh/@pdsls.dev/pdsls/" target="_blank"> 77 + <div class="i-lucide-line-squiggle text-xl" /> 73 78 </A> 74 79 </Tooltip> 75 80 <Tooltip text="Bluesky">
+3 -6
src/views/labels.tsx
··· 72 72 spellcheck={false} 73 73 rows={3} 74 74 value={searchParams.uriPatterns ?? "*"} 75 - class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900 mb-1 grow rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-blue-500" 75 + class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 mb-1 grow rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-slate-900 dark:focus:outline-slate-100" 76 76 /> 77 77 <div class="flex justify-center"> 78 78 <Show when={!response.loading}> ··· 106 106 <button 107 107 type="button" 108 108 onclick={() => refetch()} 109 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 109 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 110 110 > 111 111 Load More 112 112 </button> ··· 122 122 <div class="break-anywhere divide-y-0.5 flex flex-col gap-2 divide-neutral-400 whitespace-pre-wrap text-sm dark:divide-neutral-600"> 123 123 <For each={filterLabels()}> 124 124 {(label) => ( 125 - <div class="flex justify-between gap-2 pb-2"> 125 + <div class="flex items-center justify-between gap-2 pb-2"> 126 126 <div class="flex flex-col"> 127 127 <div class="flex items-center gap-x-2"> 128 128 <div class="min-w-[5rem] font-semibold text-stone-600 dark:text-stone-400"> ··· 169 169 </div> 170 170 <Show when={label.neg}> 171 171 <div class="i-lucide-minus shrink-0 text-xl text-red-500 dark:text-red-400" /> 172 - </Show> 173 - <Show when={!label.neg}> 174 - <div class="i-lucide-plus shrink-0 text-xl text-blue-500" /> 175 172 </Show> 176 173 </div> 177 174 )}
+4 -2
src/views/pds.tsx
··· 119 119 </> 120 120 )} 121 121 </Show> 122 - <p class="w-full font-semibold text-stone-600 dark:text-stone-400">Repositories</p> 122 + <p class="w-full font-semibold text-stone-600 dark:text-stone-400"> 123 + {repos()?.length} Repositories 124 + </p> 123 125 <For each={repos()}> 124 126 {(repo) => ( 125 127 <A ··· 151 153 <button 152 154 type="button" 153 155 onclick={() => refetch()} 154 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 mt-2 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 156 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 mt-2 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 155 157 > 156 158 Load More 157 159 </button>
+29 -27
src/views/record.tsx
··· 1 1 import { CredentialManager, Client } from "@atcute/client"; 2 2 3 - import { useParams } from "@solidjs/router"; 3 + import { useNavigate, useParams } from "@solidjs/router"; 4 4 import { createSignal, onMount, Show } from "solid-js"; 5 5 6 6 import { Backlinks } from "../components/backlinks.jsx"; 7 7 import { JSONValue } from "../components/json.jsx"; 8 - import { agent, loginState } from "../components/login.jsx"; 9 - import { setCID, setValidRecord, setValidSchema, validRecord } from "../components/navbar.jsx"; 8 + import { agent } from "../components/login.jsx"; 9 + import { pds, setCID, setValidRecord, setValidSchema, validRecord } from "../components/navbar.jsx"; 10 10 11 11 import { didDocCache, getAllBacklinks, LinkData, resolvePDS } from "../utils/api.js"; 12 12 import { AtUri, uriTemplates } from "../utils/templates.js"; ··· 21 21 import { Modal } from "../components/modal.jsx"; 22 22 23 23 export const RecordView = () => { 24 + const navigate = useNavigate(); 24 25 const params = useParams(); 25 26 const [record, setRecord] = 26 27 createSignal<InferXRPCBodyOutput<ComAtprotoRepoGetRecord.mainSchema["output"]>>(); ··· 40 41 setValidSchema(undefined); 41 42 const pds = await resolvePDS(did); 42 43 rpc = new Client({ handler: new CredentialManager({ service: pds }) }); 43 - const res = await getRecord(did, params.collection, params.rkey); 44 + const res = await rpc.get("com.atproto.repo.getRecord", { 45 + params: { 46 + repo: did as ActorIdentifier, 47 + collection: params.collection as `${string}.${string}.${string}`, 48 + rkey: params.rkey, 49 + }, 50 + }); 44 51 if (!res.ok) { 45 52 setValidRecord(false); 46 53 setNotice(res.data.error); ··· 91 98 } 92 99 }); 93 100 94 - const getRecord = (repo: string, collection: string, rkey: string) => 95 - rpc.get("com.atproto.repo.getRecord", { 96 - params: { 97 - repo: repo as ActorIdentifier, 98 - collection: collection as `${string}.${string}.${string}`, 99 - rkey: rkey, 100 - }, 101 - }); 102 - 103 101 const deleteRecord = async () => { 104 - rpc = new Client({ handler: agent }); 102 + rpc = new Client({ handler: agent()! }); 105 103 await rpc.post("com.atproto.repo.deleteRecord", { 106 104 input: { 107 105 repo: params.repo as ActorIdentifier, ··· 109 107 rkey: params.rkey, 110 108 }, 111 109 }); 112 - window.location.href = `/at://${params.repo}/${params.collection}`; 110 + navigate(`/at://${params.repo}/${params.collection}`); 113 111 }; 114 112 115 113 const checkUri = (uri: string) => { 116 114 const uriParts = uri.split("/"); // expected: ["at:", "", "repo", "collection", "rkey"] 117 115 if (uriParts.length != 5) return undefined; 118 116 if (uriParts[0] !== "at:" || uriParts[1] !== "") return undefined; 119 - const parsedUri: AtUri = { 120 - repo: uriParts[2], 121 - collection: uriParts[3], 122 - rkey: uriParts[4], 123 - }; 117 + const parsedUri: AtUri = { repo: uriParts[2], collection: uriParts[3], rkey: uriParts[4] }; 124 118 const template = uriTemplates[parsedUri.collection]; 125 119 if (!template) return undefined; 126 120 return template(parsedUri); ··· 135 129 <div class="mt-3 break-words text-red-500 dark:text-red-400">{notice()}</div> 136 130 </Show> 137 131 <Show when={record()}> 138 - <div class="my-3 flex gap-3"> 132 + <div class="dark:shadow-dark-900/80 dark:bg-dark-300 my-3 flex gap-3 rounded-full bg-white px-2.5 py-2 shadow-sm"> 139 133 <Tooltip text="Copy record"> 140 - <button onclick={() => addToClipboard(JSON.stringify(record()?.value))}> 134 + <button onclick={() => addToClipboard(JSON.stringify(record()?.value, null, 2))}> 141 135 <div class="i-lucide-copy text-xl" /> 142 136 </button> 143 137 </Tooltip> 144 - <Show when={loginState() && agent.sub === record()?.uri.split("/")[2]}> 138 + <Show when={agent() && agent()?.sub === record()?.uri.split("/")[2]}> 145 139 <RecordEditor create={false} record={record()?.value} /> 146 140 <div class="relative flex"> 147 141 <Tooltip text="Delete"> ··· 150 144 </button> 151 145 </Tooltip> 152 146 <Modal open={openDelete()} onClose={() => setOpenDelete(false)}> 153 - <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900 backdrop-blur-xs left-50% top-70 absolute -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 147 + <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900/80 backdrop-blur-xs left-50% top-70 absolute -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 154 148 <h2 class="mb-2 font-bold">Delete this record?</h2> 155 149 <div class="flex justify-end gap-2"> 156 150 <button 157 151 type="button" 158 152 onclick={() => setOpenDelete(false)} 159 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1.5 text-sm font-bold shadow-sm hover:bg-zinc-100 focus:outline-blue-500" 153 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1.5 text-sm font-bold shadow-sm hover:bg-zinc-100" 160 154 > 161 155 Cancel 162 156 </button> 163 157 <button 164 158 type="button" 165 159 onclick={deleteRecord} 166 - class="focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-red-500 px-2 py-1.5 text-sm font-bold text-slate-100 shadow-sm hover:bg-red-400 focus:outline-blue-500" 160 + class="dark:shadow-dark-900/80 rounded-lg bg-red-500 px-2 py-1.5 text-sm font-bold text-slate-100 shadow-sm hover:bg-red-400" 167 161 > 168 162 Delete 169 163 </button> ··· 176 170 {(externalLink) => ( 177 171 <Tooltip text={`Open on ${externalLink().label}`}> 178 172 <a target="_blank" href={externalLink()?.link}> 179 - <div class={`${externalLink().icon ?? "i-lucide-external-link"} text-xl`} /> 173 + <div class={`${externalLink().icon ?? "i-lucide-app-window"} text-xl`} /> 180 174 </a> 181 175 </Tooltip> 182 176 )} 183 177 </Show> 178 + <Tooltip text="Record on PDS"> 179 + <a 180 + href={`https://${pds()}/xrpc/com.atproto.repo.getRecord?repo=${params.repo}&collection=${params.collection}&rkey=${params.rkey}`} 181 + target="_blank" 182 + > 183 + <div class="i-lucide-external-link text-xl" /> 184 + </a> 185 + </Tooltip> 184 186 <Show when={backlinks()}> 185 187 <Tooltip text={showBacklinks() ? "Show record" : "Show backlinks"}> 186 188 <button onclick={() => setShowBacklinks(!showBacklinks())}>
+279 -101
src/views/repo.tsx
··· 16 16 import { BlobView } from "./blob.jsx"; 17 17 import { TextInput } from "../components/text-input.jsx"; 18 18 import Tooltip from "../components/tooltip.jsx"; 19 + import { 20 + CompatibleOperationOrTombstone, 21 + defs, 22 + IndexedEntry, 23 + processIndexedEntryLog, 24 + } from "@atcute/did-plc"; 25 + import { createOperationHistory, DiffEntry, groupBy } from "../utils/plc-logs.js"; 26 + import { localDateFromTimestamp } from "../utils/date.js"; 19 27 20 28 type Tab = "collections" | "backlinks" | "doc" | "blobs"; 29 + type PlcEvent = "handle" | "rotation_key" | "service" | "verification_method"; 30 + 31 + const PlcLogView = (props: { 32 + did: string; 33 + plcOps: [IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]; 34 + }) => { 35 + const [activePlcEvent, setActivePlcEvent] = createSignal<PlcEvent | undefined>(); 36 + 37 + const FilterButton = (props: { icon: string; event: PlcEvent }) => ( 38 + <button 39 + classList={{ 40 + "rounded-full p-1.5": true, 41 + "bg-neutral-700 dark:bg-neutral-200": activePlcEvent() === props.event, 42 + }} 43 + onclick={() => setActivePlcEvent(activePlcEvent() === props.event ? undefined : props.event)} 44 + > 45 + <div 46 + class={`${props.icon} text-xl ${activePlcEvent() === props.event ? "text-slate-100 dark:text-slate-900" : ""}`} 47 + /> 48 + </button> 49 + ); 50 + 51 + const DiffItem = (props: { diff: DiffEntry }) => { 52 + const diff = props.diff; 53 + let title = "Unknown log entry"; 54 + let icon = "i-lucide-circle-help"; 55 + let value = ""; 56 + 57 + if (diff.type === "identity_created") { 58 + icon = "i-lucide-bell"; 59 + title = `Identity created`; 60 + } else if (diff.type === "identity_tombstoned") { 61 + icon = "i-lucide-skull"; 62 + title = `Identity tombstoned`; 63 + } else if (diff.type === "handle_added" || diff.type === "handle_removed") { 64 + icon = "i-lucide-at-sign"; 65 + title = diff.type === "handle_added" ? "Alias added" : "Alias removed"; 66 + value = diff.handle; 67 + } else if (diff.type === "handle_changed") { 68 + icon = "i-lucide-at-sign"; 69 + title = "Alias updated"; 70 + value = `${diff.prev_handle} โ†’ ${diff.next_handle}`; 71 + } else if (diff.type === "rotation_key_added" || diff.type === "rotation_key_removed") { 72 + icon = "i-lucide-key-round"; 73 + title = diff.type === "rotation_key_added" ? "Rotation key added" : "Rotation key removed"; 74 + value = diff.rotation_key; 75 + } else if (diff.type === "service_added" || diff.type === "service_removed") { 76 + icon = "i-lucide-server"; 77 + title = `Service ${diff.service_id} ${diff.type === "service_added" ? "added" : "removed"}`; 78 + value = `${diff.service_endpoint}`; 79 + } else if (diff.type === "service_changed") { 80 + icon = "i-lucide-server"; 81 + title = `Service ${diff.service_id} updated`; 82 + value = `${diff.prev_service_endpoint} โ†’ ${diff.next_service_endpoint}`; 83 + } else if ( 84 + diff.type === "verification_method_added" || 85 + diff.type === "verification_method_removed" 86 + ) { 87 + icon = "i-lucide-shield-check"; 88 + title = `Verification method ${diff.method_id} ${diff.type === "verification_method_added" ? "added" : "removed"}`; 89 + value = `${diff.method_key}`; 90 + } else if (diff.type === "verification_method_changed") { 91 + icon = "i-lucide-shield-check"; 92 + title = `Verification method ${diff.method_id} updated`; 93 + value = `${diff.prev_method_key} โ†’ ${diff.next_method_key}`; 94 + } 95 + 96 + return ( 97 + <div class="grid grid-cols-[min-content_1fr] items-center gap-x-1"> 98 + <div class={icon + ` shrink-0 text-lg`} /> 99 + <p 100 + classList={{ 101 + "font-semibold": true, 102 + "text-gray-500 line-through dark:text-gray-400": diff.orig.nullified, 103 + }} 104 + > 105 + {title} 106 + </p> 107 + <div></div> 108 + {value} 109 + </div> 110 + ); 111 + }; 112 + 113 + return ( 114 + <> 115 + <div class="flex items-center justify-between"> 116 + <div class="flex items-center gap-1"> 117 + <Tooltip text="Filter operations"> 118 + <div class="i-lucide-filter text-xl" /> 119 + </Tooltip> 120 + <div class="dark:shadow-dark-900/80 dark:bg-dark-300 flex w-fit items-center rounded-full bg-white shadow-sm"> 121 + <FilterButton icon="i-lucide-at-sign" event="handle" /> 122 + <FilterButton icon="i-lucide-key-round" event="rotation_key" /> 123 + <FilterButton icon="i-lucide-server" event="service" /> 124 + <FilterButton icon="i-lucide-shield-check" event="verification_method" /> 125 + </div> 126 + </div> 127 + <Tooltip text="Audit log"> 128 + <a 129 + href={`${localStorage.plcDirectory ?? "https://plc.directory"}/${props.did}/log/audit`} 130 + target="_blank" 131 + > 132 + <div class="i-lucide-external-link text-lg" /> 133 + </a> 134 + </Tooltip> 135 + </div> 136 + <div class="flex flex-col gap-1 text-sm"> 137 + <For each={props.plcOps}> 138 + {([entry, diffs]) => ( 139 + <Show 140 + when={!activePlcEvent() || diffs.find((d) => d.type.startsWith(activePlcEvent()!))} 141 + > 142 + <div class="flex flex-col"> 143 + <span class="text-neutral-500 dark:text-neutral-400"> 144 + {localDateFromTimestamp(new Date(entry.createdAt).getTime())} 145 + </span> 146 + {diffs.map((diff) => ( 147 + <Show when={!activePlcEvent() || diff.type.startsWith(activePlcEvent()!)}> 148 + <DiffItem diff={diff} /> 149 + </Show> 150 + ))} 151 + </div> 152 + </Show> 153 + )} 154 + </For> 155 + </div> 156 + </> 157 + ); 158 + }; 21 159 22 160 const RepoView = () => { 23 161 const params = useParams(); 24 162 const [error, setError] = createSignal<string>(); 25 163 const [downloading, setDownloading] = createSignal(false); 26 164 const [didDoc, setDidDoc] = createSignal<DidDocument>(); 27 - const [backlinks, setBacklinks] = createSignal<{ 28 - links: LinkData; 29 - target: string; 30 - }>(); 165 + const [backlinks, setBacklinks] = createSignal<{ links: LinkData; target: string }>(); 31 166 const [nsids, setNsids] = createSignal<Record<string, { hidden: boolean; nsids: string[] }>>(); 32 167 const [tab, setTab] = createSignal<Tab>("collections"); 33 168 const [filter, setFilter] = createSignal<string>(); 169 + const [plcOps, setPlcOps] = 170 + createSignal<[IndexedEntry<CompatibleOperationOrTombstone>, DiffEntry[]][]>(); 171 + const [showPlcLogs, setShowPlcLogs] = createSignal(false); 172 + const [loading, setLoading] = createSignal(false); 173 + const [notice, setNotice] = createSignal<string>(); 34 174 let rpc: Client; 35 175 let pds: string; 36 176 const did = params.repo; ··· 38 178 const RepoTab = (props: { tab: Tab; label: string }) => ( 39 179 <button 40 180 classList={{ 41 - "rounded-lg flex flex-1 py-1 justify-center": true, 42 - "bg-zinc-200/70 dark:bg-dark-200 shadow-sm dark:shadow-dark-900": tab() === props.tab, 43 - "bg-transparent hover:bg-zinc-200/50 dark:hover:bg-dark-300": tab() !== props.tab, 181 + "rounded-full text-xs sm:text-sm flex flex-1 py-1.5 justify-center": true, 182 + "bg-white dark:bg-dark-300 shadow-sm dark:shadow-dark-900/80": tab() === props.tab, 183 + "bg-transparent hover:bg-zinc-200 dark:hover:bg-dark-200": tab() !== props.tab, 44 184 }} 45 185 onclick={() => setTab(props.tab)} 46 186 > 47 187 {props.label} 48 188 </button> 49 189 ); 50 - 51 - const describeRepo = (repo: string) => 52 - rpc.get("com.atproto.repo.describeRepo", { params: { repo: repo as ActorIdentifier } }); 53 190 54 191 const fetchRepo = async () => { 55 192 pds = await resolvePDS(did); 56 193 setDidDoc(didDocCache[did] as DidDocument); 57 194 58 195 rpc = new Client({ handler: new CredentialManager({ service: pds }) }); 59 - const res = await describeRepo(did); 196 + const res = await rpc.get("com.atproto.repo.describeRepo", { 197 + params: { repo: did as ActorIdentifier }, 198 + }); 60 199 if (res.ok) { 61 200 const collections: Record<string, { hidden: boolean; nsids: string[] }> = {}; 62 201 res.data.collections.forEach((c) => { ··· 81 220 break; 82 221 default: 83 222 setError("This repository is unreachable"); 84 - break; 85 223 } 86 224 setTab("doc"); 87 225 } ··· 177 315 onInput={(e) => setFilter(e.currentTarget.value)} 178 316 /> 179 317 <div class="flex flex-col font-mono"> 180 - <div class="grid grid-cols-[min-content_1fr] items-center gap-x-1 overflow-hidden text-sm"> 318 + <div class="grid grid-cols-[min-content_1fr] items-center gap-x-2 overflow-hidden text-sm"> 181 319 <For 182 320 each={Object.keys(nsids() ?? {}).filter((authority) => 183 321 filter() ? ··· 187 325 > 188 326 {(authority) => ( 189 327 <> 190 - <Show when={nsids()?.[authority].hidden}> 191 - <button onclick={() => toggleCollection(authority)}> 192 - <div class="i-lucide-chevron-right mr-1 text-lg" /> 193 - </button> 194 - </Show> 195 - <Show when={!nsids()?.[authority].hidden}> 196 - <button onclick={() => toggleCollection(authority)}> 197 - <div class="i-lucide-chevron-down mr-1 text-lg" /> 198 - </button> 199 - </Show> 328 + <button onclick={() => toggleCollection(authority)}> 329 + <div 330 + classList={{ 331 + "i-lucide-chevron-down text-lg transition-transform": true, 332 + "-rotate-90": nsids()?.[authority].hidden, 333 + }} 334 + /> 335 + </button> 200 336 <button 201 337 class="break-anywhere bg-transparent text-left" 202 338 onclick={() => toggleCollection(authority)} ··· 233 369 <Show when={tab() === "doc"}> 234 370 <Show when={didDoc()}> 235 371 {(didDocument) => ( 236 - <div class="break-anywhere flex flex-col gap-y-1"> 237 - <div class="flex items-center justify-between gap-2"> 372 + <div class="break-anywhere flex flex-col gap-y-2"> 373 + <div class="flex flex-col gap-y-1"> 374 + <div class="flex items-center justify-between gap-2"> 375 + <div> 376 + <span class="font-semibold text-stone-600 dark:text-stone-400">ID </span> 377 + <span>{didDocument().id}</span> 378 + </div> 379 + <Tooltip text="DID Document"> 380 + <a 381 + href={ 382 + did.startsWith("did:plc") ? 383 + `${localStorage.plcDirectory ?? "https://plc.directory"}/${did}` 384 + : `https://${did.split("did:web:")[1]}/.well-known/did.json` 385 + } 386 + target="_blank" 387 + > 388 + <div class="i-lucide-external-link text-lg" /> 389 + </a> 390 + </Tooltip> 391 + </div> 238 392 <div> 239 - <span class="font-semibold text-stone-600 dark:text-stone-400">ID </span> 240 - <span>{didDocument().id}</span> 393 + <p class="font-semibold text-stone-600 dark:text-stone-400">Identities</p> 394 + <ul class="ml-2"> 395 + <For each={didDocument().alsoKnownAs}>{(alias) => <li>{alias}</li>}</For> 396 + </ul> 241 397 </div> 242 - <Tooltip text="DID Document"> 243 - <a 244 - href={ 245 - did.startsWith("did:plc") ? 246 - `${localStorage.plcDirectory ?? "https://plc.directory"}/${did}` 247 - : `https://${did.split("did:web:")[1]}/.well-known/did.json` 248 - } 249 - target="_blank" 250 - > 251 - <div class="i-lucide-external-link text-lg" /> 252 - </a> 253 - </Tooltip> 254 - </div> 255 - <div> 256 - <p class="font-semibold text-stone-600 dark:text-stone-400">Identities</p> 257 - <ul class="ml-2"> 258 - <For each={didDocument().alsoKnownAs}>{(alias) => <li>{alias}</li>}</For> 259 - </ul> 260 - </div> 261 - <div> 262 - <p class="font-semibold text-stone-600 dark:text-stone-400">Services</p> 263 - <ul class="ml-2"> 264 - <For each={didDocument().service}> 265 - {(service) => ( 266 - <li class="flex flex-col"> 267 - <span>#{service.id.split("#")[1]}</span> 268 - <a 269 - class="w-fit text-blue-400 hover:underline" 270 - href={service.serviceEndpoint.toString()} 271 - target="_blank" 272 - > 273 - {service.serviceEndpoint.toString()} 274 - </a> 275 - </li> 276 - )} 277 - </For> 278 - </ul> 279 - </div> 280 - <div> 281 - <p class="font-semibold text-stone-600 dark:text-stone-400"> 282 - Verification methods 283 - </p> 284 - <ul class="ml-2"> 285 - <For each={didDocument().verificationMethod}> 286 - {(verif) => ( 287 - <li class="flex flex-col"> 288 - <span>#{verif.id.split("#")[1]}</span> 289 - <span>{verif.publicKeyMultibase}</span> 290 - </li> 291 - )} 292 - </For> 293 - </ul> 398 + <div> 399 + <p class="font-semibold text-stone-600 dark:text-stone-400">Services</p> 400 + <ul class="ml-2"> 401 + <For each={didDocument().service}> 402 + {(service) => ( 403 + <li class="flex flex-col"> 404 + <span>#{service.id.split("#")[1]}</span> 405 + <a 406 + class="w-fit text-blue-400 hover:underline" 407 + href={service.serviceEndpoint.toString()} 408 + target="_blank" 409 + > 410 + {service.serviceEndpoint.toString()} 411 + </a> 412 + </li> 413 + )} 414 + </For> 415 + </ul> 416 + </div> 417 + <div> 418 + <p class="font-semibold text-stone-600 dark:text-stone-400"> 419 + Verification methods 420 + </p> 421 + <ul class="ml-2"> 422 + <For each={didDocument().verificationMethod}> 423 + {(verif) => ( 424 + <li class="flex flex-col"> 425 + <span>#{verif.id.split("#")[1]}</span> 426 + <span>{verif.publicKeyMultibase}</span> 427 + </li> 428 + )} 429 + </For> 430 + </ul> 431 + </div> 294 432 </div> 295 - <Show when={did.startsWith("did:plc")}> 296 - <a 297 - class="flex w-fit items-center text-blue-400 hover:underline" 298 - href={`https://boat.kelinci.net/plc-oplogs?q=${did}`} 299 - target="_blank" 300 - > 301 - PLC operation logs <div class="i-lucide-external-link ml-0.5 text-sm" /> 302 - </a> 303 - </Show> 304 - <Show when={error()?.length === 0 || error() === undefined}> 305 - <div class="flex items-center gap-1"> 306 - <button 307 - type="button" 308 - onclick={() => downloadRepo()} 309 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 433 + <div class="flex justify-between"> 434 + <Show when={did.startsWith("did:plc")}> 435 + <div class="flex items-center gap-1"> 436 + <button 437 + type="button" 438 + onclick={async () => { 439 + if (!plcOps()) { 440 + setLoading(true); 441 + const response = await fetch( 442 + `${localStorage.plcDirectory ?? "https://plc.directory"}/${did}/log/audit`, 443 + ); 444 + const json = await response.json(); 445 + try { 446 + const logs = defs.indexedEntryLog.parse(json); 447 + await processIndexedEntryLog(did as any, logs); 448 + const opHistory = createOperationHistory(logs).reverse(); 449 + setPlcOps(Array.from(groupBy(opHistory, (item) => item.orig))); 450 + setLoading(false); 451 + } catch (e: any) { 452 + setNotice(e); 453 + console.error(e); 454 + setLoading(false); 455 + } 456 + } 457 + 458 + setShowPlcLogs(!showPlcLogs()); 459 + }} 460 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 461 + > 462 + <div class="i-lucide-logs text-sm" /> 463 + {showPlcLogs() ? "Hide" : "Show"} PLC Logs 464 + </button> 465 + <Show when={loading()}> 466 + <div class="i-lucide-loader-circle animate-spin text-xl" /> 467 + </Show> 468 + </div> 469 + </Show> 470 + <Show when={error()?.length === 0 || error() === undefined}> 471 + <div 472 + classList={{ 473 + "flex items-center gap-1": true, 474 + "flex-row-reverse": did.startsWith("did:web"), 475 + }} 310 476 > 311 - <div class="i-lucide-download text-sm" /> 312 - Export Repo 313 - </button> 314 - <Show when={downloading()}> 315 - <div class="i-lucide-loader-circle animate-spin text-xl" /> 316 - </Show> 317 - </div> 477 + <Show when={downloading()}> 478 + <div class="i-lucide-loader-circle animate-spin text-xl" /> 479 + </Show> 480 + <button 481 + type="button" 482 + onclick={() => downloadRepo()} 483 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 484 + > 485 + <div class="i-lucide-download text-sm" /> 486 + Export Repo 487 + </button> 488 + </div> 489 + </Show> 490 + </div> 491 + <Show when={showPlcLogs()}> 492 + <Show when={notice()}> 493 + <div>{notice()}</div> 494 + </Show> 495 + <PlcLogView plcOps={plcOps() ?? []} did={did} /> 318 496 </Show> 319 497 </div> 320 498 )}
+4 -4
src/views/stream.tsx
··· 155 155 Firehose 156 156 </A> 157 157 </div> 158 - <form ref={formRef} class="flex flex-col gap-y-2"> 158 + <form ref={formRef} class="flex flex-col gap-y-2 text-sm"> 159 159 <Show when={!connected()}> 160 160 <label class="flex items-center justify-end gap-x-2"> 161 161 <span>Instance</span> ··· 178 178 spellcheck={false} 179 179 placeholder="Comma-separated list of collections" 180 180 value={searchParams.collections ?? ""} 181 - class="w-16rem dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-blue-500" 181 + class="w-16rem dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-slate-900 dark:focus:outline-slate-100" 182 182 /> 183 183 </label> 184 184 </Show> ··· 190 190 spellcheck={false} 191 191 placeholder="Comma-separated list of DIDs" 192 192 value={searchParams.dids ?? ""} 193 - class="w-16rem dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-blue-500" 193 + class="w-16rem dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-slate-900 dark:focus:outline-slate-100" 194 194 /> 195 195 </label> 196 196 </Show> ··· 238 238 <button 239 239 type="button" 240 240 onclick={() => connectSocket(new FormData(formRef))} 241 - class="dark:hover:bg-dark-100 dark:bg-dark-300 focus:outline-1.5 dark:shadow-dark-900 w-fit rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50 focus:outline-blue-500" 241 + class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 w-fit rounded-lg bg-white px-2 py-1.5 text-xs font-bold shadow-sm hover:bg-zinc-200/50" 242 242 > 243 243 {connected() ? "Disconnect" : "Connect"} 244 244 </button>