-2
index.html
-2
index.html
···
11
11
<meta property="description" content="Browse the public data on atproto" />
12
12
<link rel="manifest" href="/manifest.json" />
13
13
<title>PDSls</title>
14
-
<link rel="preconnect" href="https://rsms.me/" />
15
-
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
16
14
<link rel="preconnect" href="https://fonts.bunny.net" />
17
15
<link href="https://fonts.bunny.net/css?family=roboto-mono:400" rel="stylesheet" />
18
16
<link href="https://fonts.cdnfonts.com/css/pecita" rel="stylesheet" />
+13
-13
package.json
+13
-13
package.json
···
9
9
"serve": "vite preview"
10
10
},
11
11
"devDependencies": {
12
-
"@iconify-json/lucide": "^1.2.77",
12
+
"@iconify-json/lucide": "^1.2.81",
13
13
"@iconify/tailwind4": "^1.2.0",
14
-
"@tailwindcss/vite": "^4.1.17",
14
+
"@tailwindcss/vite": "^4.1.18",
15
15
"prettier": "^3.7.4",
16
16
"prettier-plugin-organize-imports": "^4.3.0",
17
17
"prettier-plugin-tailwindcss": "^0.7.2",
18
-
"tailwindcss": "^4.1.17",
18
+
"tailwindcss": "^4.1.18",
19
19
"typescript": "^5.9.3",
20
-
"vite": "^7.2.6",
20
+
"vite": "^7.2.7",
21
21
"vite-plugin-solid": "^2.11.10"
22
22
},
23
23
"dependencies": {
24
24
"@atcute/atproto": "^3.1.9",
25
-
"@atcute/bluesky": "^3.2.11",
26
-
"@atcute/client": "^4.1.0",
27
-
"@atcute/crypto": "^2.2.6",
25
+
"@atcute/bluesky": "^3.2.14",
26
+
"@atcute/client": "^4.1.1",
27
+
"@atcute/crypto": "^2.3.0",
28
28
"@atcute/did-plc": "^0.2.0",
29
29
"@atcute/identity": "^1.1.3",
30
-
"@atcute/identity-resolver": "^1.1.4",
31
-
"@atcute/leaflet": "^1.0.12",
32
-
"@atcute/lexicon-doc": "^2.0.4",
30
+
"@atcute/identity-resolver": "^1.2.0",
31
+
"@atcute/leaflet": "^1.0.14",
32
+
"@atcute/lexicon-doc": "^2.0.5",
33
33
"@atcute/lexicon-resolver": "^0.1.5",
34
34
"@atcute/lexicons": "^1.2.5",
35
35
"@atcute/multibase": "^1.1.6",
36
-
"@atcute/oauth-browser-client": "^2.0.1",
36
+
"@atcute/oauth-browser-client": "^2.0.3",
37
37
"@atcute/repo": "^0.1.0",
38
-
"@atcute/tangled": "^1.0.12",
38
+
"@atcute/tangled": "^1.0.13",
39
39
"@atcute/tid": "^1.0.3",
40
40
"@codemirror/commands": "^6.10.0",
41
41
"@codemirror/lang-json": "^6.0.2",
42
42
"@codemirror/lint": "^6.9.2",
43
43
"@codemirror/state": "^6.5.2",
44
-
"@codemirror/view": "^6.38.8",
44
+
"@codemirror/view": "^6.39.4",
45
45
"@fsegurai/codemirror-theme-basic-dark": "^6.2.3",
46
46
"@fsegurai/codemirror-theme-basic-light": "^6.2.3",
47
47
"@mary/exif-rm": "jsr:^0.2.2",
+194
-193
pnpm-lock.yaml
+194
-193
pnpm-lock.yaml
···
12
12
specifier: ^3.1.9
13
13
version: 3.1.9
14
14
'@atcute/bluesky':
15
-
specifier: ^3.2.11
16
-
version: 3.2.11
15
+
specifier: ^3.2.14
16
+
version: 3.2.14
17
17
'@atcute/client':
18
-
specifier: ^4.1.0
19
-
version: 4.1.0
18
+
specifier: ^4.1.1
19
+
version: 4.1.1
20
20
'@atcute/crypto':
21
-
specifier: ^2.2.6
22
-
version: 2.2.6
21
+
specifier: ^2.3.0
22
+
version: 2.3.0
23
23
'@atcute/did-plc':
24
24
specifier: ^0.2.0
25
25
version: 0.2.0
···
27
27
specifier: ^1.1.3
28
28
version: 1.1.3
29
29
'@atcute/identity-resolver':
30
-
specifier: ^1.1.4
31
-
version: 1.1.4(@atcute/identity@1.1.3)
30
+
specifier: ^1.2.0
31
+
version: 1.2.0(@atcute/identity@1.1.3)
32
32
'@atcute/leaflet':
33
-
specifier: ^1.0.12
34
-
version: 1.0.12
33
+
specifier: ^1.0.14
34
+
version: 1.0.14
35
35
'@atcute/lexicon-doc':
36
-
specifier: ^2.0.4
37
-
version: 2.0.4
36
+
specifier: ^2.0.5
37
+
version: 2.0.5
38
38
'@atcute/lexicon-resolver':
39
39
specifier: ^0.1.5
40
-
version: 0.1.5(@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)
40
+
version: 0.1.5(@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)
41
41
'@atcute/lexicons':
42
42
specifier: ^1.2.5
43
43
version: 1.2.5
···
45
45
specifier: ^1.1.6
46
46
version: 1.1.6
47
47
'@atcute/oauth-browser-client':
48
-
specifier: ^2.0.1
49
-
version: 2.0.1
48
+
specifier: ^2.0.3
49
+
version: 2.0.3(@atcute/identity@1.1.3)
50
50
'@atcute/repo':
51
51
specifier: ^0.1.0
52
52
version: 0.1.0
53
53
'@atcute/tangled':
54
-
specifier: ^1.0.12
55
-
version: 1.0.12
54
+
specifier: ^1.0.13
55
+
version: 1.0.13
56
56
'@atcute/tid':
57
57
specifier: ^1.0.3
58
58
version: 1.0.3
···
69
69
specifier: ^6.5.2
70
70
version: 6.5.2
71
71
'@codemirror/view':
72
-
specifier: ^6.38.8
73
-
version: 6.38.8
72
+
specifier: ^6.39.4
73
+
version: 6.39.4
74
74
'@fsegurai/codemirror-theme-basic-dark':
75
75
specifier: ^6.2.3
76
-
version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3)
76
+
version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)
77
77
'@fsegurai/codemirror-theme-basic-light':
78
78
specifier: ^6.2.3
79
-
version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3)
79
+
version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)
80
80
'@mary/exif-rm':
81
81
specifier: jsr:^0.2.2
82
82
version: '@jsr/mary__exif-rm@0.2.2'
···
97
97
version: 1.9.10
98
98
devDependencies:
99
99
'@iconify-json/lucide':
100
-
specifier: ^1.2.77
101
-
version: 1.2.77
100
+
specifier: ^1.2.81
101
+
version: 1.2.81
102
102
'@iconify/tailwind4':
103
103
specifier: ^1.2.0
104
-
version: 1.2.0(tailwindcss@4.1.17)
104
+
version: 1.2.0(tailwindcss@4.1.18)
105
105
'@tailwindcss/vite':
106
-
specifier: ^4.1.17
107
-
version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
106
+
specifier: ^4.1.18
107
+
version: 4.1.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
108
108
prettier:
109
109
specifier: ^3.7.4
110
110
version: 3.7.4
···
115
115
specifier: ^0.7.2
116
116
version: 0.7.2(prettier-plugin-organize-imports@4.3.0(prettier@3.7.4)(typescript@5.9.3))(prettier@3.7.4)
117
117
tailwindcss:
118
-
specifier: ^4.1.17
119
-
version: 4.1.17
118
+
specifier: ^4.1.18
119
+
version: 4.1.18
120
120
typescript:
121
121
specifier: ^5.9.3
122
122
version: 5.9.3
123
123
vite:
124
-
specifier: ^7.2.6
125
-
version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
124
+
specifier: ^7.2.7
125
+
version: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
126
126
vite-plugin-solid:
127
127
specifier: ^2.11.10
128
-
version: 2.11.10(solid-js@1.9.10)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
128
+
version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
129
129
130
130
packages:
131
131
···
135
135
'@atcute/atproto@3.1.9':
136
136
resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==}
137
137
138
-
'@atcute/bluesky@3.2.11':
139
-
resolution: {integrity: sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A==}
138
+
'@atcute/bluesky@3.2.14':
139
+
resolution: {integrity: sha512-XlVuF55AYIyplmKvlGLlj+cUvk9ggxNRPczkTPIY991xJ4qDxDHpBJ39ekAV4dWcuBoRo2o9JynzpafPu2ljDA==}
140
140
141
141
'@atcute/car@3.1.3':
142
142
resolution: {integrity: sha512-WJ13bAEt7TjDMVi09ubjLtvhdljbWInGm9Kfy7Y6NhrmiyC/aZYaA/zHX/bHI6xv1c/h3SQduWqxOr4ae49eqA==}
···
150
150
'@atcute/cid@2.2.6':
151
151
resolution: {integrity: sha512-bTAHHbJ24p+E//V4KCS4xdmd39o211jJswvqQOevj7vk+5IYcgDLx1ryZWZ1sEPOo9x875li/kj5gpKL14RDwQ==}
152
152
153
-
'@atcute/client@4.1.0':
154
-
resolution: {integrity: sha512-AYhSu3RSDA2VDkVGOmad320NRbUUUf5pCFWJcOzlk25YC/4kyzmMFfpzhf1jjjEcY+anNBXGGhav/kKB1evggQ==}
153
+
'@atcute/client@4.1.1':
154
+
resolution: {integrity: sha512-FROCbTTCeL5u4tO/n72jDEKyKqjdlXMB56Ehve3W/gnnLGCYWvN42sS7tvL1Mgu6sbO3yZwsXKDrmM2No4XpjA==}
155
155
156
-
'@atcute/crypto@2.2.6':
157
-
resolution: {integrity: sha512-vkuexF+kmrKE1/Uqzub99Qi4QpnxA2jbu60E6PTgL4XypELQ6rb59MB/J1VbY2gs0kd3ET7+L3+NWpKD5nXyfA==}
156
+
'@atcute/crypto@2.3.0':
157
+
resolution: {integrity: sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ==}
158
158
159
159
'@atcute/did-plc@0.2.0':
160
160
resolution: {integrity: sha512-1sGek8GRM/Ph7nLVRREm8FqM7g4shGckItvdVwJcRbUa8Rh0zOsXQa0QyYWAC0k40BhkqO9FwKXhJEaXCmF5oQ==}
161
161
162
-
'@atcute/identity-resolver@1.1.4':
163
-
resolution: {integrity: sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==}
162
+
'@atcute/identity-resolver@1.2.0':
163
+
resolution: {integrity: sha512-5UbSJfdV3JIkF8ksXz7g4nKBWasf2wROvzM66cfvTIWydWFO6/oS1KZd+zo9Eokje5Scf5+jsY9ZfgVARLepXg==}
164
164
peerDependencies:
165
165
'@atcute/identity': ^1.0.0
166
166
167
167
'@atcute/identity@1.1.3':
168
168
resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==}
169
169
170
-
'@atcute/leaflet@1.0.12':
171
-
resolution: {integrity: sha512-T5laBTl8vwzy0eZXBy07IQSjsLqhbZmRJsffnNQ6XMSc+lnCZ/NHfuKy8TNJbDU6dc26Z7o5l0ELfWz5QESo+w==}
170
+
'@atcute/leaflet@1.0.14':
171
+
resolution: {integrity: sha512-TWbtB7b73GChBaYwfd7aWFyGVObZ/DqrRtwkpWGm1GO8zZmQ9eJyKDUnXim7NOAs2hmKQ1u2wk2AM4AYzkF5Gg==}
172
172
173
-
'@atcute/lexicon-doc@2.0.4':
174
-
resolution: {integrity: sha512-YfwlYFoYiBvRIYG0I1zsINCTFugFtS8l67uT3nQ04zdKVflzdg8uUj8cNZYRNY1V7okoOPdikhR4kPFhYGyemw==}
173
+
'@atcute/lexicon-doc@2.0.5':
174
+
resolution: {integrity: sha512-fNCp94ehGjWFZMIqP6pWD1F9MOJogNCyqsaMVZluPSIclZ+lDL528iXB56aW4u0eSiD6Y9WJB1OI/lElG39cSA==}
175
175
176
176
'@atcute/lexicon-resolver@0.1.5':
177
177
resolution: {integrity: sha512-0bx1/zdMQPuxvRcHW6ykAxRxktC2rEZLoAVSFoLSWDAA92Tf09F9QPK5wgXSF4MNODm1dvzMEdWSMIvlg8sr3A==}
···
188
188
'@atcute/multibase@1.1.6':
189
189
resolution: {integrity: sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==}
190
190
191
-
'@atcute/oauth-browser-client@2.0.1':
192
-
resolution: {integrity: sha512-lG021GkeORG06zfFf4bH85egObjBEKHNgAWHvbtY/E2dX4wxo88hf370pJDx8acdnuUJLJ2VKPikJtZwo4Heeg==}
191
+
'@atcute/oauth-browser-client@2.0.3':
192
+
resolution: {integrity: sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ==}
193
193
194
194
'@atcute/repo@0.1.0':
195
195
resolution: {integrity: sha512-INiYAuma8dydBu7cqd2WVpcXh3mzhIepYBUqFWAK5MqMulPRLTRCc/9GW3G9pxYrOdlvLCVamG2Jf8XK0nuFEw==}
196
196
197
-
'@atcute/tangled@1.0.12':
198
-
resolution: {integrity: sha512-JKA5sOhd8SLhDFhY+PKHqLLytQBBKSiwcaEzfYUJBeyfvqXFPNNAwvRbe3VST4IQ3izoOu3O0R9/b1mjL45UzA==}
197
+
'@atcute/tangled@1.0.13':
198
+
resolution: {integrity: sha512-K95jmjDXl/f1FFzOJkk07ibNbFsPmn64sdrMACxQmUibO9WcfSjzjZLPXuH6WHFnCNtIBG3x1FQ7ndQgLoZAmw==}
199
199
200
200
'@atcute/tid@1.0.3':
201
201
resolution: {integrity: sha512-wfMJx1IMdnu0CZgWl0uR4JO2s6PGT1YPhpytD4ZHzEYKKQVuqV6Eb/7vieaVo1eYNMp2FrY67FZObeR7utRl2w==}
202
202
203
-
'@atcute/uint8array@1.0.5':
204
-
resolution: {integrity: sha512-XLWWxoR2HNl2qU+FCr0rp1APwJXci7HnzbOQLxK55OaMNBXZ19+xNC5ii4QCsThsDxa4JS/JTzuiQLziITWf2Q==}
203
+
'@atcute/uint8array@1.0.6':
204
+
resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==}
205
205
206
206
'@atcute/util-fetch@1.0.4':
207
207
resolution: {integrity: sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg==}
···
315
315
'@codemirror/state@6.5.2':
316
316
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
317
317
318
-
'@codemirror/view@6.38.8':
319
-
resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==}
318
+
'@codemirror/view@6.39.4':
319
+
resolution: {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==}
320
320
321
321
'@cyberalien/svg-utils@1.0.11':
322
322
resolution: {integrity: sha512-qEE9mnyI+avfGT3emKuRs3ucYkITeaV0Xi7VlYN41f+uGnZBecQP3jwz/AF437H9J4Q7qPClHKm4NiTYpNE6hA==}
···
637
637
'@codemirror/view': ^6.0.0
638
638
'@lezer/highlight': ^1.0.0
639
639
640
-
'@iconify-json/lucide@1.2.77':
641
-
resolution: {integrity: sha512-FF3Z+np6Ksb0MaoQymhCHZ4xs5Oo8992Fw7By7bCgVCbBCClYV3wxpF8KzsI1FlxHD4ZXR42NVmXuqdW8YQGgA==}
640
+
'@iconify-json/lucide@1.2.81':
641
+
resolution: {integrity: sha512-6Kz/+SEuD5bkg0KImi0yFem9l6njKp4e1qF1LpQbgRfk7ngsJR/qjlB4y5rM8N1iKiDR/p19cqhmwZxyCWek+w==}
642
642
643
643
'@iconify/tailwind4@1.2.0':
644
644
resolution: {integrity: sha512-+t7XqfojOB0zzZdd8gV7IQZGq1AaIHTlsxMVzagxYR0hAlJCLUD63o3iSlNKRMH3ZR7gZ8y5c9dJ7J431avRbA==}
···
682
682
'@lezer/json@1.0.3':
683
683
resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==}
684
684
685
-
'@lezer/lr@1.4.4':
686
-
resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==}
685
+
'@lezer/lr@1.4.5':
686
+
resolution: {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==}
687
687
688
688
'@marijn/find-cluster-break@1.0.2':
689
689
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
···
817
817
'@standard-schema/spec@1.0.0':
818
818
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
819
819
820
-
'@tailwindcss/node@4.1.17':
821
-
resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
820
+
'@tailwindcss/node@4.1.18':
821
+
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
822
822
823
-
'@tailwindcss/oxide-android-arm64@4.1.17':
824
-
resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
823
+
'@tailwindcss/oxide-android-arm64@4.1.18':
824
+
resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
825
825
engines: {node: '>= 10'}
826
826
cpu: [arm64]
827
827
os: [android]
828
828
829
-
'@tailwindcss/oxide-darwin-arm64@4.1.17':
830
-
resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
829
+
'@tailwindcss/oxide-darwin-arm64@4.1.18':
830
+
resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
831
831
engines: {node: '>= 10'}
832
832
cpu: [arm64]
833
833
os: [darwin]
834
834
835
-
'@tailwindcss/oxide-darwin-x64@4.1.17':
836
-
resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
835
+
'@tailwindcss/oxide-darwin-x64@4.1.18':
836
+
resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
837
837
engines: {node: '>= 10'}
838
838
cpu: [x64]
839
839
os: [darwin]
840
840
841
-
'@tailwindcss/oxide-freebsd-x64@4.1.17':
842
-
resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
841
+
'@tailwindcss/oxide-freebsd-x64@4.1.18':
842
+
resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
843
843
engines: {node: '>= 10'}
844
844
cpu: [x64]
845
845
os: [freebsd]
846
846
847
-
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
848
-
resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
847
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
848
+
resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
849
849
engines: {node: '>= 10'}
850
850
cpu: [arm]
851
851
os: [linux]
852
852
853
-
'@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
854
-
resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
853
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
854
+
resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
855
855
engines: {node: '>= 10'}
856
856
cpu: [arm64]
857
857
os: [linux]
858
858
859
-
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
860
-
resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
859
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
860
+
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
861
861
engines: {node: '>= 10'}
862
862
cpu: [arm64]
863
863
os: [linux]
864
864
865
-
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
866
-
resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
865
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
866
+
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
867
867
engines: {node: '>= 10'}
868
868
cpu: [x64]
869
869
os: [linux]
870
870
871
-
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
872
-
resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
871
+
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
872
+
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
873
873
engines: {node: '>= 10'}
874
874
cpu: [x64]
875
875
os: [linux]
876
876
877
-
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
878
-
resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
877
+
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
878
+
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
879
879
engines: {node: '>=14.0.0'}
880
880
cpu: [wasm32]
881
881
bundledDependencies:
···
886
886
- '@emnapi/wasi-threads'
887
887
- tslib
888
888
889
-
'@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
890
-
resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
889
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
890
+
resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
891
891
engines: {node: '>= 10'}
892
892
cpu: [arm64]
893
893
os: [win32]
894
894
895
-
'@tailwindcss/oxide-win32-x64-msvc@4.1.17':
896
-
resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
895
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.18':
896
+
resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
897
897
engines: {node: '>= 10'}
898
898
cpu: [x64]
899
899
os: [win32]
900
900
901
-
'@tailwindcss/oxide@4.1.17':
902
-
resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
901
+
'@tailwindcss/oxide@4.1.18':
902
+
resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
903
903
engines: {node: '>= 10'}
904
904
905
-
'@tailwindcss/vite@4.1.17':
906
-
resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==}
905
+
'@tailwindcss/vite@4.1.18':
906
+
resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==}
907
907
peerDependencies:
908
908
vite: ^5.2.0 || ^6 || ^7
909
909
···
944
944
solid-js:
945
945
optional: true
946
946
947
-
baseline-browser-mapping@2.9.0:
948
-
resolution: {integrity: sha512-Mh++g+2LPfzZToywfE1BUzvZbfOY52Nil0rn9H1CPC5DJ7fX+Vir7nToBeoiSbB1zTNeGYbELEvJESujgGrzXw==}
947
+
baseline-browser-mapping@2.9.7:
948
+
resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==}
949
949
hasBin: true
950
950
951
951
boolbase@1.0.0:
···
956
956
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
957
957
hasBin: true
958
958
959
-
caniuse-lite@1.0.30001759:
960
-
resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
959
+
caniuse-lite@1.0.30001760:
960
+
resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==}
961
961
962
962
codemirror@6.0.2:
963
963
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
···
1023
1023
domutils@3.2.2:
1024
1024
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
1025
1025
1026
-
electron-to-chromium@1.5.263:
1027
-
resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==}
1026
+
electron-to-chromium@1.5.267:
1027
+
resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
1028
1028
1029
-
enhanced-resolve@5.18.3:
1030
-
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
1029
+
enhanced-resolve@5.18.4:
1030
+
resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
1031
1031
engines: {node: '>=10.13.0'}
1032
1032
1033
1033
entities@4.5.0:
···
1360
1360
engines: {node: '>=16'}
1361
1361
hasBin: true
1362
1362
1363
-
tailwindcss@4.1.17:
1364
-
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
1363
+
tailwindcss@4.1.18:
1364
+
resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
1365
1365
1366
1366
tapable@2.3.0:
1367
1367
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
···
1391
1391
undici-types@7.16.0:
1392
1392
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
1393
1393
1394
-
update-browserslist-db@1.2.1:
1395
-
resolution: {integrity: sha512-R9NcHbbZ45RoWfTdhn1J9SS7zxNvlddv4YRrHTUaFdtjbmfncfedB45EC9IaqJQ97iAR1GZgOfyRQO+ExIF6EQ==}
1394
+
update-browserslist-db@1.2.2:
1395
+
resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==}
1396
1396
hasBin: true
1397
1397
peerDependencies:
1398
1398
browserslist: '>= 4.21.0'
···
1407
1407
'@testing-library/jest-dom':
1408
1408
optional: true
1409
1409
1410
-
vite@7.2.6:
1411
-
resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==}
1410
+
vite@7.2.7:
1411
+
resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==}
1412
1412
engines: {node: ^20.19.0 || >=22.12.0}
1413
1413
hasBin: true
1414
1414
peerDependencies:
···
1476
1476
dependencies:
1477
1477
'@atcute/lexicons': 1.2.5
1478
1478
1479
-
'@atcute/bluesky@3.2.11':
1479
+
'@atcute/bluesky@3.2.14':
1480
1480
dependencies:
1481
1481
'@atcute/atproto': 3.1.9
1482
1482
'@atcute/lexicons': 1.2.5
···
1485
1485
dependencies:
1486
1486
'@atcute/cbor': 2.2.8
1487
1487
'@atcute/cid': 2.2.6
1488
-
'@atcute/uint8array': 1.0.5
1488
+
'@atcute/uint8array': 1.0.6
1489
1489
'@atcute/varint': 1.0.3
1490
1490
yocto-queue: 1.2.2
1491
1491
···
1493
1493
dependencies:
1494
1494
'@atcute/cbor': 2.2.8
1495
1495
'@atcute/cid': 2.2.6
1496
-
'@atcute/uint8array': 1.0.5
1496
+
'@atcute/uint8array': 1.0.6
1497
1497
'@atcute/varint': 1.0.3
1498
1498
1499
1499
'@atcute/cbor@2.2.8':
1500
1500
dependencies:
1501
1501
'@atcute/cid': 2.2.6
1502
1502
'@atcute/multibase': 1.1.6
1503
-
'@atcute/uint8array': 1.0.5
1503
+
'@atcute/uint8array': 1.0.6
1504
1504
1505
1505
'@atcute/cid@2.2.6':
1506
1506
dependencies:
1507
1507
'@atcute/multibase': 1.1.6
1508
-
'@atcute/uint8array': 1.0.5
1508
+
'@atcute/uint8array': 1.0.6
1509
1509
1510
-
'@atcute/client@4.1.0':
1510
+
'@atcute/client@4.1.1':
1511
1511
dependencies:
1512
1512
'@atcute/identity': 1.1.3
1513
1513
'@atcute/lexicons': 1.2.5
1514
1514
1515
-
'@atcute/crypto@2.2.6':
1515
+
'@atcute/crypto@2.3.0':
1516
1516
dependencies:
1517
1517
'@atcute/multibase': 1.1.6
1518
-
'@atcute/uint8array': 1.0.5
1518
+
'@atcute/uint8array': 1.0.6
1519
1519
'@noble/secp256k1': 3.0.0
1520
1520
1521
1521
'@atcute/did-plc@0.2.0':
1522
1522
dependencies:
1523
1523
'@atcute/cbor': 2.2.8
1524
1524
'@atcute/cid': 2.2.6
1525
-
'@atcute/crypto': 2.2.6
1525
+
'@atcute/crypto': 2.3.0
1526
1526
'@atcute/identity': 1.1.3
1527
1527
'@atcute/lexicons': 1.2.5
1528
1528
'@atcute/multibase': 1.1.6
1529
-
'@atcute/uint8array': 1.0.5
1529
+
'@atcute/uint8array': 1.0.6
1530
1530
'@badrap/valita': 0.4.6
1531
1531
1532
-
'@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.3)':
1532
+
'@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3)':
1533
1533
dependencies:
1534
1534
'@atcute/identity': 1.1.3
1535
1535
'@atcute/lexicons': 1.2.5
···
1541
1541
'@atcute/lexicons': 1.2.5
1542
1542
'@badrap/valita': 0.4.6
1543
1543
1544
-
'@atcute/leaflet@1.0.12':
1544
+
'@atcute/leaflet@1.0.14':
1545
1545
dependencies:
1546
1546
'@atcute/atproto': 3.1.9
1547
1547
'@atcute/lexicons': 1.2.5
1548
1548
1549
-
'@atcute/lexicon-doc@2.0.4':
1549
+
'@atcute/lexicon-doc@2.0.5':
1550
1550
dependencies:
1551
1551
'@atcute/identity': 1.1.3
1552
1552
'@atcute/lexicons': 1.2.5
1553
1553
'@badrap/valita': 0.4.6
1554
1554
1555
-
'@atcute/lexicon-resolver@0.1.5(@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)':
1555
+
'@atcute/lexicon-resolver@0.1.5(@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)':
1556
1556
dependencies:
1557
-
'@atcute/crypto': 2.2.6
1557
+
'@atcute/crypto': 2.3.0
1558
1558
'@atcute/identity': 1.1.3
1559
-
'@atcute/identity-resolver': 1.1.4(@atcute/identity@1.1.3)
1560
-
'@atcute/lexicon-doc': 2.0.4
1559
+
'@atcute/identity-resolver': 1.2.0(@atcute/identity@1.1.3)
1560
+
'@atcute/lexicon-doc': 2.0.5
1561
1561
'@atcute/lexicons': 1.2.5
1562
1562
'@atcute/repo': 0.1.0
1563
1563
'@atcute/util-fetch': 1.0.4
···
1572
1572
dependencies:
1573
1573
'@atcute/cbor': 2.2.8
1574
1574
'@atcute/cid': 2.2.6
1575
-
'@atcute/uint8array': 1.0.5
1575
+
'@atcute/uint8array': 1.0.6
1576
1576
1577
1577
'@atcute/multibase@1.1.6':
1578
1578
dependencies:
1579
-
'@atcute/uint8array': 1.0.5
1579
+
'@atcute/uint8array': 1.0.6
1580
1580
1581
-
'@atcute/oauth-browser-client@2.0.1':
1581
+
'@atcute/oauth-browser-client@2.0.3(@atcute/identity@1.1.3)':
1582
1582
dependencies:
1583
-
'@atcute/client': 4.1.0
1584
-
'@atcute/identity': 1.1.3
1585
-
'@atcute/identity-resolver': 1.1.4(@atcute/identity@1.1.3)
1583
+
'@atcute/client': 4.1.1
1584
+
'@atcute/identity-resolver': 1.2.0(@atcute/identity@1.1.3)
1586
1585
'@atcute/lexicons': 1.2.5
1587
1586
'@atcute/multibase': 1.1.6
1588
-
'@atcute/uint8array': 1.0.5
1587
+
'@atcute/uint8array': 1.0.6
1589
1588
nanoid: 5.1.6
1589
+
transitivePeerDependencies:
1590
+
- '@atcute/identity'
1590
1591
1591
1592
'@atcute/repo@0.1.0':
1592
1593
dependencies:
1593
1594
'@atcute/car': 5.0.0
1594
1595
'@atcute/cbor': 2.2.8
1595
1596
'@atcute/cid': 2.2.6
1596
-
'@atcute/crypto': 2.2.6
1597
+
'@atcute/crypto': 2.3.0
1597
1598
'@atcute/lexicons': 1.2.5
1598
1599
'@atcute/mst': 0.1.0
1599
-
'@atcute/uint8array': 1.0.5
1600
+
'@atcute/uint8array': 1.0.6
1600
1601
1601
-
'@atcute/tangled@1.0.12':
1602
+
'@atcute/tangled@1.0.13':
1602
1603
dependencies:
1603
1604
'@atcute/atproto': 3.1.9
1604
1605
'@atcute/lexicons': 1.2.5
1605
1606
1606
1607
'@atcute/tid@1.0.3': {}
1607
1608
1608
-
'@atcute/uint8array@1.0.5': {}
1609
+
'@atcute/uint8array@1.0.6': {}
1609
1610
1610
1611
'@atcute/util-fetch@1.0.4':
1611
1612
dependencies:
···
1730
1731
dependencies:
1731
1732
'@codemirror/language': 6.11.3
1732
1733
'@codemirror/state': 6.5.2
1733
-
'@codemirror/view': 6.38.8
1734
+
'@codemirror/view': 6.39.4
1734
1735
'@lezer/common': 1.4.0
1735
1736
1736
1737
'@codemirror/commands@6.10.0':
1737
1738
dependencies:
1738
1739
'@codemirror/language': 6.11.3
1739
1740
'@codemirror/state': 6.5.2
1740
-
'@codemirror/view': 6.38.8
1741
+
'@codemirror/view': 6.39.4
1741
1742
'@lezer/common': 1.4.0
1742
1743
1743
1744
'@codemirror/lang-json@6.0.2':
···
1748
1749
'@codemirror/language@6.11.3':
1749
1750
dependencies:
1750
1751
'@codemirror/state': 6.5.2
1751
-
'@codemirror/view': 6.38.8
1752
+
'@codemirror/view': 6.39.4
1752
1753
'@lezer/common': 1.4.0
1753
1754
'@lezer/highlight': 1.2.3
1754
-
'@lezer/lr': 1.4.4
1755
+
'@lezer/lr': 1.4.5
1755
1756
style-mod: 4.1.3
1756
1757
1757
1758
'@codemirror/lint@6.9.2':
1758
1759
dependencies:
1759
1760
'@codemirror/state': 6.5.2
1760
-
'@codemirror/view': 6.38.8
1761
+
'@codemirror/view': 6.39.4
1761
1762
crelt: 1.0.6
1762
1763
1763
1764
'@codemirror/search@6.5.11':
1764
1765
dependencies:
1765
1766
'@codemirror/state': 6.5.2
1766
-
'@codemirror/view': 6.38.8
1767
+
'@codemirror/view': 6.39.4
1767
1768
crelt: 1.0.6
1768
1769
1769
1770
'@codemirror/state@6.5.2':
1770
1771
dependencies:
1771
1772
'@marijn/find-cluster-break': 1.0.2
1772
1773
1773
-
'@codemirror/view@6.38.8':
1774
+
'@codemirror/view@6.39.4':
1774
1775
dependencies:
1775
1776
'@codemirror/state': 6.5.2
1776
1777
crelt: 1.0.6
···
1931
1932
'@esbuild/win32-x64@0.25.12':
1932
1933
optional: true
1933
1934
1934
-
'@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3)':
1935
+
'@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)':
1935
1936
dependencies:
1936
1937
'@codemirror/language': 6.11.3
1937
1938
'@codemirror/state': 6.5.2
1938
-
'@codemirror/view': 6.38.8
1939
+
'@codemirror/view': 6.39.4
1939
1940
'@lezer/highlight': 1.2.3
1940
1941
1941
-
'@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3)':
1942
+
'@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)':
1942
1943
dependencies:
1943
1944
'@codemirror/language': 6.11.3
1944
1945
'@codemirror/state': 6.5.2
1945
-
'@codemirror/view': 6.38.8
1946
+
'@codemirror/view': 6.39.4
1946
1947
'@lezer/highlight': 1.2.3
1947
1948
1948
-
'@iconify-json/lucide@1.2.77':
1949
+
'@iconify-json/lucide@1.2.81':
1949
1950
dependencies:
1950
1951
'@iconify/types': 2.0.0
1951
1952
1952
-
'@iconify/tailwind4@1.2.0(tailwindcss@4.1.17)':
1953
+
'@iconify/tailwind4@1.2.0(tailwindcss@4.1.18)':
1953
1954
dependencies:
1954
1955
'@iconify/tools': 5.0.0
1955
1956
'@iconify/types': 2.0.0
1956
1957
'@iconify/utils': 3.1.0
1957
-
tailwindcss: 4.1.17
1958
+
tailwindcss: 4.1.18
1958
1959
1959
1960
'@iconify/tools@5.0.0':
1960
1961
dependencies:
···
2005
2006
dependencies:
2006
2007
'@lezer/common': 1.4.0
2007
2008
'@lezer/highlight': 1.2.3
2008
-
'@lezer/lr': 1.4.4
2009
+
'@lezer/lr': 1.4.5
2009
2010
2010
-
'@lezer/lr@1.4.4':
2011
+
'@lezer/lr@1.4.5':
2011
2012
dependencies:
2012
2013
'@lezer/common': 1.4.0
2013
2014
···
2097
2098
2098
2099
'@standard-schema/spec@1.0.0': {}
2099
2100
2100
-
'@tailwindcss/node@4.1.17':
2101
+
'@tailwindcss/node@4.1.18':
2101
2102
dependencies:
2102
2103
'@jridgewell/remapping': 2.3.5
2103
-
enhanced-resolve: 5.18.3
2104
+
enhanced-resolve: 5.18.4
2104
2105
jiti: 2.6.1
2105
2106
lightningcss: 1.30.2
2106
2107
magic-string: 0.30.21
2107
2108
source-map-js: 1.2.1
2108
-
tailwindcss: 4.1.17
2109
+
tailwindcss: 4.1.18
2109
2110
2110
-
'@tailwindcss/oxide-android-arm64@4.1.17':
2111
+
'@tailwindcss/oxide-android-arm64@4.1.18':
2111
2112
optional: true
2112
2113
2113
-
'@tailwindcss/oxide-darwin-arm64@4.1.17':
2114
+
'@tailwindcss/oxide-darwin-arm64@4.1.18':
2114
2115
optional: true
2115
2116
2116
-
'@tailwindcss/oxide-darwin-x64@4.1.17':
2117
+
'@tailwindcss/oxide-darwin-x64@4.1.18':
2117
2118
optional: true
2118
2119
2119
-
'@tailwindcss/oxide-freebsd-x64@4.1.17':
2120
+
'@tailwindcss/oxide-freebsd-x64@4.1.18':
2120
2121
optional: true
2121
2122
2122
-
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
2123
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
2123
2124
optional: true
2124
2125
2125
-
'@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
2126
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
2126
2127
optional: true
2127
2128
2128
-
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
2129
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
2129
2130
optional: true
2130
2131
2131
-
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
2132
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
2132
2133
optional: true
2133
2134
2134
-
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
2135
+
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
2135
2136
optional: true
2136
2137
2137
-
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
2138
+
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
2138
2139
optional: true
2139
2140
2140
-
'@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
2141
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
2141
2142
optional: true
2142
2143
2143
-
'@tailwindcss/oxide-win32-x64-msvc@4.1.17':
2144
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.18':
2144
2145
optional: true
2145
2146
2146
-
'@tailwindcss/oxide@4.1.17':
2147
+
'@tailwindcss/oxide@4.1.18':
2147
2148
optionalDependencies:
2148
-
'@tailwindcss/oxide-android-arm64': 4.1.17
2149
-
'@tailwindcss/oxide-darwin-arm64': 4.1.17
2150
-
'@tailwindcss/oxide-darwin-x64': 4.1.17
2151
-
'@tailwindcss/oxide-freebsd-x64': 4.1.17
2152
-
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17
2153
-
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.17
2154
-
'@tailwindcss/oxide-linux-arm64-musl': 4.1.17
2155
-
'@tailwindcss/oxide-linux-x64-gnu': 4.1.17
2156
-
'@tailwindcss/oxide-linux-x64-musl': 4.1.17
2157
-
'@tailwindcss/oxide-wasm32-wasi': 4.1.17
2158
-
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
2159
-
'@tailwindcss/oxide-win32-x64-msvc': 4.1.17
2149
+
'@tailwindcss/oxide-android-arm64': 4.1.18
2150
+
'@tailwindcss/oxide-darwin-arm64': 4.1.18
2151
+
'@tailwindcss/oxide-darwin-x64': 4.1.18
2152
+
'@tailwindcss/oxide-freebsd-x64': 4.1.18
2153
+
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
2154
+
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
2155
+
'@tailwindcss/oxide-linux-arm64-musl': 4.1.18
2156
+
'@tailwindcss/oxide-linux-x64-gnu': 4.1.18
2157
+
'@tailwindcss/oxide-linux-x64-musl': 4.1.18
2158
+
'@tailwindcss/oxide-wasm32-wasi': 4.1.18
2159
+
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
2160
+
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
2160
2161
2161
-
'@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))':
2162
+
'@tailwindcss/vite@4.1.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))':
2162
2163
dependencies:
2163
-
'@tailwindcss/node': 4.1.17
2164
-
'@tailwindcss/oxide': 4.1.17
2165
-
tailwindcss: 4.1.17
2166
-
vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
2164
+
'@tailwindcss/node': 4.1.18
2165
+
'@tailwindcss/oxide': 4.1.18
2166
+
tailwindcss: 4.1.18
2167
+
vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
2167
2168
2168
2169
'@types/babel__core@7.20.5':
2169
2170
dependencies:
···
2211
2212
optionalDependencies:
2212
2213
solid-js: 1.9.10
2213
2214
2214
-
baseline-browser-mapping@2.9.0: {}
2215
+
baseline-browser-mapping@2.9.7: {}
2215
2216
2216
2217
boolbase@1.0.0: {}
2217
2218
2218
2219
browserslist@4.28.1:
2219
2220
dependencies:
2220
-
baseline-browser-mapping: 2.9.0
2221
-
caniuse-lite: 1.0.30001759
2222
-
electron-to-chromium: 1.5.263
2221
+
baseline-browser-mapping: 2.9.7
2222
+
caniuse-lite: 1.0.30001760
2223
+
electron-to-chromium: 1.5.267
2223
2224
node-releases: 2.0.27
2224
-
update-browserslist-db: 1.2.1(browserslist@4.28.1)
2225
+
update-browserslist-db: 1.2.2(browserslist@4.28.1)
2225
2226
2226
-
caniuse-lite@1.0.30001759: {}
2227
+
caniuse-lite@1.0.30001760: {}
2227
2228
2228
2229
codemirror@6.0.2:
2229
2230
dependencies:
···
2233
2234
'@codemirror/lint': 6.9.2
2234
2235
'@codemirror/search': 6.5.11
2235
2236
'@codemirror/state': 6.5.2
2236
-
'@codemirror/view': 6.38.8
2237
+
'@codemirror/view': 6.39.4
2237
2238
2238
2239
commander@11.1.0: {}
2239
2240
···
2293
2294
domelementtype: 2.3.0
2294
2295
domhandler: 5.0.3
2295
2296
2296
-
electron-to-chromium@1.5.263: {}
2297
+
electron-to-chromium@1.5.267: {}
2297
2298
2298
-
enhanced-resolve@5.18.3:
2299
+
enhanced-resolve@5.18.4:
2299
2300
dependencies:
2300
2301
graceful-fs: 4.2.11
2301
2302
tapable: 2.3.0
···
2590
2591
picocolors: 1.1.1
2591
2592
sax: 1.4.3
2592
2593
2593
-
tailwindcss@4.1.17: {}
2594
+
tailwindcss@4.1.18: {}
2594
2595
2595
2596
tapable@2.3.0: {}
2596
2597
···
2616
2617
undici-types@7.16.0:
2617
2618
optional: true
2618
2619
2619
-
update-browserslist-db@1.2.1(browserslist@4.28.1):
2620
+
update-browserslist-db@1.2.2(browserslist@4.28.1):
2620
2621
dependencies:
2621
2622
browserslist: 4.28.1
2622
2623
escalade: 3.2.0
2623
2624
picocolors: 1.1.1
2624
2625
2625
-
vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
2626
+
vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
2626
2627
dependencies:
2627
2628
'@babel/core': 7.28.5
2628
2629
'@types/babel__core': 7.20.5
···
2630
2631
merge-anything: 5.1.7
2631
2632
solid-js: 1.9.10
2632
2633
solid-refresh: 0.6.3(solid-js@1.9.10)
2633
-
vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
2634
-
vitefu: 1.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
2634
+
vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
2635
+
vitefu: 1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))
2635
2636
transitivePeerDependencies:
2636
2637
- supports-color
2637
2638
2638
-
vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2):
2639
+
vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2):
2639
2640
dependencies:
2640
2641
esbuild: 0.25.12
2641
2642
fdir: 6.5.0(picomatch@4.0.3)
···
2650
2651
lightningcss: 1.30.2
2651
2652
tsx: 4.19.2
2652
2653
2653
-
vitefu@1.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
2654
+
vitefu@1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)):
2654
2655
optionalDependencies:
2655
-
vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
2656
+
vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)
2656
2657
2657
2658
w3c-keyname@2.2.8: {}
2658
2659
public/favicon.ico
public/favicon.ico
This is a binary file and will not be displayed.
public/fonts/Figtree[wght].woff2
public/fonts/Figtree[wght].woff2
This is a binary file and will not be displayed.
+5
-1
src/auth/account.tsx
+5
-1
src/auth/account.tsx
···
41
41
return (
42
42
<MenuProvider>
43
43
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-md p-2">
44
-
<NavMenu href={`/at://${props.did}`} label="Go to repo" icon="lucide--user-round" />
44
+
<NavMenu
45
+
href={`/at://${props.did}`}
46
+
label={agent()?.sub === props.did ? "Go to repo (g)" : "Go to repo"}
47
+
icon="lucide--user-round"
48
+
/>
45
49
<ActionMenu
46
50
icon="lucide--settings"
47
51
label="Edit permissions"
+2
-2
src/auth/session-manager.ts
+2
-2
src/auth/session-manager.ts
···
1
-
import { Client, CredentialManager } from "@atcute/client";
1
+
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { Did } from "@atcute/lexicons";
3
3
import {
4
4
finalizeAuthorization,
···
20
20
21
21
export const getAvatar = async (did: Did): Promise<string | undefined> => {
22
22
const rpc = new Client({
23
-
handler: new CredentialManager({ service: "https://public.api.bsky.app" }),
23
+
handler: simpleFetchHandler({ service: "https://public.api.bsky.app" }),
24
24
});
25
25
const res = await rpc.get("app.bsky.actor.getProfile", { params: { actor: did } });
26
26
if (res.ok) {
+109
src/components/create/file-upload.tsx
+109
src/components/create/file-upload.tsx
···
1
+
import { Client } from "@atcute/client";
2
+
import { remove } from "@mary/exif-rm";
3
+
import { createSignal, onCleanup, Show } from "solid-js";
4
+
import { agent } from "../../auth/state";
5
+
import { Button } from "../button.jsx";
6
+
import { TextInput } from "../text-input.jsx";
7
+
import { editorInstance } from "./state";
8
+
9
+
export const FileUpload = (props: {
10
+
file: File;
11
+
blobInput: HTMLInputElement;
12
+
onClose: () => void;
13
+
}) => {
14
+
const [uploading, setUploading] = createSignal(false);
15
+
const [error, setError] = createSignal("");
16
+
17
+
onCleanup(() => (props.blobInput.value = ""));
18
+
19
+
const formatFileSize = (bytes: number) => {
20
+
if (bytes === 0) return "0 Bytes";
21
+
const k = 1024;
22
+
const sizes = ["Bytes", "KB", "MB", "GB"];
23
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
24
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
25
+
};
26
+
27
+
const uploadBlob = async () => {
28
+
let blob: Blob;
29
+
30
+
const mimetype = (document.getElementById("mimetype") as HTMLInputElement)?.value;
31
+
(document.getElementById("mimetype") as HTMLInputElement).value = "";
32
+
if (mimetype) blob = new Blob([props.file], { type: mimetype });
33
+
else blob = props.file;
34
+
35
+
if ((document.getElementById("exif-rm") as HTMLInputElement).checked) {
36
+
const exifRemoved = remove(new Uint8Array(await blob.arrayBuffer()));
37
+
if (exifRemoved !== null) blob = new Blob([exifRemoved], { type: blob.type });
38
+
}
39
+
40
+
const rpc = new Client({ handler: agent()! });
41
+
setUploading(true);
42
+
const res = await rpc.post("com.atproto.repo.uploadBlob", {
43
+
input: blob,
44
+
});
45
+
setUploading(false);
46
+
if (!res.ok) {
47
+
setError(res.data.error);
48
+
return;
49
+
}
50
+
editorInstance.view.dispatch({
51
+
changes: {
52
+
from: editorInstance.view.state.selection.main.head,
53
+
insert: JSON.stringify(res.data.blob, null, 2),
54
+
},
55
+
});
56
+
props.onClose();
57
+
};
58
+
59
+
return (
60
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
61
+
<h2 class="mb-2 font-semibold">Upload blob</h2>
62
+
<div class="flex flex-col gap-2 text-sm">
63
+
<div class="flex flex-col gap-1">
64
+
<p class="flex gap-1">
65
+
<span class="truncate">{props.file.name}</span>
66
+
<span class="shrink-0 text-neutral-600 dark:text-neutral-400">
67
+
({formatFileSize(props.file.size)})
68
+
</span>
69
+
</p>
70
+
</div>
71
+
<div class="flex items-center gap-x-2">
72
+
<label for="mimetype" class="shrink-0 select-none">
73
+
MIME type
74
+
</label>
75
+
<TextInput id="mimetype" placeholder={props.file.type} />
76
+
</div>
77
+
<div class="flex items-center gap-1">
78
+
<input id="exif-rm" type="checkbox" checked />
79
+
<label for="exif-rm" class="select-none">
80
+
Remove EXIF data
81
+
</label>
82
+
</div>
83
+
<p class="text-xs text-neutral-600 dark:text-neutral-400">
84
+
Metadata will be pasted after the cursor
85
+
</p>
86
+
<Show when={error()}>
87
+
<span class="text-red-500 dark:text-red-400">Error: {error()}</span>
88
+
</Show>
89
+
<div class="flex justify-between gap-2">
90
+
<Button onClick={props.onClose}>Cancel</Button>
91
+
<Show when={uploading()}>
92
+
<div class="flex items-center gap-1">
93
+
<span class="iconify lucide--loader-circle animate-spin"></span>
94
+
<span>Uploading</span>
95
+
</div>
96
+
</Show>
97
+
<Show when={!uploading()}>
98
+
<Button
99
+
onClick={uploadBlob}
100
+
class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400"
101
+
>
102
+
Upload
103
+
</Button>
104
+
</Show>
105
+
</div>
106
+
</div>
107
+
</div>
108
+
);
109
+
};
+87
src/components/create/handle-input.tsx
+87
src/components/create/handle-input.tsx
···
1
+
import { Handle } from "@atcute/lexicons";
2
+
import { createSignal, Show } from "solid-js";
3
+
import { resolveHandle } from "../../utils/api";
4
+
import { Button } from "../button.jsx";
5
+
import { TextInput } from "../text-input.jsx";
6
+
import { editorInstance } from "./state";
7
+
8
+
export const HandleInput = (props: { onClose: () => void }) => {
9
+
const [resolving, setResolving] = createSignal(false);
10
+
const [error, setError] = createSignal("");
11
+
let handleFormRef!: HTMLFormElement;
12
+
13
+
const resolveDid = async (e: SubmitEvent) => {
14
+
e.preventDefault();
15
+
const formData = new FormData(handleFormRef);
16
+
const handleValue = formData.get("handle")?.toString().trim();
17
+
18
+
if (!handleValue) {
19
+
setError("Please enter a handle");
20
+
return;
21
+
}
22
+
23
+
setResolving(true);
24
+
setError("");
25
+
try {
26
+
const did = await resolveHandle(handleValue as Handle);
27
+
editorInstance.view.dispatch({
28
+
changes: {
29
+
from: editorInstance.view.state.selection.main.head,
30
+
insert: `"${did}"`,
31
+
},
32
+
});
33
+
props.onClose();
34
+
handleFormRef.reset();
35
+
} catch (err: any) {
36
+
setError(err.message || "Failed to resolve handle");
37
+
} finally {
38
+
setResolving(false);
39
+
}
40
+
};
41
+
42
+
return (
43
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
44
+
<h2 class="mb-2 font-semibold">Insert DID from handle</h2>
45
+
<form ref={handleFormRef} onSubmit={resolveDid} class="flex flex-col gap-2 text-sm">
46
+
<div class="flex flex-col gap-1">
47
+
<label for="handle-input" class="select-none">
48
+
Handle
49
+
</label>
50
+
<TextInput id="handle-input" name="handle" placeholder="user.bsky.social" />
51
+
</div>
52
+
<p class="text-xs text-neutral-600 dark:text-neutral-400">
53
+
DID will be pasted after the cursor
54
+
</p>
55
+
<Show when={error()}>
56
+
<span class="text-red-500 dark:text-red-400">Error: {error()}</span>
57
+
</Show>
58
+
<div class="flex justify-between gap-2">
59
+
<Button
60
+
type="button"
61
+
onClick={() => {
62
+
props.onClose();
63
+
handleFormRef.reset();
64
+
setError("");
65
+
}}
66
+
>
67
+
Cancel
68
+
</Button>
69
+
<Show when={resolving()}>
70
+
<div class="flex items-center gap-1">
71
+
<span class="iconify lucide--loader-circle animate-spin"></span>
72
+
<span>Resolving</span>
73
+
</div>
74
+
</Show>
75
+
<Show when={!resolving()}>
76
+
<Button
77
+
type="submit"
78
+
class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400"
79
+
>
80
+
Insert
81
+
</Button>
82
+
</Show>
83
+
</div>
84
+
</form>
85
+
</div>
86
+
);
87
+
};
+479
src/components/create/index.tsx
+479
src/components/create/index.tsx
···
1
+
import { Client } from "@atcute/client";
2
+
import { Did } from "@atcute/lexicons";
3
+
import { isNsid, isRecordKey } from "@atcute/lexicons/syntax";
4
+
import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client";
5
+
import { useNavigate, useParams } from "@solidjs/router";
6
+
import {
7
+
createEffect,
8
+
createSignal,
9
+
For,
10
+
lazy,
11
+
onCleanup,
12
+
onMount,
13
+
Show,
14
+
Suspense,
15
+
} from "solid-js";
16
+
import { hasUserScope } from "../../auth/scope-utils";
17
+
import { agent, sessions } from "../../auth/state";
18
+
import { Button } from "../button.jsx";
19
+
import { Modal } from "../modal.jsx";
20
+
import { addNotification, removeNotification } from "../notification.jsx";
21
+
import { TextInput } from "../text-input.jsx";
22
+
import Tooltip from "../tooltip.jsx";
23
+
import { FileUpload } from "./file-upload";
24
+
import { HandleInput } from "./handle-input";
25
+
import { MenuItem } from "./menu-item";
26
+
import { editorInstance, placeholder, setPlaceholder } from "./state";
27
+
28
+
const Editor = lazy(() => import("../editor.jsx").then((m) => ({ default: m.Editor })));
29
+
30
+
export { editorInstance, placeholder, setPlaceholder };
31
+
32
+
export const RecordEditor = (props: { create: boolean; record?: any; refetch?: any }) => {
33
+
const navigate = useNavigate();
34
+
const params = useParams();
35
+
const [openDialog, setOpenDialog] = createSignal(false);
36
+
const [notice, setNotice] = createSignal("");
37
+
const [openUpload, setOpenUpload] = createSignal(false);
38
+
const [openInsertMenu, setOpenInsertMenu] = createSignal(false);
39
+
const [openHandleDialog, setOpenHandleDialog] = createSignal(false);
40
+
const [validate, setValidate] = createSignal<boolean | undefined>(undefined);
41
+
const [isMaximized, setIsMaximized] = createSignal(false);
42
+
const [isMinimized, setIsMinimized] = createSignal(false);
43
+
const [collectionError, setCollectionError] = createSignal("");
44
+
const [rkeyError, setRkeyError] = createSignal("");
45
+
let blobInput!: HTMLInputElement;
46
+
let formRef!: HTMLFormElement;
47
+
let insertMenuRef!: HTMLDivElement;
48
+
49
+
createEffect(() => {
50
+
if (openInsertMenu()) {
51
+
const handleClickOutside = (e: MouseEvent) => {
52
+
if (insertMenuRef && !insertMenuRef.contains(e.target as Node)) {
53
+
setOpenInsertMenu(false);
54
+
}
55
+
};
56
+
document.addEventListener("mousedown", handleClickOutside);
57
+
onCleanup(() => document.removeEventListener("mousedown", handleClickOutside));
58
+
}
59
+
});
60
+
61
+
onMount(() => {
62
+
const keyEvent = (ev: KeyboardEvent) => {
63
+
if (ev.target instanceof HTMLInputElement || ev.target instanceof HTMLTextAreaElement) return;
64
+
if ((ev.target as HTMLElement).closest("[data-modal]")) return;
65
+
66
+
const key = props.create ? "n" : "e";
67
+
if (ev.key === key) {
68
+
ev.preventDefault();
69
+
70
+
if (openDialog() && isMinimized()) {
71
+
setIsMinimized(false);
72
+
} else if (!openDialog() && !document.querySelector("[data-modal]")) {
73
+
setOpenDialog(true);
74
+
}
75
+
}
76
+
};
77
+
78
+
window.addEventListener("keydown", keyEvent);
79
+
onCleanup(() => window.removeEventListener("keydown", keyEvent));
80
+
});
81
+
82
+
const defaultPlaceholder = () => {
83
+
return {
84
+
$type: "app.bsky.feed.post",
85
+
text: "This post was sent from PDSls",
86
+
embed: {
87
+
$type: "app.bsky.embed.external",
88
+
external: {
89
+
uri: "https://pdsls.dev",
90
+
title: "PDSls",
91
+
description: "Browse the public data on atproto",
92
+
},
93
+
},
94
+
langs: ["en"],
95
+
createdAt: new Date().toISOString(),
96
+
};
97
+
};
98
+
99
+
const getValidateIcon = () => {
100
+
return (
101
+
validate() === true ? "lucide--circle-check"
102
+
: validate() === false ? "lucide--circle-x"
103
+
: "lucide--circle"
104
+
);
105
+
};
106
+
107
+
const getValidateLabel = () => {
108
+
return (
109
+
validate() === true ? "True"
110
+
: validate() === false ? "False"
111
+
: "Unset"
112
+
);
113
+
};
114
+
115
+
createEffect(() => {
116
+
if (openDialog()) {
117
+
setValidate(undefined);
118
+
setCollectionError("");
119
+
setRkeyError("");
120
+
}
121
+
});
122
+
123
+
const createRecord = async (formData: FormData) => {
124
+
const repo = formData.get("repo")?.toString();
125
+
if (!repo) return;
126
+
const rpc = new Client({ handler: new OAuthUserAgent(await getSession(repo as Did)) });
127
+
const collection = formData.get("collection");
128
+
const rkey = formData.get("rkey");
129
+
let record: any;
130
+
try {
131
+
record = JSON.parse(editorInstance.view.state.doc.toString());
132
+
} catch (e: any) {
133
+
setNotice(e.message);
134
+
return;
135
+
}
136
+
const res = await rpc.post("com.atproto.repo.createRecord", {
137
+
input: {
138
+
repo: repo as Did,
139
+
collection: collection ? collection.toString() : record.$type,
140
+
rkey: rkey?.toString().length ? rkey?.toString() : undefined,
141
+
record: record,
142
+
validate: validate(),
143
+
},
144
+
});
145
+
if (!res.ok) {
146
+
setNotice(`${res.data.error}: ${res.data.message}`);
147
+
return;
148
+
}
149
+
setOpenDialog(false);
150
+
const id = addNotification({
151
+
message: "Record created",
152
+
type: "success",
153
+
});
154
+
setTimeout(() => removeNotification(id), 3000);
155
+
navigate(`/${res.data.uri}`);
156
+
};
157
+
158
+
const editRecord = async (recreate?: boolean) => {
159
+
const record = editorInstance.view.state.doc.toString();
160
+
if (!record) return;
161
+
const rpc = new Client({ handler: agent()! });
162
+
try {
163
+
const editedRecord = JSON.parse(record);
164
+
if (recreate) {
165
+
const res = await rpc.post("com.atproto.repo.applyWrites", {
166
+
input: {
167
+
repo: agent()!.sub,
168
+
validate: validate(),
169
+
writes: [
170
+
{
171
+
collection: params.collection as `${string}.${string}.${string}`,
172
+
rkey: params.rkey!,
173
+
$type: "com.atproto.repo.applyWrites#delete",
174
+
},
175
+
{
176
+
collection: params.collection as `${string}.${string}.${string}`,
177
+
rkey: params.rkey,
178
+
$type: "com.atproto.repo.applyWrites#create",
179
+
value: editedRecord,
180
+
},
181
+
],
182
+
},
183
+
});
184
+
if (!res.ok) {
185
+
setNotice(`${res.data.error}: ${res.data.message}`);
186
+
return;
187
+
}
188
+
} else {
189
+
const res = await rpc.post("com.atproto.repo.applyWrites", {
190
+
input: {
191
+
repo: agent()!.sub,
192
+
validate: validate(),
193
+
writes: [
194
+
{
195
+
collection: params.collection as `${string}.${string}.${string}`,
196
+
rkey: params.rkey!,
197
+
$type: "com.atproto.repo.applyWrites#update",
198
+
value: editedRecord,
199
+
},
200
+
],
201
+
},
202
+
});
203
+
if (!res.ok) {
204
+
setNotice(`${res.data.error}: ${res.data.message}`);
205
+
return;
206
+
}
207
+
}
208
+
setOpenDialog(false);
209
+
const id = addNotification({
210
+
message: "Record edited",
211
+
type: "success",
212
+
});
213
+
setTimeout(() => removeNotification(id), 3000);
214
+
props.refetch();
215
+
} catch (err: any) {
216
+
setNotice(err.message);
217
+
}
218
+
};
219
+
220
+
const insertTimestamp = () => {
221
+
const timestamp = new Date().toISOString();
222
+
editorInstance.view.dispatch({
223
+
changes: {
224
+
from: editorInstance.view.state.selection.main.head,
225
+
insert: `"${timestamp}"`,
226
+
},
227
+
});
228
+
setOpenInsertMenu(false);
229
+
};
230
+
231
+
const insertDidFromHandle = () => {
232
+
setOpenInsertMenu(false);
233
+
setOpenHandleDialog(true);
234
+
};
235
+
236
+
return (
237
+
<>
238
+
<Modal
239
+
open={openDialog()}
240
+
onClose={() => setOpenDialog(false)}
241
+
closeOnClick={false}
242
+
nonBlocking={isMinimized()}
243
+
>
244
+
<div
245
+
style="transform: translateX(-50%) translateZ(0);"
246
+
classList={{
247
+
"dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto absolute top-18 left-1/2 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-all duration-200 dark:border-neutral-700 starting:opacity-0": true,
248
+
"w-[calc(100%-1rem)] max-w-3xl h-[65vh]": !isMaximized(),
249
+
"w-[calc(100%-1rem)] max-w-7xl h-[85vh]": isMaximized(),
250
+
hidden: isMinimized(),
251
+
}}
252
+
>
253
+
<div class="mb-2 flex w-full justify-between text-base">
254
+
<div class="flex items-center gap-2">
255
+
<span class="font-semibold select-none">
256
+
{props.create ? "Creating" : "Editing"} record
257
+
</span>
258
+
</div>
259
+
<div class="flex items-center gap-1">
260
+
<button
261
+
type="button"
262
+
onclick={() => setIsMinimized(true)}
263
+
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
264
+
>
265
+
<span class="iconify lucide--minus"></span>
266
+
</button>
267
+
<button
268
+
type="button"
269
+
onclick={() => setIsMaximized(!isMaximized())}
270
+
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
271
+
>
272
+
<span
273
+
class={`iconify ${isMaximized() ? "lucide--minimize-2" : "lucide--maximize-2"}`}
274
+
></span>
275
+
</button>
276
+
<button
277
+
id="close"
278
+
onclick={() => setOpenDialog(false)}
279
+
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
280
+
>
281
+
<span class="iconify lucide--x"></span>
282
+
</button>
283
+
</div>
284
+
</div>
285
+
<form ref={formRef} class="flex min-h-0 flex-1 flex-col gap-y-2">
286
+
<Show when={props.create}>
287
+
<div class="flex flex-wrap items-center gap-1 text-sm">
288
+
<span>at://</span>
289
+
<select
290
+
class="dark:bg-dark-100 max-w-40 truncate rounded-lg border-[0.5px] border-neutral-300 bg-white px-1 py-1 select-none focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
291
+
name="repo"
292
+
id="repo"
293
+
>
294
+
<For each={Object.keys(sessions)}>
295
+
{(session) => (
296
+
<option value={session} selected={session === agent()?.sub}>
297
+
{sessions[session].handle ?? session}
298
+
</option>
299
+
)}
300
+
</For>
301
+
</select>
302
+
<span>/</span>
303
+
<TextInput
304
+
id="collection"
305
+
name="collection"
306
+
placeholder="Collection (default: $type)"
307
+
class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`}
308
+
onInput={(e) => {
309
+
const value = e.currentTarget.value;
310
+
if (!value || isNsid(value)) setCollectionError("");
311
+
else
312
+
setCollectionError(
313
+
"Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)",
314
+
);
315
+
}}
316
+
/>
317
+
<span>/</span>
318
+
<TextInput
319
+
id="rkey"
320
+
name="rkey"
321
+
placeholder="Record key (default: TID)"
322
+
class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`}
323
+
onInput={(e) => {
324
+
const value = e.currentTarget.value;
325
+
if (!value || isRecordKey(value)) setRkeyError("");
326
+
else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -");
327
+
}}
328
+
/>
329
+
</div>
330
+
<Show when={collectionError() || rkeyError()}>
331
+
<div class="text-xs text-red-500 dark:text-red-400">
332
+
<div>{collectionError()}</div>
333
+
<div>{rkeyError()}</div>
334
+
</div>
335
+
</Show>
336
+
</Show>
337
+
<div class="min-h-0 flex-1">
338
+
<Suspense
339
+
fallback={
340
+
<div class="flex h-full items-center justify-center">
341
+
<span class="iconify lucide--loader-circle animate-spin text-xl"></span>
342
+
</div>
343
+
}
344
+
>
345
+
<Editor
346
+
content={JSON.stringify(
347
+
!props.create ? props.record
348
+
: params.rkey ? placeholder()
349
+
: defaultPlaceholder(),
350
+
null,
351
+
2,
352
+
)}
353
+
/>
354
+
</Suspense>
355
+
</div>
356
+
<div class="flex flex-col gap-2">
357
+
<Show when={notice()}>
358
+
<div class="text-sm text-red-500 dark:text-red-400">{notice()}</div>
359
+
</Show>
360
+
<div class="flex justify-between gap-2">
361
+
<div class="relative" ref={insertMenuRef}>
362
+
<button
363
+
type="button"
364
+
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 flex w-fit rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 text-base shadow-xs hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
365
+
onClick={() => setOpenInsertMenu(!openInsertMenu())}
366
+
>
367
+
<span class="iconify lucide--plus select-none"></span>
368
+
</button>
369
+
<Show when={openInsertMenu()}>
370
+
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute bottom-full left-0 z-10 mb-1 flex w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 shadow-md dark:border-neutral-700">
371
+
<MenuItem
372
+
icon="lucide--id-card"
373
+
label="Insert DID"
374
+
onClick={insertDidFromHandle}
375
+
/>
376
+
<MenuItem
377
+
icon="lucide--clock"
378
+
label="Insert timestamp"
379
+
onClick={insertTimestamp}
380
+
/>
381
+
<Show when={hasUserScope("blob")}>
382
+
<MenuItem
383
+
icon="lucide--upload"
384
+
label="Upload blob"
385
+
onClick={() => {
386
+
setOpenInsertMenu(false);
387
+
blobInput.click();
388
+
}}
389
+
/>
390
+
</Show>
391
+
</div>
392
+
</Show>
393
+
<input
394
+
type="file"
395
+
id="blob"
396
+
class="sr-only"
397
+
ref={blobInput}
398
+
onChange={(e) => {
399
+
if (e.target.files !== null) setOpenUpload(true);
400
+
}}
401
+
/>
402
+
</div>
403
+
<Modal
404
+
open={openUpload()}
405
+
onClose={() => setOpenUpload(false)}
406
+
closeOnClick={false}
407
+
>
408
+
<FileUpload
409
+
file={blobInput.files![0]}
410
+
blobInput={blobInput}
411
+
onClose={() => setOpenUpload(false)}
412
+
/>
413
+
</Modal>
414
+
<Modal
415
+
open={openHandleDialog()}
416
+
onClose={() => setOpenHandleDialog(false)}
417
+
closeOnClick={false}
418
+
>
419
+
<HandleInput onClose={() => setOpenHandleDialog(false)} />
420
+
</Modal>
421
+
<div class="flex items-center justify-end gap-2">
422
+
<button
423
+
type="button"
424
+
class="flex items-center gap-1 rounded-sm p-1.5 text-xs hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
425
+
onClick={() =>
426
+
setValidate(
427
+
validate() === true ? false
428
+
: validate() === false ? undefined
429
+
: true,
430
+
)
431
+
}
432
+
>
433
+
<Tooltip text={getValidateLabel()}>
434
+
<span class={`iconify ${getValidateIcon()}`}></span>
435
+
</Tooltip>
436
+
<span>Validate</span>
437
+
</button>
438
+
<Show when={!props.create && hasUserScope("create") && hasUserScope("delete")}>
439
+
<Button onClick={() => editRecord(true)}>Recreate</Button>
440
+
</Show>
441
+
<Button
442
+
onClick={() =>
443
+
props.create ? createRecord(new FormData(formRef)) : editRecord()
444
+
}
445
+
>
446
+
{props.create ? "Create" : "Edit"}
447
+
</Button>
448
+
</div>
449
+
</div>
450
+
</div>
451
+
</form>
452
+
</div>
453
+
</Modal>
454
+
<Show when={isMinimized() && openDialog()}>
455
+
<button
456
+
class="dark:bg-dark-300 dark:hover:bg-dark-200 dark:active:bg-dark-100 fixed right-4 bottom-4 z-30 flex items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-3 py-2 shadow-md hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700"
457
+
onclick={() => setIsMinimized(false)}
458
+
>
459
+
<span class="iconify lucide--square-pen text-lg"></span>
460
+
<span class="text-sm font-medium">{props.create ? "Creating" : "Editing"} record</span>
461
+
</button>
462
+
</Show>
463
+
<Tooltip text={props.create ? "Create record (n)" : "Edit record (e)"}>
464
+
<button
465
+
class={`flex items-center p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 ${props.create ? "rounded-lg" : "rounded-sm"}`}
466
+
onclick={() => {
467
+
setNotice("");
468
+
setOpenDialog(true);
469
+
setIsMinimized(false);
470
+
}}
471
+
>
472
+
<div
473
+
class={props.create ? "iconify lucide--square-pen text-lg" : "iconify lucide--pencil"}
474
+
/>
475
+
</button>
476
+
</Tooltip>
477
+
</>
478
+
);
479
+
};
+4
src/components/create/state.ts
+4
src/components/create/state.ts
-531
src/components/create.tsx
-531
src/components/create.tsx
···
1
-
import { Client } from "@atcute/client";
2
-
import { Did } from "@atcute/lexicons";
3
-
import { isNsid, isRecordKey } from "@atcute/lexicons/syntax";
4
-
import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client";
5
-
import { remove } from "@mary/exif-rm";
6
-
import { useNavigate, useParams } from "@solidjs/router";
7
-
import { createEffect, createSignal, For, lazy, onCleanup, Show, Suspense } from "solid-js";
8
-
import { hasUserScope } from "../auth/scope-utils";
9
-
import { agent, sessions } from "../auth/state";
10
-
import { Button } from "./button.jsx";
11
-
import { Modal } from "./modal.jsx";
12
-
import { addNotification, removeNotification } from "./notification.jsx";
13
-
import { TextInput } from "./text-input.jsx";
14
-
import Tooltip from "./tooltip.jsx";
15
-
16
-
const Editor = lazy(() => import("../components/editor.jsx").then((m) => ({ default: m.Editor })));
17
-
18
-
export const editorInstance = { view: null as any };
19
-
export const [placeholder, setPlaceholder] = createSignal<any>();
20
-
21
-
export const RecordEditor = (props: { create: boolean; record?: any; refetch?: any }) => {
22
-
const navigate = useNavigate();
23
-
const params = useParams();
24
-
const [openDialog, setOpenDialog] = createSignal(false);
25
-
const [notice, setNotice] = createSignal("");
26
-
const [openUpload, setOpenUpload] = createSignal(false);
27
-
const [openInsertMenu, setOpenInsertMenu] = createSignal(false);
28
-
const [validate, setValidate] = createSignal<boolean | undefined>(undefined);
29
-
const [isMaximized, setIsMaximized] = createSignal(false);
30
-
const [isMinimized, setIsMinimized] = createSignal(false);
31
-
const [collectionError, setCollectionError] = createSignal("");
32
-
const [rkeyError, setRkeyError] = createSignal("");
33
-
let blobInput!: HTMLInputElement;
34
-
let formRef!: HTMLFormElement;
35
-
let insertMenuRef!: HTMLDivElement;
36
-
37
-
createEffect(() => {
38
-
if (openInsertMenu()) {
39
-
const handleClickOutside = (e: MouseEvent) => {
40
-
if (insertMenuRef && !insertMenuRef.contains(e.target as Node)) {
41
-
setOpenInsertMenu(false);
42
-
}
43
-
};
44
-
document.addEventListener("mousedown", handleClickOutside);
45
-
onCleanup(() => document.removeEventListener("mousedown", handleClickOutside));
46
-
}
47
-
});
48
-
49
-
const defaultPlaceholder = () => {
50
-
return {
51
-
$type: "app.bsky.feed.post",
52
-
text: "This post was sent from PDSls",
53
-
embed: {
54
-
$type: "app.bsky.embed.external",
55
-
external: {
56
-
uri: "https://pdsls.dev",
57
-
title: "PDSls",
58
-
description: "Browse the public data on atproto",
59
-
},
60
-
},
61
-
langs: ["en"],
62
-
createdAt: new Date().toISOString(),
63
-
};
64
-
};
65
-
66
-
const getValidateIcon = () => {
67
-
return (
68
-
validate() === true ? "lucide--circle-check"
69
-
: validate() === false ? "lucide--circle-x"
70
-
: "lucide--circle"
71
-
);
72
-
};
73
-
74
-
const getValidateLabel = () => {
75
-
return (
76
-
validate() === true ? "True"
77
-
: validate() === false ? "False"
78
-
: "Unset"
79
-
);
80
-
};
81
-
82
-
createEffect(() => {
83
-
if (openDialog()) {
84
-
setValidate(undefined);
85
-
setCollectionError("");
86
-
setRkeyError("");
87
-
}
88
-
});
89
-
90
-
const createRecord = async (formData: FormData) => {
91
-
const repo = formData.get("repo")?.toString();
92
-
if (!repo) return;
93
-
const rpc = new Client({ handler: new OAuthUserAgent(await getSession(repo as Did)) });
94
-
const collection = formData.get("collection");
95
-
const rkey = formData.get("rkey");
96
-
let record: any;
97
-
try {
98
-
record = JSON.parse(editorInstance.view.state.doc.toString());
99
-
} catch (e: any) {
100
-
setNotice(e.message);
101
-
return;
102
-
}
103
-
const res = await rpc.post("com.atproto.repo.createRecord", {
104
-
input: {
105
-
repo: repo as Did,
106
-
collection: collection ? collection.toString() : record.$type,
107
-
rkey: rkey?.toString().length ? rkey?.toString() : undefined,
108
-
record: record,
109
-
validate: validate(),
110
-
},
111
-
});
112
-
if (!res.ok) {
113
-
setNotice(`${res.data.error}: ${res.data.message}`);
114
-
return;
115
-
}
116
-
setOpenDialog(false);
117
-
const id = addNotification({
118
-
message: "Record created",
119
-
type: "success",
120
-
});
121
-
setTimeout(() => removeNotification(id), 3000);
122
-
navigate(`/${res.data.uri}`);
123
-
};
124
-
125
-
const editRecord = async (recreate?: boolean) => {
126
-
const record = editorInstance.view.state.doc.toString();
127
-
if (!record) return;
128
-
const rpc = new Client({ handler: agent()! });
129
-
try {
130
-
const editedRecord = JSON.parse(record);
131
-
if (recreate) {
132
-
const res = await rpc.post("com.atproto.repo.applyWrites", {
133
-
input: {
134
-
repo: agent()!.sub,
135
-
validate: validate(),
136
-
writes: [
137
-
{
138
-
collection: params.collection as `${string}.${string}.${string}`,
139
-
rkey: params.rkey!,
140
-
$type: "com.atproto.repo.applyWrites#delete",
141
-
},
142
-
{
143
-
collection: params.collection as `${string}.${string}.${string}`,
144
-
rkey: params.rkey,
145
-
$type: "com.atproto.repo.applyWrites#create",
146
-
value: editedRecord,
147
-
},
148
-
],
149
-
},
150
-
});
151
-
if (!res.ok) {
152
-
setNotice(`${res.data.error}: ${res.data.message}`);
153
-
return;
154
-
}
155
-
} else {
156
-
const res = await rpc.post("com.atproto.repo.putRecord", {
157
-
input: {
158
-
repo: agent()!.sub,
159
-
collection: params.collection as `${string}.${string}.${string}`,
160
-
rkey: params.rkey!,
161
-
record: editedRecord,
162
-
validate: validate(),
163
-
},
164
-
});
165
-
if (!res.ok) {
166
-
setNotice(`${res.data.error}: ${res.data.message}`);
167
-
return;
168
-
}
169
-
}
170
-
setOpenDialog(false);
171
-
const id = addNotification({
172
-
message: "Record edited",
173
-
type: "success",
174
-
});
175
-
setTimeout(() => removeNotification(id), 3000);
176
-
props.refetch();
177
-
} catch (err: any) {
178
-
setNotice(err.message);
179
-
}
180
-
};
181
-
182
-
const insertTimestamp = () => {
183
-
const timestamp = new Date().toISOString();
184
-
editorInstance.view.dispatch({
185
-
changes: {
186
-
from: editorInstance.view.state.selection.main.head,
187
-
insert: `"${timestamp}"`,
188
-
},
189
-
});
190
-
setOpenInsertMenu(false);
191
-
};
192
-
193
-
const MenuItem = (props: { icon: string; label: string; onClick: () => void }) => {
194
-
return (
195
-
<button
196
-
type="button"
197
-
class="flex items-center gap-2 rounded-md p-2 text-left text-xs hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
198
-
onClick={props.onClick}
199
-
>
200
-
<span class={`iconify ${props.icon}`}></span>
201
-
<span>{props.label}</span>
202
-
</button>
203
-
);
204
-
};
205
-
206
-
const FileUpload = (props: { file: File }) => {
207
-
const [uploading, setUploading] = createSignal(false);
208
-
const [error, setError] = createSignal("");
209
-
210
-
onCleanup(() => (blobInput.value = ""));
211
-
212
-
const formatFileSize = (bytes: number) => {
213
-
if (bytes === 0) return "0 Bytes";
214
-
const k = 1024;
215
-
const sizes = ["Bytes", "KB", "MB", "GB"];
216
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
217
-
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
218
-
};
219
-
220
-
const uploadBlob = async () => {
221
-
let blob: Blob;
222
-
223
-
const mimetype = (document.getElementById("mimetype") as HTMLInputElement)?.value;
224
-
(document.getElementById("mimetype") as HTMLInputElement).value = "";
225
-
if (mimetype) blob = new Blob([props.file], { type: mimetype });
226
-
else blob = props.file;
227
-
228
-
if ((document.getElementById("exif-rm") as HTMLInputElement).checked) {
229
-
const exifRemoved = remove(new Uint8Array(await blob.arrayBuffer()));
230
-
if (exifRemoved !== null) blob = new Blob([exifRemoved], { type: blob.type });
231
-
}
232
-
233
-
const rpc = new Client({ handler: agent()! });
234
-
setUploading(true);
235
-
const res = await rpc.post("com.atproto.repo.uploadBlob", {
236
-
input: blob,
237
-
});
238
-
setUploading(false);
239
-
if (!res.ok) {
240
-
setError(res.data.error);
241
-
return;
242
-
}
243
-
editorInstance.view.dispatch({
244
-
changes: {
245
-
from: editorInstance.view.state.selection.main.head,
246
-
insert: JSON.stringify(res.data.blob, null, 2),
247
-
},
248
-
});
249
-
setOpenUpload(false);
250
-
};
251
-
252
-
return (
253
-
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0">
254
-
<h2 class="mb-2 font-semibold">Upload blob</h2>
255
-
<div class="flex flex-col gap-2 text-sm">
256
-
<div class="flex flex-col gap-1">
257
-
<p class="flex gap-1">
258
-
<span class="truncate">{props.file.name}</span>
259
-
<span class="shrink-0 text-neutral-600 dark:text-neutral-400">
260
-
({formatFileSize(props.file.size)})
261
-
</span>
262
-
</p>
263
-
</div>
264
-
<div class="flex items-center gap-x-2">
265
-
<label for="mimetype" class="shrink-0 select-none">
266
-
MIME type
267
-
</label>
268
-
<TextInput id="mimetype" placeholder={props.file.type} />
269
-
</div>
270
-
<div class="flex items-center gap-1">
271
-
<input id="exif-rm" type="checkbox" checked />
272
-
<label for="exif-rm" class="select-none">
273
-
Remove EXIF data
274
-
</label>
275
-
</div>
276
-
<p class="text-xs text-neutral-600 dark:text-neutral-400">
277
-
Metadata will be pasted after the cursor
278
-
</p>
279
-
<Show when={error()}>
280
-
<span class="text-red-500 dark:text-red-400">Error: {error()}</span>
281
-
</Show>
282
-
<div class="flex justify-between gap-2">
283
-
<Button onClick={() => setOpenUpload(false)}>Cancel</Button>
284
-
<Show when={uploading()}>
285
-
<div class="flex items-center gap-1">
286
-
<span class="iconify lucide--loader-circle animate-spin"></span>
287
-
<span>Uploading</span>
288
-
</div>
289
-
</Show>
290
-
<Show when={!uploading()}>
291
-
<Button
292
-
onClick={uploadBlob}
293
-
class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400"
294
-
>
295
-
Upload
296
-
</Button>
297
-
</Show>
298
-
</div>
299
-
</div>
300
-
</div>
301
-
);
302
-
};
303
-
304
-
return (
305
-
<>
306
-
<Modal
307
-
open={openDialog()}
308
-
onClose={() => setOpenDialog(false)}
309
-
closeOnClick={false}
310
-
nonBlocking={isMinimized()}
311
-
>
312
-
<div
313
-
style="transform: translateX(-50%) translateZ(0);"
314
-
classList={{
315
-
"dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto absolute top-18 left-1/2 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-all duration-200 dark:border-neutral-700 starting:opacity-0": true,
316
-
"w-[calc(100%-1rem)] max-w-3xl h-[65vh]": !isMaximized(),
317
-
"w-[calc(100%-1rem)] max-w-7xl h-[85vh]": isMaximized(),
318
-
hidden: isMinimized(),
319
-
}}
320
-
>
321
-
<div class="mb-2 flex w-full justify-between text-base">
322
-
<div class="flex items-center gap-2">
323
-
<span class="font-semibold select-none">
324
-
{props.create ? "Creating" : "Editing"} record
325
-
</span>
326
-
</div>
327
-
<div class="flex items-center gap-1">
328
-
<button
329
-
type="button"
330
-
onclick={() => setIsMinimized(true)}
331
-
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
332
-
>
333
-
<span class="iconify lucide--minus"></span>
334
-
</button>
335
-
<button
336
-
type="button"
337
-
onclick={() => setIsMaximized(!isMaximized())}
338
-
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
339
-
>
340
-
<span
341
-
class={`iconify ${isMaximized() ? "lucide--minimize-2" : "lucide--maximize-2"}`}
342
-
></span>
343
-
</button>
344
-
<button
345
-
id="close"
346
-
onclick={() => setOpenDialog(false)}
347
-
class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
348
-
>
349
-
<span class="iconify lucide--x"></span>
350
-
</button>
351
-
</div>
352
-
</div>
353
-
<form ref={formRef} class="flex min-h-0 flex-1 flex-col gap-y-2">
354
-
<Show when={props.create}>
355
-
<div class="flex flex-wrap items-center gap-1 text-sm">
356
-
<span>at://</span>
357
-
<select
358
-
class="dark:bg-dark-100 max-w-40 truncate rounded-lg border-[0.5px] border-neutral-300 bg-white px-1 py-1 select-none focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400"
359
-
name="repo"
360
-
id="repo"
361
-
>
362
-
<For each={Object.keys(sessions)}>
363
-
{(session) => (
364
-
<option value={session} selected={session === agent()?.sub}>
365
-
{sessions[session].handle ?? session}
366
-
</option>
367
-
)}
368
-
</For>
369
-
</select>
370
-
<span>/</span>
371
-
<TextInput
372
-
id="collection"
373
-
name="collection"
374
-
placeholder="Collection (default: $type)"
375
-
class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`}
376
-
onInput={(e) => {
377
-
const value = e.currentTarget.value;
378
-
if (!value || isNsid(value)) setCollectionError("");
379
-
else
380
-
setCollectionError(
381
-
"Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)",
382
-
);
383
-
}}
384
-
/>
385
-
<span>/</span>
386
-
<TextInput
387
-
id="rkey"
388
-
name="rkey"
389
-
placeholder="Record key (default: TID)"
390
-
class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`}
391
-
onInput={(e) => {
392
-
const value = e.currentTarget.value;
393
-
if (!value || isRecordKey(value)) setRkeyError("");
394
-
else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -");
395
-
}}
396
-
/>
397
-
</div>
398
-
<Show when={collectionError() || rkeyError()}>
399
-
<div class="text-xs text-red-500 dark:text-red-400">
400
-
<div>{collectionError()}</div>
401
-
<div>{rkeyError()}</div>
402
-
</div>
403
-
</Show>
404
-
</Show>
405
-
<div class="min-h-0 flex-1">
406
-
<Suspense
407
-
fallback={
408
-
<div class="flex h-full items-center justify-center">
409
-
<span class="iconify lucide--loader-circle animate-spin text-xl"></span>
410
-
</div>
411
-
}
412
-
>
413
-
<Editor
414
-
content={JSON.stringify(
415
-
!props.create ? props.record
416
-
: params.rkey ? placeholder()
417
-
: defaultPlaceholder(),
418
-
null,
419
-
2,
420
-
)}
421
-
/>
422
-
</Suspense>
423
-
</div>
424
-
<div class="flex flex-col gap-2">
425
-
<Show when={notice()}>
426
-
<div class="text-sm text-red-500 dark:text-red-400">{notice()}</div>
427
-
</Show>
428
-
<div class="flex justify-between gap-2">
429
-
<div class="relative" ref={insertMenuRef}>
430
-
<button
431
-
type="button"
432
-
class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 flex w-fit rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 text-base shadow-xs hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"
433
-
onClick={() => setOpenInsertMenu(!openInsertMenu())}
434
-
>
435
-
<span class="iconify lucide--plus select-none"></span>
436
-
</button>
437
-
<Show when={openInsertMenu()}>
438
-
<div class="dark:bg-dark-300 dark:shadow-dark-700 absolute bottom-full left-0 z-10 mb-1 flex w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 shadow-md dark:border-neutral-700">
439
-
<Show when={hasUserScope("blob")}>
440
-
<MenuItem
441
-
icon="lucide--upload"
442
-
label="Upload blob"
443
-
onClick={() => {
444
-
setOpenInsertMenu(false);
445
-
blobInput.click();
446
-
}}
447
-
/>
448
-
</Show>
449
-
<MenuItem
450
-
icon="lucide--clock"
451
-
label="Insert timestamp"
452
-
onClick={insertTimestamp}
453
-
/>
454
-
</div>
455
-
</Show>
456
-
<input
457
-
type="file"
458
-
id="blob"
459
-
class="sr-only"
460
-
ref={blobInput}
461
-
onChange={(e) => {
462
-
if (e.target.files !== null) setOpenUpload(true);
463
-
}}
464
-
/>
465
-
</div>
466
-
<Modal
467
-
open={openUpload()}
468
-
onClose={() => setOpenUpload(false)}
469
-
closeOnClick={false}
470
-
>
471
-
<FileUpload file={blobInput.files![0]} />
472
-
</Modal>
473
-
<div class="flex items-center justify-end gap-2">
474
-
<button
475
-
type="button"
476
-
class="flex items-center gap-1 rounded-sm p-1.5 text-xs hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
477
-
onClick={() =>
478
-
setValidate(
479
-
validate() === true ? false
480
-
: validate() === false ? undefined
481
-
: true,
482
-
)
483
-
}
484
-
>
485
-
<Tooltip text={getValidateLabel()}>
486
-
<span class={`iconify ${getValidateIcon()}`}></span>
487
-
</Tooltip>
488
-
<span>Validate</span>
489
-
</button>
490
-
<Show when={!props.create}>
491
-
<Button onClick={() => editRecord(true)}>Recreate</Button>
492
-
</Show>
493
-
<Button
494
-
onClick={() =>
495
-
props.create ? createRecord(new FormData(formRef)) : editRecord()
496
-
}
497
-
>
498
-
{props.create ? "Create" : "Edit"}
499
-
</Button>
500
-
</div>
501
-
</div>
502
-
</div>
503
-
</form>
504
-
</div>
505
-
</Modal>
506
-
<Show when={isMinimized() && openDialog()}>
507
-
<button
508
-
class="dark:bg-dark-300 dark:hover:bg-dark-200 dark:active:bg-dark-100 fixed right-4 bottom-4 z-30 flex items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-3 py-2 shadow-md hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700"
509
-
onclick={() => setIsMinimized(false)}
510
-
>
511
-
<span class="iconify lucide--square-pen text-lg"></span>
512
-
<span class="text-sm font-medium">{props.create ? "Creating" : "Editing"} record</span>
513
-
</button>
514
-
</Show>
515
-
<Tooltip text={`${props.create ? "Create" : "Edit"} record`}>
516
-
<button
517
-
class={`flex items-center p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 ${props.create ? "rounded-lg" : "rounded-sm"}`}
518
-
onclick={() => {
519
-
setNotice("");
520
-
setOpenDialog(true);
521
-
setIsMinimized(false);
522
-
}}
523
-
>
524
-
<div
525
-
class={props.create ? "iconify lucide--square-pen text-lg" : "iconify lucide--pencil"}
526
-
/>
527
-
</button>
528
-
</Tooltip>
529
-
</>
530
-
);
531
-
};
+1
-1
src/components/editor.tsx
+1
-1
src/components/editor.tsx
···
7
7
import { basicLight } from "@fsegurai/codemirror-theme-basic-light";
8
8
import { basicSetup, EditorView } from "codemirror";
9
9
import { onCleanup, onMount } from "solid-js";
10
-
import { editorInstance } from "./create";
10
+
import { editorInstance } from "./create/state";
11
11
12
12
const Editor = (props: { content: string }) => {
13
13
let editorDiv!: HTMLDivElement;
+27
-7
src/components/search.tsx
+27
-7
src/components/search.tsx
···
1
-
import { Client, CredentialManager } from "@atcute/client";
1
+
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { Nsid } from "@atcute/lexicons";
3
-
import { A, useLocation, useNavigate } from "@solidjs/router";
4
-
import { createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js";
3
+
import { A, useNavigate } from "@solidjs/router";
4
+
import {
5
+
createEffect,
6
+
createResource,
7
+
createSignal,
8
+
For,
9
+
onCleanup,
10
+
onMount,
11
+
Show,
12
+
} from "solid-js";
5
13
import { isTouchDevice } from "../layout";
6
14
import { resolveLexiconAuthority } from "../utils/api";
7
15
import { appHandleLink, appList, appName, AppUrl } from "../utils/app-urls";
···
38
46
39
47
if ((ev.ctrlKey || ev.metaKey) && ev.key == "k") {
40
48
ev.preventDefault();
41
-
setShowSearch(!showSearch());
49
+
50
+
if (showSearch()) {
51
+
const searchInput = document.querySelector("#input") as HTMLInputElement;
52
+
if (searchInput && document.activeElement !== searchInput) {
53
+
searchInput.focus();
54
+
} else {
55
+
setShowSearch(false);
56
+
}
57
+
} else {
58
+
setShowSearch(true);
59
+
}
42
60
} else if (ev.key == "Escape") {
43
61
ev.preventDefault();
44
62
setShowSearch(false);
···
67
85
const navigate = useNavigate();
68
86
let searchInput!: HTMLInputElement;
69
87
const rpc = new Client({
70
-
handler: new CredentialManager({ service: "https://public.api.bsky.app" }),
88
+
handler: simpleFetchHandler({ service: "https://public.api.bsky.app" }),
71
89
});
72
90
73
91
onMount(() => {
74
-
if (useLocation().pathname !== "/") searchInput.focus();
75
-
76
92
const handlePaste = (e: ClipboardEvent) => {
77
93
if (e.target === searchInput) return;
78
94
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
···
83
99
84
100
window.addEventListener("paste", handlePaste);
85
101
onCleanup(() => window.removeEventListener("paste", handlePaste));
102
+
});
103
+
104
+
createEffect(() => {
105
+
if (showSearch()) searchInput.focus();
86
106
});
87
107
88
108
const fetchTypeahead = async (input: string) => {
+17
-5
src/layout.tsx
+17
-5
src/layout.tsx
···
1
1
import { Handle } from "@atcute/lexicons";
2
2
import { Meta, MetaProvider } from "@solidjs/meta";
3
3
import { A, RouteSectionProps, useLocation, useNavigate } from "@solidjs/router";
4
-
import { createEffect, ErrorBoundary, onMount, Show, Suspense } from "solid-js";
4
+
import { createEffect, ErrorBoundary, onCleanup, onMount, Show, Suspense } from "solid-js";
5
5
import { AccountManager } from "./auth/account.jsx";
6
6
import { hasUserScope } from "./auth/scope-utils";
7
-
import { RecordEditor } from "./components/create.jsx";
7
+
import { agent } from "./auth/state.js";
8
+
import { RecordEditor } from "./components/create";
8
9
import { DropdownMenu, MenuProvider, MenuSeparator, NavMenu } from "./components/dropdown.jsx";
9
10
import { NavBar } from "./components/navbar.jsx";
10
11
import { NotificationContainer } from "./components/notification.jsx";
···
44
45
45
46
onMount(() => {
46
47
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", themeEvent);
48
+
49
+
const handleGoToRepo = (ev: KeyboardEvent) => {
50
+
if (document.querySelector("[data-modal]")) return;
51
+
if (ev.target instanceof HTMLInputElement || ev.target instanceof HTMLTextAreaElement) return;
52
+
53
+
if (ev.key === "g" && agent()?.sub) {
54
+
ev.preventDefault();
55
+
navigate(`/at://${agent()!.sub}`);
56
+
}
57
+
};
58
+
59
+
window.addEventListener("keydown", handleGoToRepo);
60
+
onCleanup(() => window.removeEventListener("keydown", handleGoToRepo));
47
61
48
62
if (localStorage.getItem("sailor") === "true") {
49
63
const style = document.createElement("style");
···
128
142
<span>PDSls</span>
129
143
</A>
130
144
<div class="dark:bg-dark-300/60 relative flex items-center gap-0.5 rounded-lg bg-neutral-50/60 px-1 py-0.5">
131
-
<Show when={location.pathname !== "/"}>
132
-
<SearchButton />
133
-
</Show>
145
+
<SearchButton />
134
146
<Show when={hasUserScope("create")}>
135
147
<RecordEditor create={true} />
136
148
</Show>
+7
-1
src/styles/index.css
+7
-1
src/styles/index.css
···
6
6
7
7
@custom-variant dark (&:where(.dark, .dark *));
8
8
9
+
@font-face {
10
+
font-family: "Figtree";
11
+
src: url("/fonts/Figtree[wght].woff2") format("woff2");
12
+
font-display: swap;
13
+
}
14
+
9
15
@theme {
10
-
--font-sans: "Inter", sans-serif;
16
+
--font-sans: "Figtree", sans-serif;
11
17
--font-mono: "Roboto Mono", monospace;
12
18
--font-pecita: "Pecita", serif;
13
19
+2
-2
src/views/blob.tsx
+2
-2
src/views/blob.tsx
···
1
-
import { Client, CredentialManager } from "@atcute/client";
1
+
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { createResource, createSignal, For, Show } from "solid-js";
3
3
import { Button } from "../components/button";
4
4
···
9
9
let rpc: Client;
10
10
11
11
const fetchBlobs = async () => {
12
-
if (!rpc) rpc = new Client({ handler: new CredentialManager({ service: props.pds }) });
12
+
if (!rpc) rpc = new Client({ handler: simpleFetchHandler({ service: props.pds }) });
13
13
const res = await rpc.get("com.atproto.sync.listBlobs", {
14
14
params: {
15
15
did: props.repo as `did:${string}:${string}`,
+18
-16
src/views/collection.tsx
+18
-16
src/views/collection.tsx
···
1
1
import { ComAtprotoRepoApplyWrites, ComAtprotoRepoGetRecord } from "@atcute/atproto";
2
-
import { Client, CredentialManager } from "@atcute/client";
2
+
import { Client, simpleFetchHandler } from "@atcute/client";
3
3
import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons";
4
4
import * as TID from "@atcute/tid";
5
5
import { A, useParams } from "@solidjs/router";
···
89
89
90
90
const fetchRecords = async () => {
91
91
if (!pds) pds = await resolvePDS(did!);
92
-
if (!rpc) rpc = new Client({ handler: new CredentialManager({ service: pds }) });
92
+
if (!rpc) rpc = new Client({ handler: simpleFetchHandler({ service: pds }) });
93
93
const res = await rpc.get("com.atproto.repo.listRecords", {
94
94
params: {
95
95
repo: did as ActorIdentifier,
···
230
230
</button>
231
231
}
232
232
/>
233
-
<Tooltip
234
-
text="Recreate"
235
-
children={
236
-
<button
237
-
onclick={() => {
238
-
setRecreate(true);
239
-
setOpenDelete(true);
240
-
}}
241
-
class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
242
-
>
243
-
<span class="iconify lucide--recycle text-lg text-green-500 dark:text-green-400"></span>
244
-
</button>
245
-
}
246
-
/>
233
+
<Show when={hasUserScope("create")}>
234
+
<Tooltip
235
+
text="Recreate"
236
+
children={
237
+
<button
238
+
onclick={() => {
239
+
setRecreate(true);
240
+
setOpenDelete(true);
241
+
}}
242
+
class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
243
+
>
244
+
<span class="iconify lucide--recycle text-lg text-green-500 dark:text-green-400"></span>
245
+
</button>
246
+
}
247
+
/>
248
+
</Show>
247
249
<Tooltip
248
250
text="Delete"
249
251
children={
+2
-2
src/views/labels.tsx
+2
-2
src/views/labels.tsx
···
1
1
import { ComAtprotoLabelDefs } from "@atcute/atproto";
2
-
import { Client, CredentialManager } from "@atcute/client";
2
+
import { Client, simpleFetchHandler } from "@atcute/client";
3
3
import { isAtprotoDid } from "@atcute/identity";
4
4
import { Handle } from "@atcute/lexicons";
5
5
import { A, useSearchParams } from "@solidjs/router";
···
158
158
await resolvePDS(did);
159
159
if (!labelerCache[did]) throw new Error("Repository is not a labeler");
160
160
rpc = new Client({
161
-
handler: new CredentialManager({ service: labelerCache[did] }),
161
+
handler: simpleFetchHandler({ service: labelerCache[did] }),
162
162
});
163
163
164
164
setSearchParams({ did, uriPatterns });
+2
-2
src/views/pds.tsx
+2
-2
src/views/pds.tsx
···
1
1
import { ComAtprotoServerDescribeServer, ComAtprotoSyncListRepos } from "@atcute/atproto";
2
-
import { Client, CredentialManager } from "@atcute/client";
2
+
import { Client, simpleFetchHandler } from "@atcute/client";
3
3
import { InferXRPCBodyOutput } from "@atcute/lexicons";
4
4
import * as TID from "@atcute/tid";
5
5
import { A, useLocation, useParams } from "@solidjs/router";
···
23
23
setPDS(params.pds);
24
24
const pds =
25
25
params.pds!.startsWith("localhost") ? `http://${params.pds}` : `https://${params.pds}`;
26
-
const rpc = new Client({ handler: new CredentialManager({ service: pds }) });
26
+
const rpc = new Client({ handler: simpleFetchHandler({ service: pds }) });
27
27
28
28
const getVersion = async () => {
29
29
// @ts-expect-error: undocumented endpoint
+4
-4
src/views/record.tsx
+4
-4
src/views/record.tsx
···
1
-
import { Client, CredentialManager } from "@atcute/client";
1
+
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { DidDocument, getPdsEndpoint } from "@atcute/identity";
3
3
import { lexiconDoc } from "@atcute/lexicon-doc";
4
4
import { RecordValidator } from "@atcute/lexicon-doc/validations";
···
12
12
import { agent } from "../auth/state";
13
13
import { Backlinks } from "../components/backlinks.jsx";
14
14
import { Button } from "../components/button.jsx";
15
-
import { RecordEditor, setPlaceholder } from "../components/create.jsx";
15
+
import { RecordEditor, setPlaceholder } from "../components/create";
16
16
import {
17
17
CopyMenu,
18
18
DropdownMenu,
···
68
68
});
69
69
}
70
70
71
-
const rpc = new Client({ handler: new CredentialManager({ service: pdsEndpoint }) });
71
+
const rpc = new Client({ handler: simpleFetchHandler({ service: pdsEndpoint }) });
72
72
const response = await rpc.get("com.atproto.repo.getRecord", {
73
73
params: {
74
74
repo: authority,
···
208
208
setValidSchema(undefined);
209
209
setLexiconUri(undefined);
210
210
const pds = await resolvePDS(did!);
211
-
rpc = new Client({ handler: new CredentialManager({ service: pds }) });
211
+
rpc = new Client({ handler: simpleFetchHandler({ service: pds }) });
212
212
const res = await rpc.get("com.atproto.repo.getRecord", {
213
213
params: {
214
214
repo: did as ActorIdentifier,
+2
-2
src/views/repo.tsx
+2
-2
src/views/repo.tsx
···
1
-
import { Client, CredentialManager } from "@atcute/client";
1
+
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { DidDocument } from "@atcute/identity";
3
3
import { ActorIdentifier, Did, Handle, Nsid } from "@atcute/lexicons";
4
4
import { A, useLocation, useNavigate, useParams } from "@solidjs/router";
···
139
139
return {};
140
140
}
141
141
142
-
rpc = new Client({ handler: new CredentialManager({ service: pds }) });
142
+
rpc = new Client({ handler: simpleFetchHandler({ service: pds }) });
143
143
try {
144
144
const res = await rpc.get("com.atproto.repo.describeRepo", {
145
145
params: { repo: did as ActorIdentifier },