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