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