+11
README.md
+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
-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" ||
+5
-5
package.json
+5
-5
package.json
···
9
9
"serve": "vite preview"
10
10
},
11
11
"devDependencies": {
12
-
"@iconify-json/lucide": "^1.2.60",
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
16
"typescript": "^5.9.2",
17
-
"unocss": "66.4.0",
18
-
"vite": "^7.0.6",
17
+
"unocss": "66.4.2",
18
+
"vite": "^7.1.1",
19
19
"vite-plugin-solid": "^2.11.8"
20
20
},
21
21
"dependencies": {
···
38
38
"@skyware/firehose": "^0.5.2",
39
39
"@solidjs/meta": "^0.29.4",
40
40
"@solidjs/router": "^0.15.3",
41
-
"hls.js": "^1.6.7",
41
+
"hls.js": "^1.6.9",
42
42
"monaco-editor": "^0.52.2",
43
-
"solid-js": "^1.9.7"
43
+
"solid-js": "^1.9.8"
44
44
},
45
45
"packageManager": "pnpm@10.12.2+sha512.a32540185b964ee30bb4e979e405adc6af59226b438ee4cc19f9e8773667a66d302f5bfee60a39d3cac69e35e4b96e708a71dd002b7e9359c4112a1722ac323f",
46
46
"pnpm": {
+224
-219
pnpm-lock.yaml
+224
-219
pnpm-lock.yaml
···
61
61
version: 0.5.2
62
62
'@solidjs/meta':
63
63
specifier: ^0.29.4
64
-
version: 0.29.4(solid-js@1.9.7)
64
+
version: 0.29.4(solid-js@1.9.8)
65
65
'@solidjs/router':
66
66
specifier: ^0.15.3
67
-
version: 0.15.3(solid-js@1.9.7)
67
+
version: 0.15.3(solid-js@1.9.8)
68
68
hls.js:
69
-
specifier: ^1.6.7
70
-
version: 1.6.7
69
+
specifier: ^1.6.9
70
+
version: 1.6.9
71
71
monaco-editor:
72
72
specifier: ^0.52.2
73
73
version: 0.52.2
74
74
solid-js:
75
-
specifier: ^1.9.7
76
-
version: 1.9.7
75
+
specifier: ^1.9.8
76
+
version: 1.9.8
77
77
devDependencies:
78
78
'@iconify-json/lucide':
79
-
specifier: ^1.2.60
80
-
version: 1.2.60
79
+
specifier: ^1.2.62
80
+
version: 1.2.62
81
81
'@iconify-json/lucide-lab':
82
82
specifier: ^1.2.3
83
83
version: 1.2.3
···
91
91
specifier: ^5.9.2
92
92
version: 5.9.2
93
93
unocss:
94
-
specifier: 66.4.0
95
-
version: 66.4.0(postcss@8.5.6)(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))
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))
96
96
vite:
97
-
specifier: ^7.0.6
98
-
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)
99
99
vite-plugin-solid:
100
100
specifier: ^2.11.8
101
-
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))
102
102
103
103
packages:
104
104
···
109
109
'@antfu/install-pkg@1.1.0':
110
110
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
111
111
112
-
'@antfu/utils@8.1.1':
113
-
resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==}
112
+
'@antfu/utils@9.2.0':
113
+
resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==}
114
114
115
115
'@atcute/atproto@3.1.1':
116
116
resolution: {integrity: sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg==}
···
556
556
'@iconify-json/lucide-lab@1.2.3':
557
557
resolution: {integrity: sha512-N+8vnVt4IY/6FZi81f6nh5VhJSMYrs5KTVsT2Z/E0Wn7Lu4jJKO5fOfiTVX1YWVI4FFwQ1zVXPFb8kLAwskrjA==}
558
558
559
-
'@iconify-json/lucide@1.2.60':
560
-
resolution: {integrity: sha512-iVhpjcJkrR65jaJCJAFr02FkL73Qth039MgDJOcuKYOl32183qgAeHmo44DI2SzkYURGpc0GMwnhRPpQPOaChg==}
559
+
'@iconify-json/lucide@1.2.62':
560
+
resolution: {integrity: sha512-K0KfhvP5YQZ2KraOgCm6jJbwwzQCVocvXcdMpDou5uLa48QnLBRW/dQ8VDGmxHTGpwF9EqLlvnUSinH2i6xs3Q==}
561
561
562
562
'@iconify/types@2.0.0':
563
563
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
564
564
565
-
'@iconify/utils@2.3.0':
566
-
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
565
+
'@iconify/utils@3.0.0':
566
+
resolution: {integrity: sha512-Bjf0HTRAB59thKK9QFvyLEXE9S793IqxqJEhNQEboh+IjOXj0nDtOIFh63oz+Y6X/ye4UWpxne5sVQ2W250iSA==}
567
567
568
568
'@jridgewell/gen-mapping@0.3.12':
569
569
resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==}
···
587
587
'@polka/url@1.0.0-next.29':
588
588
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
589
589
590
-
'@quansync/fs@0.1.3':
591
-
resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==}
592
-
engines: {node: '>=20.0.0'}
590
+
'@quansync/fs@0.1.4':
591
+
resolution: {integrity: sha512-vy/41FCdnIalPTQCb2Wl0ic1caMdzGus4ktDp+gpZesQNydXcx8nhh8qB3qMPbGkictOTaXgXEUUfQEm8DQYoA==}
593
592
594
593
'@rollup/rollup-android-arm-eabi@4.46.2':
595
594
resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==}
···
722
721
'@types/node@22.13.1':
723
722
resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==}
724
723
725
-
'@unocss/astro@66.4.0':
726
-
resolution: {integrity: sha512-DDc22MhzS5SD7LXiJetNl/WglkBkQEKDDzaay4rUpvINdRu3eME1ISdgUBel4jkchSSenTt2AZlD9l6CecFXEw==}
724
+
'@unocss/astro@66.4.2':
725
+
resolution: {integrity: sha512-En3AKHwkiPxtZT95vkVrNiRYrB+DFVCikew6/dMMCWDWVKK0+5tEVUTzR1ak3+YnzAXl0NpWj8D4zHb0PxOs/A==}
727
726
peerDependencies:
728
727
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
729
728
peerDependenciesMeta:
730
729
vite:
731
730
optional: true
732
731
733
-
'@unocss/cli@66.4.0':
734
-
resolution: {integrity: sha512-zSdFHrYwgDuHTklFXWnWqp5dJq+aDOFxCZHK0M3hnZtEiaSgMce8Fdje9hOOi/FtCuKr1/BHLyjD1Vj240PVOw==}
732
+
'@unocss/cli@66.4.2':
733
+
resolution: {integrity: sha512-WsXzrB0SHbSt2nOHtD5QM91VN8j38+wObqyGcoIhtBSugqzsc+t7AdPkxV/ZaYgtPAz87bR0WFEVKcbiBRnmJw==}
735
734
engines: {node: '>=14'}
736
735
hasBin: true
737
736
738
-
'@unocss/config@66.4.0':
739
-
resolution: {integrity: sha512-0H0dd5sWuFg9Z7oN+nGaL9UV4KitNuEcFcVVMUxPW3l+j3BKGMy6B+2jNS2+ezmpJoh5jaaL/fm5loYvOvaATA==}
737
+
'@unocss/config@66.4.2':
738
+
resolution: {integrity: sha512-plji1gNGSzlWjuV2Uh0q6Dt5ZlNkOKCHpgxekW9J458WghGAMBeXgB9uNpWg6flilqP1g0GJQv+XvJcSkYRGpQ==}
740
739
engines: {node: '>=14'}
741
740
742
-
'@unocss/core@66.4.0':
743
-
resolution: {integrity: sha512-vrfK8i3EwbKDbrhmR5lJQQltU1U0SvPqr2XVTHqZdCdzTUsg73I4NqFSiadt486i421C8BfTa2MPNHBnv35RuA==}
741
+
'@unocss/core@66.4.2':
742
+
resolution: {integrity: sha512-cYgMQrLhB9nRekv5c+yPDDa+5dzlMkA2UMQRil0s5D9Lb5n7NsCMcr6+nfxkcSYVLy92SbwDV45c6T7vIxFTOA==}
744
743
745
-
'@unocss/extractor-arbitrary-variants@66.4.0':
746
-
resolution: {integrity: sha512-P4bAb/oQ14TP7KZE4jxj4jcgCROkj8Ndnm3WKAmX+gwZLeAATjF0dn40EqLzmhLkXQYttp1DIEyvV77hsDZZOw==}
744
+
'@unocss/extractor-arbitrary-variants@66.4.2':
745
+
resolution: {integrity: sha512-T/eSeodfAp7HaWnQGqVLOsW4PbKUAvuybNRyvFWThMneM2qo+dOo3kFnA5my9ULAmRSFsAlyB1DnupD3qv5Klg==}
747
746
748
-
'@unocss/inspector@66.4.0':
749
-
resolution: {integrity: sha512-wYWvvoiycl06SSLMKD1PAshSRzXnAd1Zk3F3CfviJUVKrp5ugLSbzZe+mnYKpNWTrNwfCNG69YhdsJnSdkb35Q==}
747
+
'@unocss/inspector@66.4.2':
748
+
resolution: {integrity: sha512-ugcJK8r2ypM4eIdgetVn8RhfKrbA3AF3OQ/RohK5PPk2UPDAScqabzYpfdNW4eYQsBOZOgoiqWtnfc8weqo8LQ==}
750
749
751
-
'@unocss/postcss@66.4.0':
752
-
resolution: {integrity: sha512-MX6hFo54+tiysvstHKhNP1nQabqKzXDzdX/6Ctqhj++cL/yRfz6vqcv8MSbfBQDciiTin0ikDytBYik0pRgENQ==}
750
+
'@unocss/postcss@66.4.2':
751
+
resolution: {integrity: sha512-tu4lnh6K27pIAuaQHlFlhXin8korwC0r1kQl00YMmF3THiX7orXkTP6xWGcQwnkbx4uQz1dw+tBimYxeaAMrhA==}
753
752
engines: {node: '>=14'}
754
753
peerDependencies:
755
754
postcss: ^8.4.21
756
755
757
-
'@unocss/preset-attributify@66.4.0':
758
-
resolution: {integrity: sha512-iH/ZwbAJmbIMIBfeahzNcQ7OmHHzqvyHyC8rGIkInE0xdFsHcfqjsb6hasedy5VTX3EecWZ3RE7FpNjuV3PLAA==}
756
+
'@unocss/preset-attributify@66.4.2':
757
+
resolution: {integrity: sha512-DwFJJkkawmHpjo3pGQE8FyoPsvhbxh+QMvvaAdYpo+iZ5HRkeDml9SOj7u6SGTcmbNyI+QR61s0KM8fxx6HcVQ==}
759
758
760
-
'@unocss/preset-icons@66.4.0':
761
-
resolution: {integrity: sha512-Fm4/wgNfnVrJgmFrWs9JUjJy+il57hM+4qilSo7zR0QaeyES1z+VnIavGAPI02neBSztIHR8Rh6+6/bhVmByzg==}
759
+
'@unocss/preset-icons@66.4.2':
760
+
resolution: {integrity: sha512-qJx9gmesrvrmoTe9Mqoidihad8hm2MSD4QAezhfDSAyllioJOgyT0Bev/IEWAbehe9jtqYIh8v1oCerBPbGn6Q==}
762
761
763
-
'@unocss/preset-mini@66.4.0':
764
-
resolution: {integrity: sha512-gOdTB9qo5PIusB8WTyCnkwc/GQT7ifAYzn4a+wuk51Ml3i+JxxN90l25dRlgw6hsyx2LgX/CHMzoKXYzuqsnPg==}
762
+
'@unocss/preset-mini@66.4.2':
763
+
resolution: {integrity: sha512-Ry+5hM+XLmT8HrEb182mUfcZuyrZ8xR+TBe72DBcliJ1DhOV3K67TCxwQucfb0zHbGV71HNWdPmHsLKxPDgweQ==}
765
764
766
-
'@unocss/preset-tagify@66.4.0':
767
-
resolution: {integrity: sha512-DeIwGoW39iGI4BHz53PWJk2HTOqzJKWQnGBwYb0qw3+PknGRFg18ERRwm4KBGQjyAjt46sIrGm9Zxu5Y9wYh+w==}
765
+
'@unocss/preset-tagify@66.4.2':
766
+
resolution: {integrity: sha512-dECS09LqWJY4sYpgPUH2OAUftWU/tiZPR2XDRoTngeGU37GxSN+1sWtSmB7vwDm3C7opsdVUN20he8F1LUNubw==}
768
767
769
-
'@unocss/preset-typography@66.4.0':
770
-
resolution: {integrity: sha512-iWPsCzmUBzwHQRq7cHbtkWAy6V1S4QyzitT6cLf4241njeHnjMJHWwrpyfYNCrdeESjgO9HuoGiyevvqcQ9mRw==}
768
+
'@unocss/preset-typography@66.4.2':
769
+
resolution: {integrity: sha512-ZOKRuR5+V0r30QTVq04/6ZoIw75me3V25v2dU2YWJXIzwpMKmQ9TUN/M1yeiEUFfXjOaruWX6Ad6CvAw2MlCew==}
771
770
772
-
'@unocss/preset-uno@66.4.0':
773
-
resolution: {integrity: sha512-1Ep9gkxsW6hfEeZUjJTNofNbZ2/SgFohKb41U9DwBoXCOhGYTE2nmjr6EgoooF6XQNicPNa0tO6xVM/8n9z/NQ==}
771
+
'@unocss/preset-uno@66.4.2':
772
+
resolution: {integrity: sha512-1MFtPivGcpqRQFWdjtP40Enop1y3XDb3tlZXoMQUX0IGLG8HJOT+lfQx/Xl9t73ShJ8aAJ/l6qTxC43ZGNACzA==}
774
773
775
-
'@unocss/preset-web-fonts@66.4.0':
776
-
resolution: {integrity: sha512-pq9lOuR0VoshLaWlZNqM8A3V9DtsGZEmnX6qAzXCBF7LKO72gFKBn+K2IB6TxET0fMV0pagwhezzU5Jnu9nbMw==}
774
+
'@unocss/preset-web-fonts@66.4.2':
775
+
resolution: {integrity: sha512-4FYmleeRoM8r2DqGl6dfIjnX57tepcfZCvVfeCqYnk7475Yddmv1OYkoMjkWMnkK9MzdSxsFwHMU6CIUTmFTzQ==}
777
776
778
-
'@unocss/preset-wind3@66.4.0':
779
-
resolution: {integrity: sha512-9Qo8W3TBcSDtQDV/J1sJrsTa4AHss+wxzZj1ngyHUpgZTE45KEaHH0zEjxM04oC5hrOU9FqRZgwV8Q03UR4v8w==}
777
+
'@unocss/preset-wind3@66.4.2':
778
+
resolution: {integrity: sha512-0Aye/PaT08M/cQhPnGKn93iEVoRJbym0/1eomMvXoL+8oc7DVry35ws06r5CLu5h1sXI6UmS6sejoePFlSkLJQ==}
780
779
781
-
'@unocss/preset-wind4@66.4.0':
782
-
resolution: {integrity: sha512-Ut0B8JRt+aDjHJxZpwm4RtiBBEHE//XBhFFWMz2iljPZLPgN/uhbwr/M53yvpoA07Bz4IhtkaSsgOTLCSEsN0w==}
780
+
'@unocss/preset-wind4@66.4.2':
781
+
resolution: {integrity: sha512-F4RZsDqIpnSevD9hY353+Tw5gxpJuHA5HwdKjLnC/TnT9VKKVmV7qUEZ6M0jEuAk1kz2x3/ngnQ9Ftw+C2L84A==}
783
782
784
-
'@unocss/preset-wind@66.4.0':
785
-
resolution: {integrity: sha512-M1RrLvr827F6jNZsWjvM8FqhJgLR+bJKouhfPhixQFk00dqmS0NiFMKhMEt4kMtByh0fR+CBsEmB0um/vw+T3A==}
783
+
'@unocss/preset-wind@66.4.2':
784
+
resolution: {integrity: sha512-z/rFYFINNqmBtl3Dh+7UCKpPnPkxM7IIUGszMnvdntky9uhLauJ11dt/Puir73sM2cAfywfgvnHyZ00m0pg7rA==}
786
785
787
-
'@unocss/reset@66.4.0':
788
-
resolution: {integrity: sha512-zbH648K61/Umjy2tCj481ETMuaOlKjyzlXCvVO+U5dF1LhoWM2B7/mdBAiz/cmsKTeE2SfpUmusTRQr6X3n0/Q==}
786
+
'@unocss/reset@66.4.2':
787
+
resolution: {integrity: sha512-s3Kq4Q6a/d3/jYe6HTCfXUx7zYAYufetId5n66DZHzQxpeu6CoBS83+b37STTKsw27SOgV28cPJlJtZ6/D6Bhw==}
789
788
790
-
'@unocss/rule-utils@66.4.0':
791
-
resolution: {integrity: sha512-cWqs6Vre54iwbeYmJIjx1I912M3zNXYQ+lvytkn3NMysNsJlYYhyM4T0L6Jt3dz74X7I4vTcN0sQvVeE2TS3Fg==}
789
+
'@unocss/rule-utils@66.4.2':
790
+
resolution: {integrity: sha512-7z3IuajwXhy2cx3E0IGOFXIiuKC79/jzm4Tt56TC68nXLh/etlH0fKhxVwkZ/HbcQRpVwWyDRNcbh29pmA3DwQ==}
792
791
engines: {node: '>=14'}
793
792
794
-
'@unocss/transformer-attributify-jsx@66.4.0':
795
-
resolution: {integrity: sha512-jDCzDAqGft3WR0cYGJWdghRJnSnu0dqnMNyii0avp/v2qH2J+X6Lmbn6y11sdW9krkPTtXnuF29nd/XWbK7leg==}
793
+
'@unocss/transformer-attributify-jsx@66.4.2':
794
+
resolution: {integrity: sha512-de6LzoyW1tkdOftlCrj6z8wEb4j6l1sqmOU1nYKkYHw7luLFGxRUELC7iujlI9KmylbM02bcKfLETAfJy/je2w==}
796
795
797
-
'@unocss/transformer-compile-class@66.4.0':
798
-
resolution: {integrity: sha512-QETg2SAzmU15e5QmM9lPoWE6Yq8O/pcjLkSrL4HhkARnrEFCiRO3nohXXA/bdnu1bRLxgYp43Q1JwVGPooeb4Q==}
796
+
'@unocss/transformer-compile-class@66.4.2':
797
+
resolution: {integrity: sha512-+oiIrV8c3T7qiJdICr6YsEWik5sjbWirXF0mlpcBvZu2HyV559hvHjzuWKr/fl7xYYZKDL9FvddbqWo3DOXh3Q==}
799
798
800
-
'@unocss/transformer-directives@66.4.0':
801
-
resolution: {integrity: sha512-QOKQNEEuG/WRdD5thYgMWh/RFQtBpk0T1g5bobWzxi4Z0HxIpUKhu7bgmN9pUzeiN5rW8O42aNHMzIR9thP/1g==}
799
+
'@unocss/transformer-directives@66.4.2':
800
+
resolution: {integrity: sha512-7m/dTrCUkBkZeSRKPxPEo65Rav239orQSLq6sztwZhoA4x/6H8r58xCkAK0qC9VEalyerpCpyarU3sKN4+ehNg==}
802
801
803
-
'@unocss/transformer-variant-group@66.4.0':
804
-
resolution: {integrity: sha512-6GEtDyVuac06MVeVmAlZHQ4KvWivplHasYWcRll1517XnnCcTJq7qScHv8OoiL6MOYLyTt0hWlecWubESP3MPg==}
802
+
'@unocss/transformer-variant-group@66.4.2':
803
+
resolution: {integrity: sha512-SbPDbZUrhQyL4CpvnpvUfrr1DFq8AKf8ofPGbMJDm5S2TInQ34vFaIrhNroGR0szntMZRH5Zlkq6LtVUKDRs5g==}
805
804
806
-
'@unocss/vite@66.4.0':
807
-
resolution: {integrity: sha512-TCfHwjU6L5ddtTsRe2RmYy6y9zTsu7SD+lFiD5fidUh3FJ80M9wcE3+xNAdjYEdbow4bkF8IzZPbImr2C9imFw==}
805
+
'@unocss/vite@66.4.2':
806
+
resolution: {integrity: sha512-7eON9iPF3qWzuI+M6u0kq7K3y9nEbimZlLj01nGoqrgSGxEsyJpP01QQQsmT7FPRiZzRMJv7BiKMEyDQSuRRCA==}
808
807
peerDependencies:
809
808
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
810
809
···
817
816
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
818
817
engines: {node: '>= 8'}
819
818
820
-
babel-plugin-jsx-dom-expressions@0.39.8:
821
-
resolution: {integrity: sha512-/MVOIIjonylDXnrWmG23ZX82m9mtKATsVHB7zYlPfDR9Vdd/NBE48if+wv27bSkBtyO7EPMUlcUc4J63QwuACQ==}
819
+
babel-plugin-jsx-dom-expressions@0.40.1:
820
+
resolution: {integrity: sha512-b4iHuirqK7RgaMzB2Lsl7MqrlDgQtVRSSazyrmx7wB3T759ggGjod5Rkok5MfHjQXhR7tRPmdwoeGPqBnW2KfA==}
822
821
peerDependencies:
823
822
'@babel/core': ^7.20.12
824
823
825
-
babel-preset-solid@1.9.6:
826
-
resolution: {integrity: sha512-HXTK9f93QxoH8dYn1M2mJdOlWgMsR88Lg/ul6QCZGkNTktjTE5HAf93YxQumHoCudLEtZrU1cFCMFOVho6GqFg==}
824
+
babel-preset-solid@1.9.8:
825
+
resolution: {integrity: sha512-Tz2ZoKCPITeV+cANGeIA6pxHBLeEtX7hwk04tEh3xSWVqHMf2FqFwVz0RBxCLlBehpKfY1scDiuijBkmyVpqrQ==}
827
826
peerDependencies:
828
827
'@babel/core': ^7.0.0
828
+
solid-js: ^1.9.8
829
+
peerDependenciesMeta:
830
+
solid-js:
831
+
optional: true
829
832
830
833
binary-extensions@2.3.0:
831
834
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
···
835
838
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
836
839
engines: {node: '>=8'}
837
840
838
-
browserslist@4.25.1:
839
-
resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==}
841
+
browserslist@4.25.2:
842
+
resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==}
840
843
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
841
844
hasBin: true
842
845
···
844
847
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
845
848
engines: {node: '>=8'}
846
849
847
-
caniuse-lite@1.0.30001731:
848
-
resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==}
850
+
caniuse-lite@1.0.30001733:
851
+
resolution: {integrity: sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==}
849
852
850
853
chokidar@3.6.0:
851
854
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
···
892
895
duplexer@0.1.2:
893
896
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
894
897
895
-
electron-to-chromium@1.5.194:
896
-
resolution: {integrity: sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==}
898
+
electron-to-chromium@1.5.199:
899
+
resolution: {integrity: sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==}
897
900
898
901
entities@6.0.1:
899
902
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
···
955
958
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
956
959
engines: {node: '>=10'}
957
960
958
-
hls.js@1.6.7:
959
-
resolution: {integrity: sha512-QW2fnwDGKGc9DwQUGLbmMOz8G48UZK7PVNJPcOUql1b8jubKx4/eMHNP5mGqr6tYlJNDG1g10Lx2U/qPzL6zwQ==}
961
+
hls.js@1.6.9:
962
+
resolution: {integrity: sha512-q7qPrri6GRwjcNd7EkFCmhiJ6PBIxeUsdxKbquBkQZpg9jAnp6zSAeN9eEWFlOB09J8JfzAQGoXL5ZEAltjO9g==}
960
963
961
964
html-entities@2.3.3:
962
965
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
···
1190
1193
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
1191
1194
engines: {node: '>=18'}
1192
1195
1193
-
solid-js@1.9.7:
1194
-
resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==}
1196
+
solid-js@1.9.8:
1197
+
resolution: {integrity: sha512-zF9Whfqk+s8wWuyDKnE7ekl+dJburjdZq54O6X1k4XChA57uZ5FOauYAa0s4I44XkBOM3CZmPrZC0DGjH9fKjQ==}
1195
1198
1196
1199
solid-refresh@0.6.3:
1197
1200
resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==}
···
1236
1239
undici-types@6.20.0:
1237
1240
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
1238
1241
1239
-
unocss@66.4.0:
1240
-
resolution: {integrity: sha512-rT88p+Q0O3BX9WmWE1EQi4eNXdRhrFxQRBSvjGXFuWSMZWGWM66jF68OBNf7C5uWtVlv1fT9oFJCwW8cvaBQaA==}
1242
+
unocss@66.4.2:
1243
+
resolution: {integrity: sha512-PsZ+4XF/ekiParR7PZEM7AchvHJ78EIfOXlqTPflTOXCYgZ77kG9NaIaIf4lHRevY+rRTyrHrjxdg1Ern2j8qw==}
1241
1244
engines: {node: '>=14'}
1242
1245
peerDependencies:
1243
-
'@unocss/webpack': 66.4.0
1246
+
'@unocss/webpack': 66.4.2
1244
1247
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
1245
1248
peerDependenciesMeta:
1246
1249
'@unocss/webpack':
···
1248
1251
vite:
1249
1252
optional: true
1250
1253
1251
-
unplugin-utils@0.2.4:
1252
-
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==}
1253
1256
engines: {node: '>=18.12.0'}
1254
1257
1255
1258
update-browserslist-db@1.1.3:
···
1271
1274
'@testing-library/jest-dom':
1272
1275
optional: true
1273
1276
1274
-
vite@7.0.6:
1275
-
resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==}
1277
+
vite@7.1.1:
1278
+
resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==}
1276
1279
engines: {node: ^20.19.0 || >=22.12.0}
1277
1280
hasBin: true
1278
1281
peerDependencies:
···
1341
1344
package-manager-detector: 1.3.0
1342
1345
tinyexec: 1.0.1
1343
1346
1344
-
'@antfu/utils@8.1.1': {}
1347
+
'@antfu/utils@9.2.0': {}
1345
1348
1346
1349
'@atcute/atproto@3.1.1':
1347
1350
dependencies:
···
1476
1479
dependencies:
1477
1480
'@babel/compat-data': 7.28.0
1478
1481
'@babel/helper-validator-option': 7.27.1
1479
-
browserslist: 4.25.1
1482
+
browserslist: 4.25.2
1480
1483
lru-cache: 5.1.1
1481
1484
semver: 6.3.1
1482
1485
···
1703
1706
dependencies:
1704
1707
'@iconify/types': 2.0.0
1705
1708
1706
-
'@iconify-json/lucide@1.2.60':
1709
+
'@iconify-json/lucide@1.2.62':
1707
1710
dependencies:
1708
1711
'@iconify/types': 2.0.0
1709
1712
1710
1713
'@iconify/types@2.0.0': {}
1711
1714
1712
-
'@iconify/utils@2.3.0':
1715
+
'@iconify/utils@3.0.0':
1713
1716
dependencies:
1714
1717
'@antfu/install-pkg': 1.1.0
1715
-
'@antfu/utils': 8.1.1
1718
+
'@antfu/utils': 9.2.0
1716
1719
'@iconify/types': 2.0.0
1717
1720
debug: 4.4.1
1718
1721
globals: 15.15.0
···
1742
1745
1743
1746
'@polka/url@1.0.0-next.29': {}
1744
1747
1745
-
'@quansync/fs@0.1.3':
1748
+
'@quansync/fs@0.1.4':
1746
1749
dependencies:
1747
1750
quansync: 0.2.10
1748
1751
···
1812
1815
'@atcute/cbor': 2.2.5
1813
1816
nanoevents: 9.1.0
1814
1817
1815
-
'@solidjs/meta@0.29.4(solid-js@1.9.7)':
1818
+
'@solidjs/meta@0.29.4(solid-js@1.9.8)':
1816
1819
dependencies:
1817
-
solid-js: 1.9.7
1820
+
solid-js: 1.9.8
1818
1821
1819
-
'@solidjs/router@0.15.3(solid-js@1.9.7)':
1822
+
'@solidjs/router@0.15.3(solid-js@1.9.8)':
1820
1823
dependencies:
1821
-
solid-js: 1.9.7
1824
+
solid-js: 1.9.8
1822
1825
1823
1826
'@types/babel__core@7.20.5':
1824
1827
dependencies:
···
1848
1851
undici-types: 6.20.0
1849
1852
optional: true
1850
1853
1851
-
'@unocss/astro@66.4.0(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))':
1854
+
'@unocss/astro@66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))':
1852
1855
dependencies:
1853
-
'@unocss/core': 66.4.0
1854
-
'@unocss/reset': 66.4.0
1855
-
'@unocss/vite': 66.4.0(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))
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))
1856
1859
optionalDependencies:
1857
-
vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
1860
+
vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
1858
1861
1859
-
'@unocss/cli@66.4.0':
1862
+
'@unocss/cli@66.4.2':
1860
1863
dependencies:
1861
1864
'@ampproject/remapping': 2.3.0
1862
-
'@unocss/config': 66.4.0
1863
-
'@unocss/core': 66.4.0
1864
-
'@unocss/preset-uno': 66.4.0
1865
+
'@unocss/config': 66.4.2
1866
+
'@unocss/core': 66.4.2
1867
+
'@unocss/preset-uno': 66.4.2
1865
1868
cac: 6.7.14
1866
1869
chokidar: 3.6.0
1867
1870
colorette: 2.0.20
···
1870
1873
pathe: 2.0.3
1871
1874
perfect-debounce: 1.0.0
1872
1875
tinyglobby: 0.2.14
1873
-
unplugin-utils: 0.2.4
1876
+
unplugin-utils: 0.2.5
1874
1877
1875
-
'@unocss/config@66.4.0':
1878
+
'@unocss/config@66.4.2':
1876
1879
dependencies:
1877
-
'@unocss/core': 66.4.0
1880
+
'@unocss/core': 66.4.2
1878
1881
unconfig: 7.3.2
1879
1882
1880
-
'@unocss/core@66.4.0': {}
1883
+
'@unocss/core@66.4.2': {}
1881
1884
1882
-
'@unocss/extractor-arbitrary-variants@66.4.0':
1885
+
'@unocss/extractor-arbitrary-variants@66.4.2':
1883
1886
dependencies:
1884
-
'@unocss/core': 66.4.0
1887
+
'@unocss/core': 66.4.2
1885
1888
1886
-
'@unocss/inspector@66.4.0':
1889
+
'@unocss/inspector@66.4.2':
1887
1890
dependencies:
1888
-
'@unocss/core': 66.4.0
1889
-
'@unocss/rule-utils': 66.4.0
1891
+
'@unocss/core': 66.4.2
1892
+
'@unocss/rule-utils': 66.4.2
1890
1893
colorette: 2.0.20
1891
1894
gzip-size: 6.0.0
1892
1895
sirv: 3.0.1
1893
1896
vue-flow-layout: 0.2.0
1894
1897
1895
-
'@unocss/postcss@66.4.0(postcss@8.5.6)':
1898
+
'@unocss/postcss@66.4.2(postcss@8.5.6)':
1896
1899
dependencies:
1897
-
'@unocss/config': 66.4.0
1898
-
'@unocss/core': 66.4.0
1899
-
'@unocss/rule-utils': 66.4.0
1900
+
'@unocss/config': 66.4.2
1901
+
'@unocss/core': 66.4.2
1902
+
'@unocss/rule-utils': 66.4.2
1900
1903
css-tree: 3.1.0
1901
1904
postcss: 8.5.6
1902
1905
tinyglobby: 0.2.14
1903
1906
1904
-
'@unocss/preset-attributify@66.4.0':
1907
+
'@unocss/preset-attributify@66.4.2':
1905
1908
dependencies:
1906
-
'@unocss/core': 66.4.0
1909
+
'@unocss/core': 66.4.2
1907
1910
1908
-
'@unocss/preset-icons@66.4.0':
1911
+
'@unocss/preset-icons@66.4.2':
1909
1912
dependencies:
1910
-
'@iconify/utils': 2.3.0
1911
-
'@unocss/core': 66.4.0
1913
+
'@iconify/utils': 3.0.0
1914
+
'@unocss/core': 66.4.2
1912
1915
ofetch: 1.4.1
1913
1916
transitivePeerDependencies:
1914
1917
- supports-color
1915
1918
1916
-
'@unocss/preset-mini@66.4.0':
1919
+
'@unocss/preset-mini@66.4.2':
1917
1920
dependencies:
1918
-
'@unocss/core': 66.4.0
1919
-
'@unocss/extractor-arbitrary-variants': 66.4.0
1920
-
'@unocss/rule-utils': 66.4.0
1921
+
'@unocss/core': 66.4.2
1922
+
'@unocss/extractor-arbitrary-variants': 66.4.2
1923
+
'@unocss/rule-utils': 66.4.2
1921
1924
1922
-
'@unocss/preset-tagify@66.4.0':
1925
+
'@unocss/preset-tagify@66.4.2':
1923
1926
dependencies:
1924
-
'@unocss/core': 66.4.0
1927
+
'@unocss/core': 66.4.2
1925
1928
1926
-
'@unocss/preset-typography@66.4.0':
1929
+
'@unocss/preset-typography@66.4.2':
1927
1930
dependencies:
1928
-
'@unocss/core': 66.4.0
1929
-
'@unocss/preset-mini': 66.4.0
1930
-
'@unocss/rule-utils': 66.4.0
1931
+
'@unocss/core': 66.4.2
1932
+
'@unocss/preset-mini': 66.4.2
1933
+
'@unocss/rule-utils': 66.4.2
1931
1934
1932
-
'@unocss/preset-uno@66.4.0':
1935
+
'@unocss/preset-uno@66.4.2':
1933
1936
dependencies:
1934
-
'@unocss/core': 66.4.0
1935
-
'@unocss/preset-wind3': 66.4.0
1937
+
'@unocss/core': 66.4.2
1938
+
'@unocss/preset-wind3': 66.4.2
1936
1939
1937
-
'@unocss/preset-web-fonts@66.4.0':
1940
+
'@unocss/preset-web-fonts@66.4.2':
1938
1941
dependencies:
1939
-
'@unocss/core': 66.4.0
1942
+
'@unocss/core': 66.4.2
1940
1943
ofetch: 1.4.1
1941
1944
1942
-
'@unocss/preset-wind3@66.4.0':
1945
+
'@unocss/preset-wind3@66.4.2':
1943
1946
dependencies:
1944
-
'@unocss/core': 66.4.0
1945
-
'@unocss/preset-mini': 66.4.0
1946
-
'@unocss/rule-utils': 66.4.0
1947
+
'@unocss/core': 66.4.2
1948
+
'@unocss/preset-mini': 66.4.2
1949
+
'@unocss/rule-utils': 66.4.2
1947
1950
1948
-
'@unocss/preset-wind4@66.4.0':
1951
+
'@unocss/preset-wind4@66.4.2':
1949
1952
dependencies:
1950
-
'@unocss/core': 66.4.0
1951
-
'@unocss/extractor-arbitrary-variants': 66.4.0
1952
-
'@unocss/rule-utils': 66.4.0
1953
+
'@unocss/core': 66.4.2
1954
+
'@unocss/extractor-arbitrary-variants': 66.4.2
1955
+
'@unocss/rule-utils': 66.4.2
1953
1956
1954
-
'@unocss/preset-wind@66.4.0':
1957
+
'@unocss/preset-wind@66.4.2':
1955
1958
dependencies:
1956
-
'@unocss/core': 66.4.0
1957
-
'@unocss/preset-wind3': 66.4.0
1959
+
'@unocss/core': 66.4.2
1960
+
'@unocss/preset-wind3': 66.4.2
1958
1961
1959
-
'@unocss/reset@66.4.0': {}
1962
+
'@unocss/reset@66.4.2': {}
1960
1963
1961
-
'@unocss/rule-utils@66.4.0':
1964
+
'@unocss/rule-utils@66.4.2':
1962
1965
dependencies:
1963
-
'@unocss/core': 66.4.0
1966
+
'@unocss/core': 66.4.2
1964
1967
magic-string: 0.30.17
1965
1968
1966
-
'@unocss/transformer-attributify-jsx@66.4.0':
1969
+
'@unocss/transformer-attributify-jsx@66.4.2':
1967
1970
dependencies:
1968
1971
'@babel/parser': 7.28.0
1969
1972
'@babel/traverse': 7.28.0
1970
-
'@unocss/core': 66.4.0
1973
+
'@unocss/core': 66.4.2
1971
1974
transitivePeerDependencies:
1972
1975
- supports-color
1973
1976
1974
-
'@unocss/transformer-compile-class@66.4.0':
1977
+
'@unocss/transformer-compile-class@66.4.2':
1975
1978
dependencies:
1976
-
'@unocss/core': 66.4.0
1979
+
'@unocss/core': 66.4.2
1977
1980
1978
-
'@unocss/transformer-directives@66.4.0':
1981
+
'@unocss/transformer-directives@66.4.2':
1979
1982
dependencies:
1980
-
'@unocss/core': 66.4.0
1981
-
'@unocss/rule-utils': 66.4.0
1983
+
'@unocss/core': 66.4.2
1984
+
'@unocss/rule-utils': 66.4.2
1982
1985
css-tree: 3.1.0
1983
1986
1984
-
'@unocss/transformer-variant-group@66.4.0':
1987
+
'@unocss/transformer-variant-group@66.4.2':
1985
1988
dependencies:
1986
-
'@unocss/core': 66.4.0
1989
+
'@unocss/core': 66.4.2
1987
1990
1988
-
'@unocss/vite@66.4.0(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))':
1991
+
'@unocss/vite@66.4.2(vite@7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))':
1989
1992
dependencies:
1990
1993
'@ampproject/remapping': 2.3.0
1991
-
'@unocss/config': 66.4.0
1992
-
'@unocss/core': 66.4.0
1993
-
'@unocss/inspector': 66.4.0
1994
+
'@unocss/config': 66.4.2
1995
+
'@unocss/core': 66.4.2
1996
+
'@unocss/inspector': 66.4.2
1994
1997
chokidar: 3.6.0
1995
1998
magic-string: 0.30.17
1996
1999
pathe: 2.0.3
1997
2000
tinyglobby: 0.2.14
1998
-
unplugin-utils: 0.2.4
1999
-
vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
2001
+
unplugin-utils: 0.2.5
2002
+
vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
2000
2003
2001
2004
acorn@8.15.0: {}
2002
2005
···
2005
2008
normalize-path: 3.0.0
2006
2009
picomatch: 2.3.1
2007
2010
2008
-
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):
2009
2012
dependencies:
2010
2013
'@babel/core': 7.28.0
2011
2014
'@babel/helper-module-imports': 7.18.6
···
2015
2018
parse5: 7.3.0
2016
2019
validate-html-nesting: 1.2.3
2017
2020
2018
-
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):
2019
2022
dependencies:
2020
2023
'@babel/core': 7.28.0
2021
-
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
2022
2027
2023
2028
binary-extensions@2.3.0: {}
2024
2029
···
2026
2031
dependencies:
2027
2032
fill-range: 7.1.1
2028
2033
2029
-
browserslist@4.25.1:
2034
+
browserslist@4.25.2:
2030
2035
dependencies:
2031
-
caniuse-lite: 1.0.30001731
2032
-
electron-to-chromium: 1.5.194
2036
+
caniuse-lite: 1.0.30001733
2037
+
electron-to-chromium: 1.5.199
2033
2038
node-releases: 2.0.19
2034
-
update-browserslist-db: 1.1.3(browserslist@4.25.1)
2039
+
update-browserslist-db: 1.1.3(browserslist@4.25.2)
2035
2040
2036
2041
cac@6.7.14: {}
2037
2042
2038
-
caniuse-lite@1.0.30001731: {}
2043
+
caniuse-lite@1.0.30001733: {}
2039
2044
2040
2045
chokidar@3.6.0:
2041
2046
dependencies:
···
2076
2081
2077
2082
duplexer@0.1.2: {}
2078
2083
2079
-
electron-to-chromium@1.5.194: {}
2084
+
electron-to-chromium@1.5.199: {}
2080
2085
2081
2086
entities@6.0.1: {}
2082
2087
···
2171
2176
dependencies:
2172
2177
duplexer: 0.1.2
2173
2178
2174
-
hls.js@1.6.7: {}
2179
+
hls.js@1.6.9: {}
2175
2180
2176
2181
html-entities@2.3.3: {}
2177
2182
···
2339
2344
mrmime: 2.0.1
2340
2345
totalist: 3.0.1
2341
2346
2342
-
solid-js@1.9.7:
2347
+
solid-js@1.9.8:
2343
2348
dependencies:
2344
2349
csstype: 3.1.3
2345
2350
seroval: 1.3.2
2346
2351
seroval-plugins: 1.3.2(seroval@1.3.2)
2347
2352
2348
-
solid-refresh@0.6.3(solid-js@1.9.7):
2353
+
solid-refresh@0.6.3(solid-js@1.9.8):
2349
2354
dependencies:
2350
2355
'@babel/generator': 7.28.0
2351
2356
'@babel/helper-module-imports': 7.27.1
2352
2357
'@babel/types': 7.28.2
2353
-
solid-js: 1.9.7
2358
+
solid-js: 1.9.8
2354
2359
transitivePeerDependencies:
2355
2360
- supports-color
2356
2361
···
2383
2388
2384
2389
unconfig@7.3.2:
2385
2390
dependencies:
2386
-
'@quansync/fs': 0.1.3
2391
+
'@quansync/fs': 0.1.4
2387
2392
defu: 6.1.4
2388
2393
jiti: 2.5.1
2389
2394
quansync: 0.2.10
···
2391
2396
undici-types@6.20.0:
2392
2397
optional: true
2393
2398
2394
-
unocss@66.4.0(postcss@8.5.6)(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)):
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)):
2395
2400
dependencies:
2396
-
'@unocss/astro': 66.4.0(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))
2397
-
'@unocss/cli': 66.4.0
2398
-
'@unocss/core': 66.4.0
2399
-
'@unocss/postcss': 66.4.0(postcss@8.5.6)
2400
-
'@unocss/preset-attributify': 66.4.0
2401
-
'@unocss/preset-icons': 66.4.0
2402
-
'@unocss/preset-mini': 66.4.0
2403
-
'@unocss/preset-tagify': 66.4.0
2404
-
'@unocss/preset-typography': 66.4.0
2405
-
'@unocss/preset-uno': 66.4.0
2406
-
'@unocss/preset-web-fonts': 66.4.0
2407
-
'@unocss/preset-wind': 66.4.0
2408
-
'@unocss/preset-wind3': 66.4.0
2409
-
'@unocss/preset-wind4': 66.4.0
2410
-
'@unocss/transformer-attributify-jsx': 66.4.0
2411
-
'@unocss/transformer-compile-class': 66.4.0
2412
-
'@unocss/transformer-directives': 66.4.0
2413
-
'@unocss/transformer-variant-group': 66.4.0
2414
-
'@unocss/vite': 66.4.0(vite@7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2))
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))
2415
2420
optionalDependencies:
2416
-
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)
2417
2422
transitivePeerDependencies:
2418
2423
- postcss
2419
2424
- supports-color
2420
2425
2421
-
unplugin-utils@0.2.4:
2426
+
unplugin-utils@0.2.5:
2422
2427
dependencies:
2423
2428
pathe: 2.0.3
2424
2429
picomatch: 4.0.3
2425
2430
2426
-
update-browserslist-db@1.1.3(browserslist@4.25.1):
2431
+
update-browserslist-db@1.1.3(browserslist@4.25.2):
2427
2432
dependencies:
2428
-
browserslist: 4.25.1
2433
+
browserslist: 4.25.2
2429
2434
escalade: 3.2.0
2430
2435
picocolors: 1.1.1
2431
2436
2432
2437
validate-html-nesting@1.2.3: {}
2433
2438
2434
-
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)):
2435
2440
dependencies:
2436
2441
'@babel/core': 7.28.0
2437
2442
'@types/babel__core': 7.20.5
2438
-
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)
2439
2444
merge-anything: 5.1.7
2440
-
solid-js: 1.9.7
2441
-
solid-refresh: 0.6.3(solid-js@1.9.7)
2442
-
vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
2443
-
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))
2444
2449
transitivePeerDependencies:
2445
2450
- supports-color
2446
2451
2447
-
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):
2448
2453
dependencies:
2449
2454
esbuild: 0.25.8
2450
2455
fdir: 6.4.6(picomatch@4.0.3)
···
2458
2463
jiti: 2.5.1
2459
2464
tsx: 4.19.2
2460
2465
2461
-
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)):
2462
2467
optionalDependencies:
2463
-
vite: 7.0.6(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
2468
+
vite: 7.1.1(@types/node@22.13.1)(jiti@2.5.1)(tsx@4.19.2)
2464
2469
2465
2470
vue-flow-layout@0.2.0: {}
2466
2471
+12
-12
src/components/account.tsx
+12
-12
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 w-21rem 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
73
<div class="mb-3 max-h-[20rem] overflow-y-auto md:max-h-[25rem]">
74
74
<For each={Object.keys(sessions)}>
···
79
79
onclick={() => resumeSession(did as Did)}
80
80
>
81
81
<span class="truncate">{sessions[did]?.length ? sessions[did] : did}</span>
82
-
<Show when={did === agent?.sub}>
82
+
<Show when={did === agent()?.sub}>
83
83
<div class="i-lucide-check shrink-0" />
84
84
</Show>
85
85
</button>
···
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>
+1
-1
src/components/backlinks.tsx
+1
-1
src/components/backlinks.tsx
···
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-slate-900 dark:focus:outline-slate-100"
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>
+13
-12
src/components/create.tsx
+13
-12
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("");
···
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,
···
171
172
return (
172
173
<>
173
174
<Modal open={openDialog()} onClose={() => setOpenDialog(false)}>
174
-
<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">
175
176
<div class="mb-2 flex w-full justify-between">
176
177
<h3 class="font-bold">{props.create ? "Creating" : "Editing"} record</h3>
177
178
<div
···
207
208
<select
208
209
name="validate"
209
210
id="validate"
210
-
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-slate-900 dark:focus:outline-slate-100"
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"
211
212
>
212
213
<option value="unset">Unset</option>
213
214
<option value="true">True</option>
···
216
217
</div>
217
218
<div class="flex items-center gap-2">
218
219
<Show when={!uploading()}>
219
-
<div class="dark:hover:bg-dark-100 dark:bg-dark-300 focus-within:outline-1.5 dark:shadow-dark-900 dark:within-focus:outline-slate-100 flex rounded-lg bg-white text-xs font-bold shadow-sm focus-within:outline-slate-900 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">
220
221
<input type="file" id="blob" hidden onChange={() => uploadBlob()} />
221
222
<label class="flex items-center gap-1 px-2 py-1.5" for="blob">
222
223
<div class="i-lucide-upload text-sm" />
···
266
267
createRecord(new FormData(formRef))
267
268
: editRecord(new FormData(formRef))
268
269
}
269
-
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-slate-900 sm:text-sm dark:focus:outline-slate-100"
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"
270
271
>
271
272
{props.create ? "Create" : "Edit"}
272
273
</button>
+6
-1
src/components/editor.tsx
+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 };
+3
-7
src/components/login.tsx
+3
-7
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("");
···
106
105
107
106
const session = await init().catch(() => {});
108
107
109
-
if (session) {
110
-
agent = new OAuthUserAgent(session);
111
-
setLoginState(true);
112
-
}
108
+
if (session) setAgent(new OAuthUserAgent(session));
113
109
};
114
110
115
-
export { Login, retrieveSession, agent };
111
+
export { Login, retrieveSession };
+6
-23
src/components/search.tsx
+6
-23
src/components/search.tsx
···
1
1
import { resolveHandle } from "../utils/api.js";
2
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
-
8
-
const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 1;
9
7
10
8
const Search = () => {
11
9
const navigate = useNavigate();
···
45
43
navigate(`/at://${did}${uriParts.length > 1 ? `/${uriParts.slice(1).join("/")}` : ""}`);
46
44
};
47
45
48
-
onMount(() => window.addEventListener("keydown", keyEvent));
49
-
onCleanup(() => window.removeEventListener("keydown", keyEvent));
50
-
51
-
const keyEvent = (event: KeyboardEvent) => {
52
-
if (event.key == "/" && document.activeElement !== searchInput) {
53
-
event.preventDefault();
54
-
searchInput.focus();
55
-
}
56
-
if (event.key == "Escape" && document.activeElement === searchInput) {
57
-
event.preventDefault();
58
-
searchInput.blur();
59
-
}
60
-
};
61
-
62
46
return (
63
47
<form
64
48
class="flex w-full max-w-[21rem] flex-col sm:max-w-[24rem]"
···
67
51
>
68
52
<div class="w-full">
69
53
<label for="input" class="ml-0.5 text-sm">
70
-
PDS URL or AT URI
54
+
PDS URL or AT URI (at:// optional)
71
55
</label>
72
56
</div>
73
57
<div class="flex w-full items-center gap-2">
74
-
<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-slate-900 dark:focus-within:outline-slate-100">
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">
75
59
<input
76
60
type="text"
77
61
spellcheck={false}
78
62
ref={searchInput}
79
63
id="input"
80
-
placeholder={isTouchDevice ? "" : "Type / to search"}
81
64
class="grow focus:outline-none"
82
65
/>
83
66
<Show when={loading()}>
···
89
72
</button>
90
73
</Show>
91
74
</div>
92
-
<Show when={loginState()}>
75
+
<Show when={agent()}>
93
76
<Tooltip
94
77
text="Repository"
95
78
children={
96
-
<A href={`/at://${agent.sub}`} class="flex">
79
+
<A href={`/at://${agent()?.sub}`} class="flex">
97
80
<div class="i-lucide-house text-xl" />
98
81
</A>
99
82
}
+8
-5
src/components/settings.tsx
+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 w-21rem 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="flex w-full 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
+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 dark:shadow-dark-900 rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-slate-900 dark:focus:outline-slate-100 " +
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
+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;
+18
-18
src/layout.tsx
+18
-18
src/layout.tsx
···
1
-
import { createEffect, ErrorBoundary, onMount, Show, Suspense } from "solid-js";
1
+
import { createEffect, ErrorBoundary, Show, Suspense } from "solid-js";
2
2
import { A, RouteSectionProps, useLocation, useNavigate, useParams } from "@solidjs/router";
3
-
import { agent, loginState, retrieveSession } from "./components/login.jsx";
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
22
const navigate = useNavigate();
19
-
20
-
onMount(async () => {
21
-
if (location.search.includes("kawaii=true")) localStorage.kawaii = "true";
22
-
await retrieveSession();
23
-
if (loginState() && location.pathname === "/") navigate(`/at://${agent.sub}`);
24
-
});
23
+
if (location.search.includes("kawaii=true")) localStorage.kawaii = "true";
25
24
26
25
createEffect(async () => {
27
26
if (params.repo && !params.repo.startsWith("did:")) {
28
27
const did = await resolveHandle(params.repo as Handle);
29
-
window.location.replace(location.pathname.replace(params.repo, did));
28
+
navigate(location.pathname.replace(params.repo, did));
30
29
}
31
30
});
32
31
33
32
return (
34
33
<div id="main" class="m-4 flex flex-col items-center text-slate-900 dark:text-slate-100">
35
-
<Show when={location.pathname !== "/"}>
36
-
<MetaProvider>
34
+
<MetaProvider>
35
+
<Show when={location.pathname !== "/"}>
37
36
<Meta name="robots" content="noindex, nofollow" />
38
-
</MetaProvider>
39
-
</Show>
37
+
</Show>
38
+
<Title>{customTitle[params.repo] ?? "PDSls"}</Title>
39
+
</MetaProvider>
40
40
<div class="mb-2 flex w-[21rem] items-center sm:w-[24rem]">
41
41
<div class="flex basis-1/3 gap-x-2">
42
42
<A href="/jetstream">
···
48
48
</div>
49
49
<div class="flex basis-1/3 items-center justify-center text-center">
50
50
<A href="/" class="font-mono font-bold hover:underline">
51
-
PDSls
51
+
{customTitle[params.repo] ?? "PDSls"}
52
52
</A>
53
-
<Show when={location.search.includes("kawaii=true") || kawaii()}>
53
+
<Show when={localStorage.kawaii === "true" || kawaii()}>
54
54
<a
55
55
href="https://bsky.app/profile/ninikyuu.bsky.social/post/3l3tq5xwqf22o"
56
56
target="_blank"
···
65
65
</Show>
66
66
</div>
67
67
<div class="justify-right flex basis-1/3 items-center gap-x-2">
68
-
<Show when={loginState()}>
68
+
<Show when={agent()}>
69
69
<RecordEditor create={true} />
70
70
</Show>
71
71
<Settings />
···
93
93
</Show>
94
94
</div>
95
95
<Show when={copyNotice()}>
96
-
<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">
97
97
<div class="i-lucide-clipboard-check mr-1 text-xl" />
98
98
Copied to clipboard
99
99
</div>
+416
src/utils/plc-logs.ts
+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
+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-slate-900 dark:focus:outline-slate-100"
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>
+23
-18
src/views/collection.tsx
+23
-18
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"}
···
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-slate-900 dark:focus:outline-slate-100"
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
+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">
+2
-2
src/views/labels.tsx
+2
-2
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-slate-900 dark:focus:outline-slate-100"
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-slate-900 dark:focus:outline-slate-100"
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>
+4
-2
src/views/pds.tsx
+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-slate-900 dark:focus:outline-slate-100"
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
+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-slate-900 dark:focus:outline-slate-100"
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-slate-900 dark:focus:outline-slate-100"
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())}>
+275
-92
src/views/repo.tsx
+275
-92
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();
···
28
166
const [nsids, setNsids] = createSignal<Record<string, { hidden: boolean; nsids: string[] }>>();
29
167
const [tab, setTab] = createSignal<Tab>("collections");
30
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>();
31
174
let rpc: Client;
32
175
let pds: string;
33
176
const did = params.repo;
···
35
178
const RepoTab = (props: { tab: Tab; label: string }) => (
36
179
<button
37
180
classList={{
38
-
"rounded-lg flex flex-1 py-1 justify-center": true,
39
-
"bg-zinc-200/70 dark:bg-dark-200 shadow-sm dark:shadow-dark-900": tab() === props.tab,
40
-
"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,
41
184
}}
42
185
onclick={() => setTab(props.tab)}
43
186
>
···
172
315
onInput={(e) => setFilter(e.currentTarget.value)}
173
316
/>
174
317
<div class="flex flex-col font-mono">
175
-
<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">
176
319
<For
177
320
each={Object.keys(nsids() ?? {}).filter((authority) =>
178
321
filter() ?
···
182
325
>
183
326
{(authority) => (
184
327
<>
185
-
<Show when={nsids()?.[authority].hidden}>
186
-
<button onclick={() => toggleCollection(authority)}>
187
-
<div class="i-lucide-chevron-right mr-1 text-lg" />
188
-
</button>
189
-
</Show>
190
-
<Show when={!nsids()?.[authority].hidden}>
191
-
<button onclick={() => toggleCollection(authority)}>
192
-
<div class="i-lucide-chevron-down mr-1 text-lg" />
193
-
</button>
194
-
</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>
195
336
<button
196
337
class="break-anywhere bg-transparent text-left"
197
338
onclick={() => toggleCollection(authority)}
···
228
369
<Show when={tab() === "doc"}>
229
370
<Show when={didDoc()}>
230
371
{(didDocument) => (
231
-
<div class="break-anywhere flex flex-col gap-y-1">
232
-
<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>
233
392
<div>
234
-
<span class="font-semibold text-stone-600 dark:text-stone-400">ID </span>
235
-
<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>
236
397
</div>
237
-
<Tooltip text="DID Document">
238
-
<a
239
-
href={
240
-
did.startsWith("did:plc") ?
241
-
`${localStorage.plcDirectory ?? "https://plc.directory"}/${did}`
242
-
: `https://${did.split("did:web:")[1]}/.well-known/did.json`
243
-
}
244
-
target="_blank"
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>
432
+
</div>
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
+
}}
245
476
>
246
-
<div class="i-lucide-external-link text-lg" />
247
-
</a>
248
-
</Tooltip>
249
-
</div>
250
-
<div>
251
-
<p class="font-semibold text-stone-600 dark:text-stone-400">Identities</p>
252
-
<ul class="ml-2">
253
-
<For each={didDocument().alsoKnownAs}>{(alias) => <li>{alias}</li>}</For>
254
-
</ul>
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>
255
490
</div>
256
-
<div>
257
-
<p class="font-semibold text-stone-600 dark:text-stone-400">Services</p>
258
-
<ul class="ml-2">
259
-
<For each={didDocument().service}>
260
-
{(service) => (
261
-
<li class="flex flex-col">
262
-
<span>#{service.id.split("#")[1]}</span>
263
-
<a
264
-
class="w-fit text-blue-400 hover:underline"
265
-
href={service.serviceEndpoint.toString()}
266
-
target="_blank"
267
-
>
268
-
{service.serviceEndpoint.toString()}
269
-
</a>
270
-
</li>
271
-
)}
272
-
</For>
273
-
</ul>
274
-
</div>
275
-
<div>
276
-
<p class="font-semibold text-stone-600 dark:text-stone-400">
277
-
Verification methods
278
-
</p>
279
-
<ul class="ml-2">
280
-
<For each={didDocument().verificationMethod}>
281
-
{(verif) => (
282
-
<li class="flex flex-col">
283
-
<span>#{verif.id.split("#")[1]}</span>
284
-
<span>{verif.publicKeyMultibase}</span>
285
-
</li>
286
-
)}
287
-
</For>
288
-
</ul>
289
-
</div>
290
-
<Show when={did.startsWith("did:plc")}>
291
-
<a
292
-
class="flex w-fit items-center text-blue-400 hover:underline"
293
-
href={`https://boat.kelinci.net/plc-oplogs?q=${did}`}
294
-
target="_blank"
295
-
>
296
-
PLC operation logs <div class="i-lucide-external-link ml-0.5 text-sm" />
297
-
</a>
298
-
</Show>
299
-
<Show when={error()?.length === 0 || error() === undefined}>
300
-
<div class="flex items-center gap-1">
301
-
<button
302
-
type="button"
303
-
onclick={() => downloadRepo()}
304
-
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-slate-900 dark:focus:outline-slate-100"
305
-
>
306
-
<div class="i-lucide-download text-sm" />
307
-
Export Repo
308
-
</button>
309
-
<Show when={downloading()}>
310
-
<div class="i-lucide-loader-circle animate-spin text-xl" />
311
-
</Show>
312
-
</div>
491
+
<Show when={showPlcLogs()}>
492
+
<Show when={notice()}>
493
+
<div>{notice()}</div>
494
+
</Show>
495
+
<PlcLogView plcOps={plcOps() ?? []} did={did} />
313
496
</Show>
314
497
</div>
315
498
)}
+4
-4
src/views/stream.tsx
+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-slate-900 dark:focus:outline-slate-100"
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-slate-900 dark:focus:outline-slate-100"
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-slate-900 dark:focus:outline-slate-100"
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>