+12
.envrc
+12
.envrc
···
1
+
#!/usr/bin/env bash
2
+
# ^ added for shellcheck and file-type detection
3
+
4
+
# Watch & reload direnv on change
5
+
watch_file devshell.toml
6
+
7
+
if [[ $(type -t use_flake) != function ]]; then
8
+
echo "ERROR: use_flake function missing."
9
+
echo "Please update direnv to v2.30.0 or later."
10
+
exit 1
11
+
fi
12
+
use flake
+14
deno.json
+14
deno.json
+757
deno.lock
+757
deno.lock
···
1
+
{
2
+
"version": "5",
3
+
"specifiers": {
4
+
"npm:@atcute/atproto@^3.1.1": "3.1.2",
5
+
"npm:@atcute/bluesky@^3.2.0": "3.2.1",
6
+
"npm:@atcute/client@^4.0.3": "4.0.3",
7
+
"npm:@atcute/identity-resolver@^1.1.3": "1.1.3_@atcute+identity@1.1.0",
8
+
"npm:@atcute/identity@^1.0.2": "1.1.0",
9
+
"npm:copyfiles@^2.4.1": "2.4.1",
10
+
"npm:esbuild@~0.25.8": "0.25.9",
11
+
"npm:http-server@^14.1.1": "14.1.1",
12
+
"npm:typescript@^5.8.3": "5.9.2"
13
+
},
14
+
"npm": {
15
+
"@atcute/atproto@3.1.2": {
16
+
"integrity": "sha512-m6OWoGTeL8Wlw7cm8Nrf+NU24rlx5A0DzxEQQPPu1wqYNHw6S33nOU90W+U09vVN0aZGZUrLFfWWhAaSWTp1ZQ==",
17
+
"dependencies": [
18
+
"@atcute/lexicons"
19
+
]
20
+
},
21
+
"@atcute/bluesky@3.2.1": {
22
+
"integrity": "sha512-WehZuGNEC9NNB2y7Jg/i4ANzbMEQzs2EwrLYKWDuvJT3YnPHEol3vkc4DK47TODs3TiacQJp4MWhsQyfktsZ6g==",
23
+
"dependencies": [
24
+
"@atcute/atproto",
25
+
"@atcute/lexicons"
26
+
]
27
+
},
28
+
"@atcute/client@4.0.3": {
29
+
"integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==",
30
+
"dependencies": [
31
+
"@atcute/identity",
32
+
"@atcute/lexicons"
33
+
]
34
+
},
35
+
"@atcute/identity-resolver@1.1.3_@atcute+identity@1.1.0": {
36
+
"integrity": "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA==",
37
+
"dependencies": [
38
+
"@atcute/identity",
39
+
"@atcute/lexicons",
40
+
"@atcute/util-fetch",
41
+
"@badrap/valita"
42
+
]
43
+
},
44
+
"@atcute/identity@1.1.0": {
45
+
"integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==",
46
+
"dependencies": [
47
+
"@atcute/lexicons",
48
+
"@badrap/valita"
49
+
]
50
+
},
51
+
"@atcute/lexicons@1.1.1": {
52
+
"integrity": "sha512-k6qy5p3j9fJJ6ekaMPfEfp3ni4TW/XNuH9ZmsuwC0fi0tOjp+Fa8ZQakHwnqOzFt/cVBfGcmYE/lKNAbeTjgUg==",
53
+
"dependencies": [
54
+
"esm-env"
55
+
]
56
+
},
57
+
"@atcute/util-fetch@1.0.1": {
58
+
"integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==",
59
+
"dependencies": [
60
+
"@badrap/valita"
61
+
]
62
+
},
63
+
"@badrap/valita@0.4.6": {
64
+
"integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="
65
+
},
66
+
"@esbuild/aix-ppc64@0.25.9": {
67
+
"integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
68
+
"os": ["aix"],
69
+
"cpu": ["ppc64"]
70
+
},
71
+
"@esbuild/android-arm64@0.25.9": {
72
+
"integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
73
+
"os": ["android"],
74
+
"cpu": ["arm64"]
75
+
},
76
+
"@esbuild/android-arm@0.25.9": {
77
+
"integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
78
+
"os": ["android"],
79
+
"cpu": ["arm"]
80
+
},
81
+
"@esbuild/android-x64@0.25.9": {
82
+
"integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
83
+
"os": ["android"],
84
+
"cpu": ["x64"]
85
+
},
86
+
"@esbuild/darwin-arm64@0.25.9": {
87
+
"integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
88
+
"os": ["darwin"],
89
+
"cpu": ["arm64"]
90
+
},
91
+
"@esbuild/darwin-x64@0.25.9": {
92
+
"integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
93
+
"os": ["darwin"],
94
+
"cpu": ["x64"]
95
+
},
96
+
"@esbuild/freebsd-arm64@0.25.9": {
97
+
"integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
98
+
"os": ["freebsd"],
99
+
"cpu": ["arm64"]
100
+
},
101
+
"@esbuild/freebsd-x64@0.25.9": {
102
+
"integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
103
+
"os": ["freebsd"],
104
+
"cpu": ["x64"]
105
+
},
106
+
"@esbuild/linux-arm64@0.25.9": {
107
+
"integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
108
+
"os": ["linux"],
109
+
"cpu": ["arm64"]
110
+
},
111
+
"@esbuild/linux-arm@0.25.9": {
112
+
"integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
113
+
"os": ["linux"],
114
+
"cpu": ["arm"]
115
+
},
116
+
"@esbuild/linux-ia32@0.25.9": {
117
+
"integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
118
+
"os": ["linux"],
119
+
"cpu": ["ia32"]
120
+
},
121
+
"@esbuild/linux-loong64@0.25.9": {
122
+
"integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
123
+
"os": ["linux"],
124
+
"cpu": ["loong64"]
125
+
},
126
+
"@esbuild/linux-mips64el@0.25.9": {
127
+
"integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
128
+
"os": ["linux"],
129
+
"cpu": ["mips64el"]
130
+
},
131
+
"@esbuild/linux-ppc64@0.25.9": {
132
+
"integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
133
+
"os": ["linux"],
134
+
"cpu": ["ppc64"]
135
+
},
136
+
"@esbuild/linux-riscv64@0.25.9": {
137
+
"integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
138
+
"os": ["linux"],
139
+
"cpu": ["riscv64"]
140
+
},
141
+
"@esbuild/linux-s390x@0.25.9": {
142
+
"integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
143
+
"os": ["linux"],
144
+
"cpu": ["s390x"]
145
+
},
146
+
"@esbuild/linux-x64@0.25.9": {
147
+
"integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
148
+
"os": ["linux"],
149
+
"cpu": ["x64"]
150
+
},
151
+
"@esbuild/netbsd-arm64@0.25.9": {
152
+
"integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
153
+
"os": ["netbsd"],
154
+
"cpu": ["arm64"]
155
+
},
156
+
"@esbuild/netbsd-x64@0.25.9": {
157
+
"integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
158
+
"os": ["netbsd"],
159
+
"cpu": ["x64"]
160
+
},
161
+
"@esbuild/openbsd-arm64@0.25.9": {
162
+
"integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
163
+
"os": ["openbsd"],
164
+
"cpu": ["arm64"]
165
+
},
166
+
"@esbuild/openbsd-x64@0.25.9": {
167
+
"integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
168
+
"os": ["openbsd"],
169
+
"cpu": ["x64"]
170
+
},
171
+
"@esbuild/openharmony-arm64@0.25.9": {
172
+
"integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
173
+
"os": ["openharmony"],
174
+
"cpu": ["arm64"]
175
+
},
176
+
"@esbuild/sunos-x64@0.25.9": {
177
+
"integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
178
+
"os": ["sunos"],
179
+
"cpu": ["x64"]
180
+
},
181
+
"@esbuild/win32-arm64@0.25.9": {
182
+
"integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
183
+
"os": ["win32"],
184
+
"cpu": ["arm64"]
185
+
},
186
+
"@esbuild/win32-ia32@0.25.9": {
187
+
"integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
188
+
"os": ["win32"],
189
+
"cpu": ["ia32"]
190
+
},
191
+
"@esbuild/win32-x64@0.25.9": {
192
+
"integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
193
+
"os": ["win32"],
194
+
"cpu": ["x64"]
195
+
},
196
+
"ansi-regex@5.0.1": {
197
+
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
198
+
},
199
+
"ansi-styles@4.3.0": {
200
+
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
201
+
"dependencies": [
202
+
"color-convert"
203
+
]
204
+
},
205
+
"async@3.2.6": {
206
+
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
207
+
},
208
+
"balanced-match@1.0.2": {
209
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
210
+
},
211
+
"basic-auth@2.0.1": {
212
+
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
213
+
"dependencies": [
214
+
"safe-buffer"
215
+
]
216
+
},
217
+
"brace-expansion@1.1.12": {
218
+
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
219
+
"dependencies": [
220
+
"balanced-match",
221
+
"concat-map"
222
+
]
223
+
},
224
+
"call-bind-apply-helpers@1.0.2": {
225
+
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
226
+
"dependencies": [
227
+
"es-errors",
228
+
"function-bind"
229
+
]
230
+
},
231
+
"call-bound@1.0.4": {
232
+
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
233
+
"dependencies": [
234
+
"call-bind-apply-helpers",
235
+
"get-intrinsic"
236
+
]
237
+
},
238
+
"chalk@4.1.2": {
239
+
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
240
+
"dependencies": [
241
+
"ansi-styles",
242
+
"supports-color"
243
+
]
244
+
},
245
+
"cliui@7.0.4": {
246
+
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
247
+
"dependencies": [
248
+
"string-width",
249
+
"strip-ansi",
250
+
"wrap-ansi"
251
+
]
252
+
},
253
+
"color-convert@2.0.1": {
254
+
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
255
+
"dependencies": [
256
+
"color-name"
257
+
]
258
+
},
259
+
"color-name@1.1.4": {
260
+
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
261
+
},
262
+
"concat-map@0.0.1": {
263
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
264
+
},
265
+
"copyfiles@2.4.1": {
266
+
"integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==",
267
+
"dependencies": [
268
+
"glob",
269
+
"minimatch",
270
+
"mkdirp",
271
+
"noms",
272
+
"through2",
273
+
"untildify",
274
+
"yargs"
275
+
],
276
+
"bin": true
277
+
},
278
+
"core-util-is@1.0.3": {
279
+
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
280
+
},
281
+
"corser@2.0.1": {
282
+
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ=="
283
+
},
284
+
"debug@4.4.1": {
285
+
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
286
+
"dependencies": [
287
+
"ms"
288
+
]
289
+
},
290
+
"dunder-proto@1.0.1": {
291
+
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
292
+
"dependencies": [
293
+
"call-bind-apply-helpers",
294
+
"es-errors",
295
+
"gopd"
296
+
]
297
+
},
298
+
"emoji-regex@8.0.0": {
299
+
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
300
+
},
301
+
"es-define-property@1.0.1": {
302
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
303
+
},
304
+
"es-errors@1.3.0": {
305
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
306
+
},
307
+
"es-object-atoms@1.1.1": {
308
+
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
309
+
"dependencies": [
310
+
"es-errors"
311
+
]
312
+
},
313
+
"esbuild@0.25.9": {
314
+
"integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
315
+
"optionalDependencies": [
316
+
"@esbuild/aix-ppc64",
317
+
"@esbuild/android-arm",
318
+
"@esbuild/android-arm64",
319
+
"@esbuild/android-x64",
320
+
"@esbuild/darwin-arm64",
321
+
"@esbuild/darwin-x64",
322
+
"@esbuild/freebsd-arm64",
323
+
"@esbuild/freebsd-x64",
324
+
"@esbuild/linux-arm",
325
+
"@esbuild/linux-arm64",
326
+
"@esbuild/linux-ia32",
327
+
"@esbuild/linux-loong64",
328
+
"@esbuild/linux-mips64el",
329
+
"@esbuild/linux-ppc64",
330
+
"@esbuild/linux-riscv64",
331
+
"@esbuild/linux-s390x",
332
+
"@esbuild/linux-x64",
333
+
"@esbuild/netbsd-arm64",
334
+
"@esbuild/netbsd-x64",
335
+
"@esbuild/openbsd-arm64",
336
+
"@esbuild/openbsd-x64",
337
+
"@esbuild/openharmony-arm64",
338
+
"@esbuild/sunos-x64",
339
+
"@esbuild/win32-arm64",
340
+
"@esbuild/win32-ia32",
341
+
"@esbuild/win32-x64"
342
+
],
343
+
"scripts": true,
344
+
"bin": true
345
+
},
346
+
"escalade@3.2.0": {
347
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
348
+
},
349
+
"esm-env@1.2.2": {
350
+
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
351
+
},
352
+
"eventemitter3@4.0.7": {
353
+
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
354
+
},
355
+
"follow-redirects@1.15.11": {
356
+
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
357
+
},
358
+
"fs.realpath@1.0.0": {
359
+
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
360
+
},
361
+
"function-bind@1.1.2": {
362
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
363
+
},
364
+
"get-caller-file@2.0.5": {
365
+
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
366
+
},
367
+
"get-intrinsic@1.3.0": {
368
+
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
369
+
"dependencies": [
370
+
"call-bind-apply-helpers",
371
+
"es-define-property",
372
+
"es-errors",
373
+
"es-object-atoms",
374
+
"function-bind",
375
+
"get-proto",
376
+
"gopd",
377
+
"has-symbols",
378
+
"hasown",
379
+
"math-intrinsics"
380
+
]
381
+
},
382
+
"get-proto@1.0.1": {
383
+
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
384
+
"dependencies": [
385
+
"dunder-proto",
386
+
"es-object-atoms"
387
+
]
388
+
},
389
+
"glob@7.2.3": {
390
+
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
391
+
"dependencies": [
392
+
"fs.realpath",
393
+
"inflight",
394
+
"inherits",
395
+
"minimatch",
396
+
"once",
397
+
"path-is-absolute"
398
+
],
399
+
"deprecated": true
400
+
},
401
+
"gopd@1.2.0": {
402
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
403
+
},
404
+
"has-flag@4.0.0": {
405
+
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
406
+
},
407
+
"has-symbols@1.1.0": {
408
+
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
409
+
},
410
+
"hasown@2.0.2": {
411
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
412
+
"dependencies": [
413
+
"function-bind"
414
+
]
415
+
},
416
+
"he@1.2.0": {
417
+
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
418
+
"bin": true
419
+
},
420
+
"html-encoding-sniffer@3.0.0": {
421
+
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
422
+
"dependencies": [
423
+
"whatwg-encoding"
424
+
]
425
+
},
426
+
"http-proxy@1.18.1": {
427
+
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
428
+
"dependencies": [
429
+
"eventemitter3",
430
+
"follow-redirects",
431
+
"requires-port"
432
+
]
433
+
},
434
+
"http-server@14.1.1": {
435
+
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
436
+
"dependencies": [
437
+
"basic-auth",
438
+
"chalk",
439
+
"corser",
440
+
"he",
441
+
"html-encoding-sniffer",
442
+
"http-proxy",
443
+
"mime",
444
+
"minimist",
445
+
"opener",
446
+
"portfinder",
447
+
"secure-compare",
448
+
"union",
449
+
"url-join"
450
+
],
451
+
"bin": true
452
+
},
453
+
"iconv-lite@0.6.3": {
454
+
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
455
+
"dependencies": [
456
+
"safer-buffer"
457
+
]
458
+
},
459
+
"inflight@1.0.6": {
460
+
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
461
+
"dependencies": [
462
+
"once",
463
+
"wrappy"
464
+
],
465
+
"deprecated": true
466
+
},
467
+
"inherits@2.0.4": {
468
+
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
469
+
},
470
+
"is-fullwidth-code-point@3.0.0": {
471
+
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
472
+
},
473
+
"isarray@0.0.1": {
474
+
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
475
+
},
476
+
"isarray@1.0.0": {
477
+
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
478
+
},
479
+
"math-intrinsics@1.1.0": {
480
+
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
481
+
},
482
+
"mime@1.6.0": {
483
+
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
484
+
"bin": true
485
+
},
486
+
"minimatch@3.1.2": {
487
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
488
+
"dependencies": [
489
+
"brace-expansion"
490
+
]
491
+
},
492
+
"minimist@1.2.8": {
493
+
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
494
+
},
495
+
"mkdirp@1.0.4": {
496
+
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
497
+
"bin": true
498
+
},
499
+
"ms@2.1.3": {
500
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
501
+
},
502
+
"noms@0.0.0": {
503
+
"integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==",
504
+
"dependencies": [
505
+
"inherits",
506
+
"readable-stream@1.0.34"
507
+
]
508
+
},
509
+
"object-inspect@1.13.4": {
510
+
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
511
+
},
512
+
"once@1.4.0": {
513
+
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
514
+
"dependencies": [
515
+
"wrappy"
516
+
]
517
+
},
518
+
"opener@1.5.2": {
519
+
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
520
+
"bin": true
521
+
},
522
+
"path-is-absolute@1.0.1": {
523
+
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
524
+
},
525
+
"portfinder@1.0.37": {
526
+
"integrity": "sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==",
527
+
"dependencies": [
528
+
"async",
529
+
"debug"
530
+
]
531
+
},
532
+
"process-nextick-args@2.0.1": {
533
+
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
534
+
},
535
+
"qs@6.14.0": {
536
+
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
537
+
"dependencies": [
538
+
"side-channel"
539
+
]
540
+
},
541
+
"readable-stream@1.0.34": {
542
+
"integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
543
+
"dependencies": [
544
+
"core-util-is",
545
+
"inherits",
546
+
"isarray@0.0.1",
547
+
"string_decoder@0.10.31"
548
+
]
549
+
},
550
+
"readable-stream@2.3.8": {
551
+
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
552
+
"dependencies": [
553
+
"core-util-is",
554
+
"inherits",
555
+
"isarray@1.0.0",
556
+
"process-nextick-args",
557
+
"safe-buffer",
558
+
"string_decoder@1.1.1",
559
+
"util-deprecate"
560
+
]
561
+
},
562
+
"require-directory@2.1.1": {
563
+
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
564
+
},
565
+
"requires-port@1.0.0": {
566
+
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
567
+
},
568
+
"safe-buffer@5.1.2": {
569
+
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
570
+
},
571
+
"safer-buffer@2.1.2": {
572
+
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
573
+
},
574
+
"secure-compare@3.0.1": {
575
+
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw=="
576
+
},
577
+
"side-channel-list@1.0.0": {
578
+
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
579
+
"dependencies": [
580
+
"es-errors",
581
+
"object-inspect"
582
+
]
583
+
},
584
+
"side-channel-map@1.0.1": {
585
+
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
586
+
"dependencies": [
587
+
"call-bound",
588
+
"es-errors",
589
+
"get-intrinsic",
590
+
"object-inspect"
591
+
]
592
+
},
593
+
"side-channel-weakmap@1.0.2": {
594
+
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
595
+
"dependencies": [
596
+
"call-bound",
597
+
"es-errors",
598
+
"get-intrinsic",
599
+
"object-inspect",
600
+
"side-channel-map"
601
+
]
602
+
},
603
+
"side-channel@1.1.0": {
604
+
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
605
+
"dependencies": [
606
+
"es-errors",
607
+
"object-inspect",
608
+
"side-channel-list",
609
+
"side-channel-map",
610
+
"side-channel-weakmap"
611
+
]
612
+
},
613
+
"string-width@4.2.3": {
614
+
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
615
+
"dependencies": [
616
+
"emoji-regex",
617
+
"is-fullwidth-code-point",
618
+
"strip-ansi"
619
+
]
620
+
},
621
+
"string_decoder@0.10.31": {
622
+
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
623
+
},
624
+
"string_decoder@1.1.1": {
625
+
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
626
+
"dependencies": [
627
+
"safe-buffer"
628
+
]
629
+
},
630
+
"strip-ansi@6.0.1": {
631
+
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
632
+
"dependencies": [
633
+
"ansi-regex"
634
+
]
635
+
},
636
+
"supports-color@7.2.0": {
637
+
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
638
+
"dependencies": [
639
+
"has-flag"
640
+
]
641
+
},
642
+
"through2@2.0.5": {
643
+
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
644
+
"dependencies": [
645
+
"readable-stream@2.3.8",
646
+
"xtend"
647
+
]
648
+
},
649
+
"typescript@5.9.2": {
650
+
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
651
+
"bin": true
652
+
},
653
+
"union@0.5.0": {
654
+
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
655
+
"dependencies": [
656
+
"qs"
657
+
]
658
+
},
659
+
"untildify@4.0.0": {
660
+
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="
661
+
},
662
+
"url-join@4.0.1": {
663
+
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
664
+
},
665
+
"util-deprecate@1.0.2": {
666
+
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
667
+
},
668
+
"whatwg-encoding@2.0.0": {
669
+
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
670
+
"dependencies": [
671
+
"iconv-lite"
672
+
]
673
+
},
674
+
"wrap-ansi@7.0.0": {
675
+
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
676
+
"dependencies": [
677
+
"ansi-styles",
678
+
"string-width",
679
+
"strip-ansi"
680
+
]
681
+
},
682
+
"wrappy@1.0.2": {
683
+
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
684
+
},
685
+
"xtend@4.0.2": {
686
+
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
687
+
},
688
+
"y18n@5.0.8": {
689
+
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
690
+
},
691
+
"yargs-parser@20.2.9": {
692
+
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
693
+
},
694
+
"yargs@16.2.0": {
695
+
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
696
+
"dependencies": [
697
+
"cliui",
698
+
"escalade",
699
+
"get-caller-file",
700
+
"require-directory",
701
+
"string-width",
702
+
"y18n",
703
+
"yargs-parser"
704
+
]
705
+
}
706
+
},
707
+
"remote": {
708
+
"https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
709
+
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
710
+
"https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293",
711
+
"https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7",
712
+
"https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74",
713
+
"https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd",
714
+
"https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff",
715
+
"https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46",
716
+
"https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b",
717
+
"https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c",
718
+
"https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491",
719
+
"https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68",
720
+
"https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3",
721
+
"https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7",
722
+
"https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29",
723
+
"https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a",
724
+
"https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a",
725
+
"https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8",
726
+
"https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693",
727
+
"https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31",
728
+
"https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5",
729
+
"https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8",
730
+
"https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb",
731
+
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
732
+
"https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
733
+
"https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68",
734
+
"https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3",
735
+
"https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73",
736
+
"https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19",
737
+
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
738
+
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
739
+
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
740
+
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e"
741
+
},
742
+
"workspace": {
743
+
"packageJson": {
744
+
"dependencies": [
745
+
"npm:@atcute/atproto@^3.1.1",
746
+
"npm:@atcute/bluesky@^3.2.0",
747
+
"npm:@atcute/client@^4.0.3",
748
+
"npm:@atcute/identity-resolver@^1.1.3",
749
+
"npm:@atcute/identity@^1.0.2",
750
+
"npm:copyfiles@^2.4.1",
751
+
"npm:esbuild@~0.25.8",
752
+
"npm:http-server@^14.1.1",
753
+
"npm:typescript@^5.8.3"
754
+
]
755
+
}
756
+
}
757
+
}
+61
flake.lock
+61
flake.lock
···
1
+
{
2
+
"nodes": {
3
+
"flake-utils": {
4
+
"inputs": {
5
+
"systems": "systems"
6
+
},
7
+
"locked": {
8
+
"lastModified": 1731533236,
9
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
10
+
"owner": "numtide",
11
+
"repo": "flake-utils",
12
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
13
+
"type": "github"
14
+
},
15
+
"original": {
16
+
"owner": "numtide",
17
+
"repo": "flake-utils",
18
+
"type": "github"
19
+
}
20
+
},
21
+
"nixpkgs": {
22
+
"locked": {
23
+
"lastModified": 1755615617,
24
+
"narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=",
25
+
"owner": "NixOS",
26
+
"repo": "nixpkgs",
27
+
"rev": "20075955deac2583bb12f07151c2df830ef346b4",
28
+
"type": "github"
29
+
},
30
+
"original": {
31
+
"owner": "NixOS",
32
+
"ref": "nixos-unstable",
33
+
"repo": "nixpkgs",
34
+
"type": "github"
35
+
}
36
+
},
37
+
"root": {
38
+
"inputs": {
39
+
"flake-utils": "flake-utils",
40
+
"nixpkgs": "nixpkgs"
41
+
}
42
+
},
43
+
"systems": {
44
+
"locked": {
45
+
"lastModified": 1681028828,
46
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
47
+
"owner": "nix-systems",
48
+
"repo": "default",
49
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
50
+
"type": "github"
51
+
},
52
+
"original": {
53
+
"owner": "nix-systems",
54
+
"repo": "default",
55
+
"type": "github"
56
+
}
57
+
}
58
+
},
59
+
"root": "root",
60
+
"version": 7
61
+
}
+19
flake.nix
+19
flake.nix
···
1
+
{
2
+
inputs = {
3
+
flake-utils.url = "github:numtide/flake-utils";
4
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
5
+
};
6
+
7
+
outputs = inputs:
8
+
inputs.flake-utils.lib.eachDefaultSystem (system:
9
+
let
10
+
pkgs = (import (inputs.nixpkgs) { inherit system; });
11
+
in {
12
+
devShell = pkgs.mkShell {
13
+
buildInputs = with pkgs; [
14
+
deno rsync
15
+
];
16
+
};
17
+
}
18
+
);
19
+
}
+4
-4
package.json
+4
-4
package.json
···
14
14
"@atcute/identity-resolver": "^1.1.3"
15
15
},
16
16
"scripts": {
17
-
"cert": "mkdir certs && cd certs && mkcert 127.0.0.1",
18
-
"serve": "http-server ./out -C ./certs/127.0.0.1.pem -K ./certs/127.0.0.1-key.pem",
19
-
"build": "esbuild src/* --bundle --outdir=out && rsync -av static/ out/",
20
-
"build-prod": "esbuild src/* --minify --tree-shaking=true --bundle --outdir=out && rsync -av static/ out/"
17
+
"serve": "http-server ./out -C",
18
+
"build": "deno bundle --platform=browser src/*.ts --outdir=out --sourcemap=inline && rsync -av static/ out/",
19
+
"build-prod": "deno bundle --platform=browser src/*.ts --outdir=out --minify && rsync -av static/ out/",
20
+
"test": "deno test test/**/*.ts"
21
21
}
22
22
}
+12
readme
+12
readme
···
18
18
hey! heads up! im just thinking that as i add things someone might go "wow i want to use that"
19
19
i dont know how packaging works (yet). if you can somehow pull my code into your project as it stands, expect it to break.
20
20
its not good for your health. just dont. not yet?
21
+
22
+
23
+
> I have learned that the name you give something when you don't know
24
+
> what you should call it should always end up being the final name and
25
+
> that's also the case if objectively, it sucks.
26
+
- @poggers.website
27
+
at://did:plc:7ggpsji45es4auo5pqjmnarr/app.bsky.feed.post/3lx5d3p4oos2n
28
+
29
+
bro idk what this is anymore but im a fan of it and will keep going
30
+
31
+
i really gotta stop using Node.wraps...
32
+
that literally means that my implementation around it is inadequate
-8
shell.nix
-8
shell.nix
+31
src/desktop.ts
+31
src/desktop.ts
···
1
+
import { Body, Button, Column, Input, Link, Row } from "./domlink.ts";
2
+
import { resolveMiniDoc } from "./support/slingshot.ts";
3
+
import { Window } from "./windowing_mod.ts";
4
+
import { Feed } from "./windows/bluesky/feed.ts";
5
+
6
+
const about = new Window("About", 150).with(
7
+
new Column().with(
8
+
new Row().with(`"Domlink"/toybox project by `, new Link("@hotsocket.fyi").to("https://bsky.app/profile/hotsocket.fyi")),
9
+
new Link("Source code here!").to("https://tangled.sh/@hotsocket.fyi/domlink"),
10
+
"Copyrighted by default(?), no license chosen or anything atp."
11
+
)
12
+
).closable(false); // haha
13
+
14
+
const userInput = new Input();
15
+
const authorFeedWindowCreator = new Row().with(
16
+
userInput,
17
+
new Button("Get Author Feed", async () => {
18
+
const doc = await resolveMiniDoc(userInput.value);
19
+
const feed = new Feed();
20
+
Body.with(new Window("Posts by @"+doc.handle).with(feed));
21
+
await feed.loadFeed(Feed.createAuthorGenerator(doc.did)); // separated out to allow for updating
22
+
})
23
+
);
24
+
const instantiator = new Column().with(
25
+
authorFeedWindowCreator,
26
+
).style((x) => x.maxWidth = "100ch");
27
+
28
+
Body.with(
29
+
instantiator,
30
+
about
31
+
);
+61
-23
src/domlink.ts
+61
-23
src/domlink.ts
···
1
-
let _LCounter = 0; // is there a point to this? idk. might be unnecessary overhead.
1
+
let _LCounter = 0;
2
+
const NodeMap = new WeakMap<HTMLElement, Node>();
3
+
4
+
/** Core wrapper for {@link HTMLElement DOM elements}. */
2
5
export class Node {
3
6
wraps: HTMLElement;
4
7
children: Node[] = [];
5
-
id: string = "L" + _LCounter++;;
6
-
constructor(wrap: HTMLElement) {
7
-
this.wraps = wrap;
8
+
id: string = "L" + _LCounter++;
9
+
/**
10
+
* Creates a node, wrapping an HTML element.
11
+
* @example new Node(document.createElement("h1"));
12
+
*/
13
+
constructor(wraps: HTMLElement) {
14
+
this.wraps = wraps;
8
15
this.wraps.id = this.id;
9
16
}
10
-
// could i make add and with one function? yeah.
11
-
// will i do that? no.
12
-
// why not? because it feels nice to have them separate.
13
-
add(node: Node | string) {
17
+
/** Adds a child node. Should not be overridden. */
18
+
add(node: NodeEquivalent) {
14
19
if (node != undefined) {
15
20
let xnode: Node;
16
21
if (typeof node === "string") {
···
25
30
}
26
31
return this;
27
32
}
28
-
remove(node: Node) {
33
+
/** Works similarly to {@link add}, but takes multiple nodes and is the one you would want to override. */
34
+
with(...nodes: NodeEquivalent[]) {
35
+
nodes.forEach(x=>{this.add(x)});
36
+
return this;
37
+
}
38
+
/** Removes a child node in the same way {@link add} does. */
39
+
delete(node: Node) {
29
40
let idx = this.children.indexOf(node);
30
41
if (idx < 0) {
31
-
throw new Error("node not a child of this node");
42
+
throw new Error(`Node ${node.id} not found.`);
32
43
}
33
44
this.children.splice(idx, 1);
34
45
this.wraps.removeChild(node.wraps);
35
46
}
36
-
with(...nodes: (Node | string)[]) {
37
-
nodes.forEach(x=>{this.add(x)});
38
-
return this
39
-
}
40
-
style(fn: (style: CSSStyleDeclaration) => void) {
47
+
/** Removes one or more child nodes, like {@link with} does adding them. */
48
+
remove(...nodes: Node[]) {
49
+
nodes.forEach(x=>{this.delete(x)});
50
+
return this;
51
+
}
52
+
/** Conveniently chain-able callback thing for styling your nodes.
53
+
* @example new Label("This is a label!").style((s)=>{s.color="maroon";}) */
54
+
style(fn: (style: CSSStyleDeclaration) => void) {
41
55
fn(this.wraps.style);
42
56
return this;
43
57
}
58
+
/** Adds a CSS class to your node.
59
+
* @example new Container().class("my-class") */
44
60
class(cls: string) {
45
61
this.wraps.classList.add(cls);
46
62
return this;
47
63
}
64
+
/** Hopefully, this points at the parent element. */
65
+
get parent(): Node | undefined{
66
+
if (this.wraps.parentElement == null) return undefined;
67
+
return NodeMap.get(this.wraps.parentElement);
68
+
}
48
69
}
49
70
type NodeEquivalent = (Node | string);
50
71
enum LinkTarget {
···
52
73
NEW_TAB = "_blank",
53
74
OUT_FRAME = "_parent",
54
75
NOT_FRAME = "_top"
55
-
};
76
+
}
77
+
/** Wrapper for {@link HTMLAnchorElement `<a>`} */
56
78
export class Link extends Node {
57
79
constructor(around: NodeEquivalent | null) {
58
80
let a = document.createElement("a");
···
65
87
set destination(x: string) {
66
88
(this.wraps as HTMLAnchorElement).href = x;
67
89
}
68
-
get destination(): string {
90
+
// noinspection JSUnusedGlobalSymbols
91
+
get destination(): string {
69
92
return (this.wraps as HTMLAnchorElement).href;
70
93
}
71
94
set target(x: string) {
···
81
104
}
82
105
83
106
}
107
+
/** Wrapper for {@link HTMLDivElement `<div>`} */
84
108
export class Container extends Node {
85
109
constructor() {
86
110
let div = document.createElement("div");
···
88
112
super(div);
89
113
}
90
114
}
115
+
/** Vertical Flexbox container */
91
116
export class Column extends Container {
92
117
constructor() {
93
118
super();
94
119
this.wraps.classList.add("LColumn");
95
120
}
96
121
}
122
+
/** Horizontal Flexbox container */
97
123
export class Row extends Container {
98
124
constructor() {
99
125
super();
100
126
this.wraps.classList.add("LRow");
101
127
}
102
128
}
129
+
/** Wrapper for {@link HTMLTableElement `<table>`} */
103
130
export class Table extends Node {
104
131
constructor() {
105
132
let table = document.createElement("table");
···
107
134
super(table);
108
135
}
109
136
}
137
+
/** Wrapper for {@link HTMLTableRowElement `<tr>`} */
110
138
export class TableRow extends Node {
111
139
constructor() {
112
140
let tr = document.createElement("tr");
···
114
142
super(tr);
115
143
}
116
144
}
145
+
/** Wrapper for {@link HTMLTableCellElement `<td>`} */
117
146
export class TableCell extends Node {
118
147
constructor() {
119
148
let td = document.createElement("td");
···
121
150
super(td);
122
151
}
123
152
}
153
+
/** An "abstract" class of sorts for {@link HTMLElement}s that have a textContent member. */
124
154
export class Text extends Node {
125
155
private _text: string = "";
126
156
get text() {
···
131
161
this.wraps.textContent = x;
132
162
}
133
163
}
164
+
/** Wrapper for {@link HTMLSpanElement `<span>`} */
134
165
export class Label extends Text {
135
166
constructor(text: string = "") {
136
167
let el = document.createElement("span");
···
139
170
this.text = text;
140
171
}
141
172
}
173
+
/** Wrapper for {@link HTMLButtonElement `<button>`} */
142
174
export class Button extends Text {
143
175
constructor(label: string, action: EventListener) {
144
176
let btn = document.createElement("button");
···
149
181
this.text = label;
150
182
}
151
183
}
152
-
interface Updatable {
153
-
watch: (watcher: (newValue: string)=>void) => void;
184
+
/** Interface providing {@link Updatable.watch} */
185
+
interface Updatable<T> {
186
+
/** Takes a callback `watcher` that is called when something like a text input is updated. */
187
+
watch: (watcher: (newValue: T)=>void) => void;
154
188
}
155
-
export class Input extends Text implements Updatable {
189
+
export class Input extends Text implements Updatable<string> {
156
190
constructor(type: HTMLInputElement["type"] = "text") {
157
191
let el = document.createElement("input");
158
192
el.type = type;
···
172
206
173
207
174
208
175
-
// Implementation for Updatable
176
209
watching: ((newValue: string) => void)[] = [];
210
+
/** Implementation for {@link Updatable.watch} */
177
211
watch(watcher: (newValue: string)=>void) {
178
212
this.watching.push(watcher);
179
-
this.wraps.oninput = (e)=>{watcher(this.value)};
213
+
this.wraps.oninput = ()=>{watcher(this.value)};
180
214
watcher(this.value);
181
215
}
182
216
}
217
+
/** Wrapper for {@link HTMLImageElement `<img>`} */
183
218
export class Image extends Node {
184
219
constructor(src: string) {
185
220
let img = document.createElement("img");
···
188
223
super(img);
189
224
}
190
225
}
226
+
/** Wrapper for {@link HTMLDialogElement `<dialog>`} with methods to show/hide it as a modal. */
191
227
export class Modal extends Node {
192
228
constructor() {
193
229
let dlg: HTMLDialogElement = document.createElement("dialog");
194
230
dlg.classList.add("LModal");
195
231
super(dlg);
196
232
}
233
+
/** Shows the dialog as a modal. */
197
234
show() {
198
235
Body.add(this);
199
236
(this.wraps as HTMLDialogElement).showModal();
200
237
}
238
+
/** Hides the dialog. */
201
239
hide() {
202
240
(this.wraps as HTMLDialogElement).close();
203
241
Body.wraps.removeChild(this.wraps);
204
242
}
205
243
}
206
244
207
-
// user helper shit
245
+
/** Convenience wrapper for the {@link HTMLBodyElement body} of the document. */
208
246
export const Body = new Node(document.body);
+29
src/extras.ts
+29
src/extras.ts
···
1
+
import { Row, Button, Column, Node } from "./domlink.ts";
2
+
3
+
// extra little handy things i guess.
4
+
export async function timeout<T>(time: number, promise: Promise<T>): Promise<T> {
5
+
const result = await Promise.race([new Promise<null>(r=>setTimeout(r, time, null)), promise]);
6
+
if (result == null) {
7
+
throw new Error("timeout");
8
+
}
9
+
return result;
10
+
}
11
+
12
+
function webFrame(url: string) {
13
+
const frame = document.createElement("iframe");
14
+
frame.src = url;
15
+
frame.style.width = "100%";
16
+
frame.style.height = "100%";
17
+
const controls = new Row().with(
18
+
"Web Frame Controls:",
19
+
new Button("reload", ()=>{frame.contentWindow?.location.reload();})
20
+
);
21
+
22
+
return new Column().with(
23
+
controls,
24
+
new Node(frame)
25
+
).style((s)=>{
26
+
s.width = "100%";
27
+
s.height = "100%";
28
+
});
29
+
}
-199
src/issuesearch.ts
-199
src/issuesearch.ts
···
1
-
import { Body, Button, Column, Input, Label, Link, Row, Table, TableCell, TableRow } from "./domlink.js";
2
-
Body.with(new Link("TypeScript source here!").to("https://tangled.sh/@hotsocket.fyi/domlink/blob/main/src/issuesearch.ts"));
3
-
type issueRecord = {
4
-
body: string;
5
-
createdAt: string;
6
-
issueId: number;
7
-
owner: string;
8
-
repo: string;
9
-
title: string;
10
-
};
11
-
type constellationRecordItem = {
12
-
did: string;
13
-
collection: string;
14
-
rkey: string;
15
-
};
16
-
type constellationResponse = {
17
-
total: number;
18
-
linking_records: [constellationRecordItem];
19
-
cursor: string;
20
-
};
21
-
type slingshotResponse<T> = {
22
-
cid: string;
23
-
uri: string;
24
-
value: T;
25
-
};
26
-
type miniDoc = {
27
-
did: string;
28
-
handle: string;
29
-
pds: string;
30
-
signing_key: string;
31
-
};
32
-
type repoRecord = {
33
-
knot: string;
34
-
name: string;
35
-
owner: string;
36
-
createdAt: string;
37
-
};
38
-
type recordListingItem<T> = {
39
-
uri: string;
40
-
cid: string;
41
-
value: T;
42
-
};
43
-
type recordListing<T> = {
44
-
cursor: string;
45
-
records: [recordListingItem<T>];
46
-
};
47
-
48
-
async function xcall(host: string, method: string, params: Record<string, string | number> | null = null): Promise<unknown> {
49
-
let url = `${host}/xrpc/${method}`;
50
-
if (params) {
51
-
let usp = new URLSearchParams();
52
-
for (let key in params) {
53
-
usp.append(key, params[key].toString());
54
-
}
55
-
url += "?" + usp.toString();
56
-
}
57
-
return await (await fetch(url)).json();
58
-
}
59
-
60
-
type issueItem = ({handle: string;issue: issueRecord});
61
-
let allIssues:issueItem[] = [];
62
-
const SLINGSHOT = "https://slingshot.microcosm.blue";
63
-
let currentRepoLink = ""; // essentially for issue links. ensures any extras are stripped off.
64
-
async function getIssues(repo: string) {
65
-
let repoUrl = new URL(repo);
66
-
let repoUrlSplat = repoUrl.pathname.split("/");
67
-
let repoOwnerHandle = repoUrlSplat[1].substring(1);
68
-
let repoName = repoUrlSplat[2];
69
-
currentRepoLink = `https://tangled.sh/@${repoOwnerHandle}/${repoName}`;
70
-
statusText.text = "resolving owner did";
71
-
let repoOwnerDoc = await xcall(SLINGSHOT, "com.bad-example.identity.resolveMiniDoc", {identifier: repoOwnerHandle}) as miniDoc;
72
-
statusText.text = "finding repository";
73
-
let ownedRepos = await xcall(repoOwnerDoc.pds, "com.atproto.repo.listRecords", { repo: repoOwnerDoc.did, collection: "sh.tangled.repo" }) as recordListing<repoRecord>;
74
-
let repoRecord = ownedRepos.records.find(x=>x.value.name == repoName)!;
75
-
let encodedRepoUri = encodeURIComponent(repoRecord.uri);
76
-
statusText.text = `finding issues...`;
77
-
let irsp = await (await fetch(`https://constellation.microcosm.blue/links?target=${encodedRepoUri}&collection=sh.tangled.repo.issue&path=.repo`)).json() as constellationResponse;
78
-
let allIssueRefs: constellationRecordItem[] = irsp.linking_records;
79
-
let nextCursor = irsp.cursor;
80
-
while (allIssueRefs.length < irsp.total && nextCursor != null) {
81
-
statusText.text = `finding issues... (${allIssueRefs.length}/${irsp.total})`;
82
-
let rsp = await (await fetch(`https://constellation.microcosm.blue/links?target=${encodedRepoUri}&collection=sh.tangled.repo.issue&path=.repo&cursor=${nextCursor}`, {headers:{Accept:"application/json"}})).json() as constellationResponse;
83
-
nextCursor = rsp.cursor;
84
-
allIssueRefs = allIssueRefs.concat(rsp.linking_records);
85
-
if (nextCursor == null) break;
86
-
}
87
-
allIssues = (await Promise.all(allIssueRefs.map(async (issueRef) => {
88
-
let rsp = await xcall(SLINGSHOT, "com.atproto.repo.getRecord", {
89
-
repo: issueRef.did,
90
-
collection: issueRef.collection,
91
-
rkey: issueRef.rkey
92
-
}) as slingshotResponse<issueRecord>;
93
-
let issue = rsp.value;
94
-
let doc;
95
-
try {
96
-
doc = await xcall(SLINGSHOT, "com.bad-example.identity.resolveMiniDoc", { identifier: issue.owner }) as miniDoc;
97
-
} catch (error) {
98
-
return null;
99
-
}
100
-
statusText.text = `retrieved issue #${issue.issueId} (not in order!)`;
101
-
let handle = doc.handle;
102
-
return {
103
-
handle: handle,
104
-
issue: issue
105
-
};
106
-
}))).filter(x=>x as issueItem).sort((a,b)=>{
107
-
let x = 0;
108
-
if (a && b) {
109
-
x = b.issue.issueId-a.issue.issueId
110
-
}
111
-
return x;
112
-
}) as issueItem[];
113
-
statusText.text = `got ${allIssues.length} issues!`;
114
-
}
115
-
function renderIssues(issues:issueItem[]) {
116
-
let view_issues_list = new Table().add(
117
-
new TableRow().with(
118
-
new TableCell().add("Handle"),
119
-
new TableCell().add("Issue#"),
120
-
new TableCell().add("Title")
121
-
)
122
-
);
123
-
issues.forEach(issue =>{
124
-
if (issue) {
125
-
let view_issue_row = new TableRow();
126
-
view_issue_row.with(
127
-
new TableCell().add(new Link(issue.handle).to(`https://tangled.sh/@${issue.handle}`)),
128
-
new TableCell().add(new Link(issue.issue.issueId.toString()).to(`${currentRepoLink}/issues/${issue.issue.issueId}`)),
129
-
new TableCell().add(issue.issue.title)
130
-
);
131
-
view_issues_list.add(view_issue_row);
132
-
}
133
-
});
134
-
return view_issues_list;
135
-
}
136
-
137
-
138
-
let repoUrl = new Input();
139
-
let last: any;
140
-
let statusText = new Label("waiting");
141
-
let runButton = new Button("GO!",async ()=>{
142
-
(runButton.wraps as HTMLButtonElement).disabled = true;
143
-
if (last) {
144
-
try {
145
-
Body.remove(last);
146
-
} catch {}
147
-
last = null;
148
-
}
149
-
allIssues = [];
150
-
searchRow.style(x=>x.display="none");
151
-
try {
152
-
await getIssues(repoUrl.value);
153
-
console.log(allIssues);
154
-
last = renderIssues(allIssues);
155
-
searchRow.style(x=>x.removeProperty("display"));
156
-
} catch (e) {
157
-
last = new Label("Failed");
158
-
console.log(e);
159
-
}
160
-
Body.add(last);
161
-
searchInput.value = "";
162
-
console.log(last);
163
-
(runButton.wraps as HTMLButtonElement).disabled = false;
164
-
});
165
-
166
-
167
-
168
-
let searchInput = new Input();
169
-
searchInput.watch((search)=>{
170
-
if (allIssues.length > 0) {
171
-
if (last) {
172
-
Body.remove(last);
173
-
}
174
-
search = search.toLowerCase();
175
-
last = renderIssues(allIssues.filter((item)=>{
176
-
return item.issue.issueId == Number(item) ||
177
-
search.split(" ").every((word)=> (
178
-
item.handle.toLowerCase().includes(word) ||
179
-
item.issue.title.toLowerCase().includes(word) ||
180
-
item.issue.body.toLowerCase().includes(word)))
181
-
}));
182
-
Body.add(last);
183
-
}
184
-
});
185
-
let searchRow = new Row().with(
186
-
"search",
187
-
searchInput
188
-
);
189
-
searchRow.style(x=>x.display="none");
190
-
191
-
192
-
193
-
Body.add(new Row().with(
194
-
"List issues for repo: ",
195
-
repoUrl,
196
-
runButton,
197
-
statusText
198
-
));
199
-
Body.add(searchRow);
-107
src/main.ts
-107
src/main.ts
···
1
-
// yeah this is mildly busted so dont get too excited
2
-
3
-
import { Client, ok, simpleFetchHandler, AtpSessionData, CredentialManager } from "@atcute/client";
4
-
import { Body, Button, Column, Container, Image, Input, Label, Modal, Row } from "./domlink.js";
5
-
import { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/bluesky';
6
-
import { CompositeDidDocumentResolver, CompositeHandleResolver, DohJsonHandleResolver, PlcDidDocumentResolver, WebDidDocumentResolver, WellKnownHandleResolver } from "@atcute/identity-resolver";
7
-
8
-
const handleResolver = new CompositeHandleResolver({
9
-
strategy: 'race',
10
-
methods: {
11
-
dns: new DohJsonHandleResolver({ dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query' }),
12
-
http: new WellKnownHandleResolver(),
13
-
},
14
-
});
15
-
const docResolver = new CompositeDidDocumentResolver({
16
-
methods: {
17
-
plc: new PlcDidDocumentResolver(),
18
-
web: new WebDidDocumentResolver(),
19
-
},
20
-
});
21
-
const handler = simpleFetchHandler({service: "https://public.api.zeppelin.social"});
22
-
const client = new Client({handler});
23
-
let manager: CredentialManager | undefined;
24
-
const MY_DID = "did:plc:jlplwn5pi4dqrls7i6dx2me7";
25
-
26
-
class PostView extends Container {
27
-
constructor(post: AppBskyFeedDefs.PostView) {
28
-
super();
29
-
let record: AppBskyFeedPost.Main = post.record as AppBskyFeedPost.Main;
30
-
this.with(
31
-
new Row().with(
32
-
new Image(post.author.avatar ?? "/media/default-avatar.png").class("Avatar"),
33
-
new Column().with(
34
-
post.author.displayName ?? post.author.handle,
35
-
record.text,
36
-
new Row().with(
37
-
`๐จ๏ธ ${post.replyCount} `,
38
-
`๐ ${post.repostCount} `,
39
-
`๐ ${post.quoteCount} `,
40
-
`โค๏ธ ${post.likeCount} `,
41
-
)
42
-
),
43
-
)
44
-
);
45
-
this.wraps.classList.add("PostView");
46
-
}
47
-
}
48
-
async function login() {
49
-
let mdl = new Modal();
50
-
let handle = new Input("text");
51
-
handle.placeholder = "Handle";
52
-
let pass = new Input("password");
53
-
pass.placeholder = "Password";
54
-
let col = new Column().with(
55
-
"log in here",
56
-
handle,pass,
57
-
new Row().with(
58
-
new Button("cancel",()=>{mdl.hide();}),
59
-
new Button("log in",()=>{
60
-
61
-
}),
62
-
)
63
-
);
64
-
mdl.add(col).show();
65
-
}
66
-
67
-
class AccountPanel extends Container {
68
-
constructor(client: Client) {
69
-
super();
70
-
this.wraps.classList.add("AccountPanel");
71
-
72
-
const sessionJson = localStorage.getItem('atp-session');
73
-
if (sessionJson) {
74
-
const session: AtpSessionData = JSON.parse(sessionJson);
75
-
this.with(
76
-
new Label(`Logged in as ${session.handle}`),
77
-
new Button("Log out", () => {
78
-
localStorage.removeItem('atp-session');
79
-
window.location.reload();
80
-
})
81
-
);
82
-
} else {
83
-
this.add(new Button("Log in", () => {
84
-
login();
85
-
}));
86
-
}
87
-
}
88
-
}
89
-
90
-
async function main() {
91
-
const postsColumn = new Column().class("PostsColumn");
92
-
const mainColumn = new Column().with(
93
-
new AccountPanel(client),
94
-
new Label("who up skeeting they deck"),
95
-
postsColumn
96
-
);
97
-
Body.add(mainColumn);
98
-
99
-
// Fetch and display the feed
100
-
let data = await ok(client.get("app.bsky.feed.getAuthorFeed", {
101
-
params: {actor: MY_DID}
102
-
}));
103
-
const postViews = data.feed.map(post => new PostView(post.post));
104
-
postsColumn.with(...postViews);
105
-
}
106
-
107
-
main();
+2
-2
src/pds.ts
+2
-2
src/pds.ts
···
1
-
import { Body, Button, Column, Input, Label, Row } from "./domlink.js";
1
+
import { Body, Button, Column, Input, Label, Row } from "./domlink.ts";
2
2
import { DidDocument } from "@atcute/identity";
3
3
4
4
let input_source_pds = [
···
65
65
let runButton = new Button("GO!",async ()=>{
66
66
(runButton.wraps as HTMLButtonElement).disabled = true;
67
67
if (last) {
68
-
Body.remove(last);
68
+
Body.delete(last);
69
69
}
70
70
try {
71
71
last = await listActiveUsers(hostInput.value);
+106
src/support/atproto.ts
+106
src/support/atproto.ts
···
1
+
// skinny atproto types file for supporting constellation.ts
2
+
3
+
const DEFAULT_SERVICE = "https://api.bsky.app";
4
+
5
+
/** Type used to imply that a parameter will be run through {@link ValidateNSID} */
6
+
export type NSID = string;
7
+
const NSIDExpression = /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$/;
8
+
export function ValidateNSID(nsid: string): string | null {
9
+
return NSIDExpression.test(nsid) ? nsid : null;
10
+
}
11
+
12
+
export type DID = `did:${"web"|"plc"}:${string}`;
13
+
export function ValidateDID(did: string): string | null {
14
+
const parts = did.split(":");
15
+
const isValid = parts.length == 3 && parts[0] == "did" && (parts[1] == "plc" || parts[1] == "web") && parts[2].length > 0;
16
+
return isValid ? did : null;
17
+
}
18
+
19
+
export type AtURIString = `at://${string}/${string}/${string}`;
20
+
export class AtURI {
21
+
readonly authority: string | null;
22
+
readonly collection: string | null;
23
+
readonly rkey: string | null;
24
+
static fromString(uri: AtURIString): AtURI {
25
+
const parts = uri.split("/").slice(2);
26
+
return new AtURI(ValidateDID(parts[0]), ValidateNSID(parts[1]), parts[2]);
27
+
}
28
+
constructor(authority: string | null, collection: string | null, rkey: string | null) {
29
+
this.authority = authority;
30
+
this.collection = collection;
31
+
this.rkey = rkey;
32
+
}
33
+
/**
34
+
* Converts URI to at:// URI.
35
+
* @returns The string form of this URI, unless if any parts are specified without any preceding elements.
36
+
* @example ```
37
+
* // Invalid collection NSID, returns null.
38
+
* new AtURI("at://did:web:example.com/cheese/abc123").toString()
39
+
* // Invalid 'authority' DID, returns null.
40
+
* new AtURI("at://not-a-did/com.example.nsid").toString()
41
+
* // All good and happy, returns the string fed in.
42
+
* new AtURI("at://did:web:example.com/com.example.nsid/abc123").toString()
43
+
* ```
44
+
*/
45
+
toString(): string | null {
46
+
const ret: (string|null)[] = ["at://"];
47
+
// using `?? ""` to have a "bad" value to find
48
+
if (this.authority) {
49
+
ret.push(this.authority ?? "");
50
+
} else ret.push(null);
51
+
if (this.collection) {
52
+
if (ret.indexOf(null) != -1) {return null;}
53
+
ret.push("/");
54
+
ret.push(this.collection ?? "");
55
+
} else ret.push(null);
56
+
if (this.rkey) {
57
+
if (ret.indexOf(null) != -1) {return null;}
58
+
ret.push("/");
59
+
ret.push(this.rkey ?? "");
60
+
}
61
+
return ret.join("");
62
+
}
63
+
}
64
+
65
+
export type RecordResponse<T> = {
66
+
cid: string;
67
+
uri: AtURIString;
68
+
value: T;
69
+
}
70
+
71
+
// technically you can cast it to whatever you want but i feel like using a generic(?) makes it cleaner
72
+
/** Calls an XRPC "query" method (HTTP GET)
73
+
* @param service Defaults to {@link https://slingshot.microcosm.blue Slingshot}.
74
+
*/
75
+
export async function XQuery<T>(method: string, params: Record<string, string | number | null> | null = null, service: string = DEFAULT_SERVICE) {
76
+
let QueryURL = `${service}/xrpc/${method}`;
77
+
if (params) {
78
+
const usp = new URLSearchParams();
79
+
for (const key in params) {
80
+
if (params[key]) {
81
+
usp.append(key, params[key].toString());
82
+
}
83
+
}
84
+
QueryURL += "?" + usp.toString();
85
+
}
86
+
return (await (await fetch(QueryURL)).json()) as T;
87
+
}
88
+
/** Calls com.atproto.repo.getRecord with XQuery */
89
+
export async function GetRecord<T>(uri: AtURI, service: string = "https://slingshot.microcosm.blue") {
90
+
return await XQuery<RecordResponse<T>>("com.atproto.repo.getRecord", {
91
+
repo: uri.authority!,
92
+
collection: uri.collection!,
93
+
rkey: uri.rkey!
94
+
}, service);
95
+
}
96
+
type ListRecordsResponse<T> = {
97
+
cursor: string;
98
+
records: RecordResponse<T>[];
99
+
};
100
+
export async function ListRecords<T>(repo: string, collection: NSID, service: string, limit: number = 50) {
101
+
return await XQuery<ListRecordsResponse<T>>("com.atproto.repo.listRecords", {
102
+
repo: repo,
103
+
collection: collection,
104
+
limit: limit
105
+
}, service);
106
+
}
+6
src/support/bluesky.ts
+6
src/support/bluesky.ts
+39
src/support/constellation.ts
+39
src/support/constellation.ts
···
1
+
import { AtURI, DID, NSID, ValidateNSID } from "./atproto.ts";
2
+
3
+
const BASEURL = "https://constellation.microcosm.blue";
4
+
5
+
/** Essentially an AtURI, fed back by Constellation. */
6
+
export type Reference = {
7
+
did: string;
8
+
collection: string;
9
+
rkey: string;
10
+
}
11
+
12
+
/** Raw response type from /links */
13
+
type LinksResponse = {
14
+
total: number;
15
+
linking_records: Reference[];
16
+
cursor: string;
17
+
};
18
+
19
+
type Target = AtURI | DID;
20
+
/**
21
+
* Retrieves an array of record references to records containing links to the specified target.
22
+
* @throws When the provided NSID is invalid.
23
+
*/
24
+
export async function links(target: Target, collection: NSID, path: string): Promise<AtURI[]> {
25
+
if (ValidateNSID(collection) == null) {
26
+
throw new Error("invalid NSID for collection parameter");
27
+
}
28
+
const _target = encodeURIComponent(target.toString()!);
29
+
const _path = encodeURIComponent(path);
30
+
let cursor = "";
31
+
let records: AtURI[] = [];
32
+
while (cursor != null) {
33
+
const rsp = await fetch(`${BASEURL}/links?target=${_target.toString()}&collection=${collection}&path=${_path}${cursor ? `&cursor=${cursor}` : ""}`);
34
+
const data = await rsp.json() as LinksResponse;
35
+
records = records.concat(data.linking_records.map(x=>new AtURI(x.did, x.collection, x.rkey)));
36
+
cursor = data.cursor;
37
+
}
38
+
return records;
39
+
}
+21
src/support/slingshot.ts
+21
src/support/slingshot.ts
···
1
+
import { AtURI, DID, RecordResponse, XQuery } from "./atproto.ts";
2
+
3
+
const SLINGSHOT = "https://slingshot.microcosm.blue";
4
+
5
+
export async function resolveHandle(handle: string) {
6
+
const res = await XQuery("com.atproto.identity.resolveHandle", {handle: handle}, SLINGSHOT) as {did: DID};
7
+
return res.did;
8
+
}
9
+
10
+
export type MiniDoc = {
11
+
did: string;
12
+
handle: string;
13
+
pds: string;
14
+
signing_key: string;
15
+
};
16
+
export async function resolveMiniDoc(identifier: string) {
17
+
return await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", {identifier: identifier}, SLINGSHOT);
18
+
}
19
+
export async function getUriRecord<T>(at_uri: AtURI, cid: string | null = null) {
20
+
return await XQuery<RecordResponse<T>>("com.bad-example.repo.getRecord", {at_uri: at_uri.toString()!, cid: cid}, SLINGSHOT);
21
+
}
+80
src/support/tangled.ts
+80
src/support/tangled.ts
···
1
+
import * as x from "../extras.ts";
2
+
import { AtURI, ListRecords, XQuery } from "./atproto.ts";
3
+
import { getUriRecord, MiniDoc } from "./slingshot.ts";
4
+
import * as Constellation from "./constellation.ts";
5
+
6
+
export type Issue = {
7
+
body: string;
8
+
createdAt: string;
9
+
issueId: number;
10
+
owner: string;
11
+
repo: string;
12
+
title: string;
13
+
};
14
+
export type RepoRef = {
15
+
readonly owner: string;
16
+
readonly name: string;
17
+
};
18
+
type RepoCommon = {
19
+
readonly description: string;
20
+
readonly name: string;
21
+
readonly knot: string;
22
+
readonly spindle: string;
23
+
readonly createdAt: string;
24
+
}
25
+
type RawRepo = RepoCommon & {
26
+
readonly owner: string;
27
+
};
28
+
export type Repo = RepoCommon & {
29
+
readonly owner: MiniDoc; // The record itself returns a DID, but a MiniDoc is awfully handy.
30
+
readonly uri: AtURI;
31
+
};
32
+
33
+
export async function GetRepo(ref: RepoRef): Promise<Repo> {
34
+
const repoOwnerDoc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", {identifier: ref.owner});
35
+
const ownedRepos = await ListRecords<RawRepo>(repoOwnerDoc.did, "sh.tangled.repo", repoOwnerDoc.pds);
36
+
const repoRecord = ownedRepos.records.find(x=>x.value.name == ref.name)!;
37
+
return {
38
+
...repoRecord.value,
39
+
owner: repoOwnerDoc,
40
+
uri: AtURI.fromString(repoRecord.uri)
41
+
};
42
+
}
43
+
export async function GetIssues(repo: Repo, timeout: number = 10000) {
44
+
const allIssueRefs = await x.timeout(timeout, Constellation.links(repo.uri, "sh.tangled.repo.issue", ".repo"));
45
+
const issues = allIssueRefs.map(async (uri)=>{
46
+
try {
47
+
return (await x.timeout(timeout/5,getUriRecord<Issue>(uri))).value;
48
+
} catch {
49
+
console.log(`issue timed out: ${uri.toString()}`);
50
+
return null;
51
+
}
52
+
}).filter(x=>x!=null) as unknown as Issue[]; // despite filtering them out, typescript still thinks there could be nulls
53
+
return issues.sort((a,b)=>b.issueId-a.issueId);
54
+
}
55
+
56
+
/** Takes a string like `tangled.sh/core` or `https://tangled.sh/@tangled.sh/core` and returns a {@link RepoRef} */
57
+
export function StringRepoRef(repo: string): RepoRef {
58
+
// pick out repo info
59
+
const repoSplat = repo.split("//");
60
+
let repoOwner: string;
61
+
let repoName: string;
62
+
if (repoSplat.length == 1) {
63
+
const repoHalves = repo.split("/");
64
+
if (repoHalves.length == 1) throw new Error("invalid repo string");
65
+
repoOwner = repoHalves[0];
66
+
repoName = repoHalves[1];
67
+
} else {
68
+
if (repoSplat[0] == "https:") {
69
+
const repoUrlSplat = new URL(repo).pathname.split("/");
70
+
// [ "", "@tangled.sh", "core" ]
71
+
if (repoUrlSplat.length < 2) throw new Error("invalid repo url");
72
+
repoOwner = repoUrlSplat[1];
73
+
repoName = repoUrlSplat[2];
74
+
} else {
75
+
throw new Error("unknown repo uri scheme");
76
+
}
77
+
}
78
+
if (repoOwner.startsWith("@")) repoOwner = repoOwner.substring(1);
79
+
return {owner: repoOwner, name: repoName};
80
+
}
+38
src/windowing.ts
+38
src/windowing.ts
···
1
+
import { Button, Container, Body, Node, Row, Column, Image } from "./domlink.ts";
2
+
import { Window } from "./windowing_mod.ts";
3
+
4
+
function webFrame(url: string) {
5
+
const frame = document.createElement("iframe");
6
+
frame.src = url;
7
+
frame.style.width = "100%";
8
+
frame.style.height = "100%";
9
+
const controls = new Row().with(
10
+
"Web Frame Controls:",
11
+
new Button("reload", ()=>{frame.contentWindow?.location.reload();})
12
+
);
13
+
14
+
return new Column().with(
15
+
controls,
16
+
new Node(frame)
17
+
).style((s)=>{
18
+
s.width = "100%";
19
+
s.height = "100%";
20
+
});
21
+
}
22
+
const userListWindow = new Window("User List").with(webFrame("./pds.html"));
23
+
const issuesWindow = new Window("Tangled Issues").with(webFrame("./issuesearch.html"));
24
+
const feedWindow = new Window("My Posts").with(webFrame("./main.html"));
25
+
26
+
// Example usage to demonstrate the new Window class
27
+
Body.with(
28
+
"you know, i really should check if adding elements normally works",
29
+
userListWindow,
30
+
issuesWindow,
31
+
feedWindow,
32
+
new Window('"native" window').with(
33
+
new Column().with(
34
+
"this doesnt have a web frame in it, but it does have a dog",
35
+
new Image("./media/dog.jpeg")
36
+
)
37
+
)
38
+
);
+116
src/windowing_mod.ts
+116
src/windowing_mod.ts
···
1
+
import { string } from "../out/main.js";
2
+
import { Button, Container, Label, Node } from "./domlink.ts";
3
+
4
+
5
+
class TitleBar extends Container {
6
+
label: Label;
7
+
closeButton: Button;
8
+
constructor(title: string, closeCallback: EventListener) {
9
+
super();
10
+
this.class("LWindowHandle");
11
+
this.label = new Label(title);
12
+
this.closeButton = new Button("x", closeCallback);
13
+
this.with(this.label,this.closeButton);
14
+
}
15
+
get closable(): boolean {
16
+
return this.closeButton.wraps.style.display != "none";
17
+
}
18
+
set closable(x: boolean) {
19
+
this.closeButton.wraps.style.display = x ? "inline-block" : "none";
20
+
}
21
+
}
22
+
export class Window extends Container {
23
+
private static currentlyDragged: Window | null = null;
24
+
private static mouseRelX = 0;
25
+
private static mouseRelY = 0;
26
+
private static zIndexCounter = 100;
27
+
titleBar: TitleBar;
28
+
content = new Container().class("LWindowContent");
29
+
30
+
constructor(title: string = "New Window", height: number = 300, width: number = 400) {
31
+
super();
32
+
this.class("LWindow");
33
+
this.titleBar = new TitleBar(title, ()=>{this.wraps.remove();});
34
+
this.add(this.titleBar);
35
+
this.titleBar.wraps.addEventListener("mousedown", this.titleGrabHandler.bind(this));
36
+
this.wraps.addEventListener("mousedown", ()=>{this.wraps.style.zIndex = `${Window.zIndexCounter++}`;});
37
+
this.add(this.content);
38
+
this.style((s)=>{
39
+
s.width = `${width}px`;
40
+
s.height = `${height}px`;
41
+
s.top = "0px";
42
+
s.left = "0px";
43
+
});
44
+
}
45
+
override with(...nodes: (Node | string)[]): this {
46
+
const w = this.wraps.style.width;
47
+
const h = this.wraps.style.height;
48
+
this.content.with(...nodes);
49
+
this.wraps.style.width = w;
50
+
this.wraps.style.height = h;
51
+
return this;
52
+
}
53
+
54
+
get title() {
55
+
return (this.titleBar.children[0] as Label).text;
56
+
}
57
+
set title(newTitle: string) {
58
+
(this.titleBar.children[0] as Label).text = newTitle;
59
+
}
60
+
public closable(can: boolean = true) {
61
+
this.titleBar.closable = can;
62
+
return this;
63
+
}
64
+
65
+
private titleGrabHandler(ev: MouseEvent) {
66
+
if (ev.button !== 0) return;
67
+
68
+
Window.currentlyDragged = this;
69
+
this.titleBar.wraps.style.cursor = 'grabbing';
70
+
71
+
const rect = this.wraps.getBoundingClientRect();
72
+
Window.mouseRelX = ev.clientX - rect.left;
73
+
Window.mouseRelY = ev.clientY - rect.top;
74
+
75
+
ev.preventDefault();
76
+
}
77
+
78
+
private static onMouseMove(ev: MouseEvent) {
79
+
const draggedWindow = Window.currentlyDragged;
80
+
if (draggedWindow) {
81
+
const viewportWidth = document.documentElement.clientWidth;
82
+
const viewportHeight = document.documentElement.clientHeight;
83
+
84
+
const newLeft = ev.clientX - Window.mouseRelX;
85
+
const newTop = ev.clientY - Window.mouseRelY;
86
+
87
+
const clampedLeft = Math.min(Math.max(0, newLeft), viewportWidth - draggedWindow.wraps.offsetWidth);
88
+
const clampedTop = Math.min(Math.max(0, newTop), viewportHeight - draggedWindow.wraps.offsetHeight);
89
+
90
+
draggedWindow.wraps.style.left = `${clampedLeft}px`;
91
+
draggedWindow.wraps.style.top = `${clampedTop}px`;
92
+
}
93
+
}
94
+
95
+
private static onMouseUp(_ev: MouseEvent) {
96
+
if (Window.currentlyDragged) {
97
+
Window.currentlyDragged.titleBar.wraps.style.cursor = 'grab';
98
+
Window.currentlyDragged = null;
99
+
}
100
+
}
101
+
102
+
// A single place to initialize global listeners
103
+
private static _initialized = false;
104
+
private static initializeGlobalListeners() {
105
+
if (this._initialized) return;
106
+
globalThis.addEventListener("mousemove", this.onMouseMove);
107
+
globalThis.addEventListener("mouseup", this.onMouseUp);
108
+
this._initialized = true;
109
+
}
110
+
111
+
// Static initializer block to set up listeners once
112
+
static {
113
+
this.initializeGlobalListeners();
114
+
}
115
+
}
116
+
+52
src/windows/bluesky/feed.ts
+52
src/windows/bluesky/feed.ts
···
1
+
// formerly src/main.ts. how far we've come.
2
+
3
+
import { Column, Container, Image, Row } from "../../domlink.ts";
4
+
import { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/bluesky';
5
+
import { XQuery } from "../../support/atproto.ts";
6
+
import { FeedResponse as RawFeedResponse } from "../../support/bluesky.ts";
7
+
8
+
class PostView extends Container {
9
+
constructor(fvp: AppBskyFeedDefs.FeedViewPost) {
10
+
super();
11
+
const post = fvp.post;
12
+
const record = post.record as AppBskyFeedPost.Main;
13
+
this.with(
14
+
new Row().with(
15
+
new Image(post.author.avatar ?? "/media/default-avatar.png").class("Avatar"),
16
+
new Column().with(
17
+
post.author.displayName ?? post.author.handle,
18
+
record.text,
19
+
new Row().with(
20
+
`๐จ๏ธ ${post.replyCount} `,
21
+
`๐ ${post.repostCount} `,
22
+
`๐ ${post.quoteCount} `,
23
+
`โค๏ธ ${post.likeCount} `,
24
+
)
25
+
),
26
+
)
27
+
);
28
+
this.wraps.classList.add("PostView");
29
+
}
30
+
}
31
+
32
+
type FeedGenerator = (cursor: string | null) => Promise<RawFeedResponse>;
33
+
export class Feed extends Column {
34
+
generator: FeedGenerator | null = null;
35
+
cursor: string | null = null;
36
+
constructor() {
37
+
super();
38
+
this.class("Feed");
39
+
}
40
+
async loadFeed(generator: FeedGenerator) {
41
+
this.generator = generator;
42
+
this.children.forEach(x=>this.delete(x));
43
+
const data = await this.generator(this.cursor);
44
+
this.cursor = data.cursor;
45
+
const postViews = data.feed.map(post => new PostView(post));
46
+
this.with(...postViews);
47
+
return this;
48
+
}
49
+
static createAuthorGenerator(did: string): FeedGenerator {
50
+
return (cursor) => XQuery<RawFeedResponse>("app.bsky.feed.getAuthorFeed", {actor: did, cursor: cursor});
51
+
}
52
+
}
+120
src/windows/tangled/issuesearch.ts
+120
src/windows/tangled/issuesearch.ts
···
1
+
import { Body, Button, Column, Input, Label, Link, Node, Row, Table, TableCell, TableRow } from "../../domlink.ts";
2
+
import { AtURI, AtURIString, GetRecord, ListRecords, XQuery } from "../../support/atproto.ts";
3
+
import { GetIssues, GetRepo, Issue, StringRepoRef } from "../../support/tangled.ts";
4
+
import * as Constellation from "../../support/constellation.ts";
5
+
import { MiniDoc } from "../../support/slingshot.ts";
6
+
Body.with(new Link("TypeScript source here!").to("https://tangled.sh/@hotsocket.fyi/domlink/blob/main/src/issuesearch.ts"));
7
+
8
+
type recordListingItem<T> = {
9
+
uri: string;
10
+
cid: string;
11
+
value: T;
12
+
};
13
+
14
+
async function timeout<T>(time: number, promise: Promise<T>): Promise<T> {
15
+
const result = await Promise.race([new Promise<null>(r => setTimeout(r, time, null)), promise]);
16
+
if (result == null) {
17
+
throw new Error("timeout");
18
+
}
19
+
return result;
20
+
}
21
+
22
+
type issueItem = ({ handle: string; issue: Issue });
23
+
let allIssues: issueItem[] = [];
24
+
let currentRepoLink = ""; // used to build issue links
25
+
26
+
function renderIssues(issues: issueItem[]) {
27
+
const view_issues_list = new Table().add(
28
+
new TableRow().with(
29
+
new TableCell().add("Handle"),
30
+
new TableCell().add("Issue#"),
31
+
new TableCell().add("Title")
32
+
)
33
+
);
34
+
issues.forEach(issue => {
35
+
if (issue) {
36
+
const view_issue_row = new TableRow();
37
+
view_issue_row.with(
38
+
new TableCell().add(new Link(issue.handle).to(`https://tangled.sh/@${issue.handle}`)),
39
+
new TableCell().add(new Link(issue.issue.issueId.toString()).to(`${currentRepoLink}/issues/${issue.issue.issueId}`)),
40
+
new TableCell().add(issue.issue.title)
41
+
);
42
+
view_issues_list.add(view_issue_row);
43
+
}
44
+
});
45
+
return view_issues_list;
46
+
}
47
+
48
+
49
+
const repoUrl = new Input();
50
+
let last: Node | null = null;
51
+
const statusText = new Label("waiting");
52
+
const runButton = new Button("GO!", async () => {
53
+
(runButton.wraps as HTMLButtonElement).disabled = true;
54
+
if (last) {
55
+
try {
56
+
Body.delete(last);
57
+
} finally {
58
+
last = null;
59
+
}
60
+
}
61
+
allIssues = [];
62
+
searchRow.style(x => x.display = "none");
63
+
try {
64
+
await getIssues(repoUrl.value);
65
+
last = renderIssues(allIssues);
66
+
searchRow.style(x => x.removeProperty("display"));
67
+
} catch (e) {
68
+
last = new Label("Failed");
69
+
console.error(e);
70
+
}
71
+
Body.add(last);
72
+
searchInput.value = "";
73
+
(runButton.wraps as HTMLButtonElement).disabled = false;
74
+
});
75
+
76
+
77
+
78
+
const searchInput = new Input();
79
+
searchInput.watch((search) => {
80
+
if (allIssues.length > 0) {
81
+
if (last) {
82
+
Body.delete(last);
83
+
}
84
+
search = search.toLowerCase();
85
+
last = renderIssues(allIssues.filter((item) => {
86
+
return item.issue.issueId == Number(item) ||
87
+
search.split(" ").every((word) => (
88
+
item.handle.toLowerCase().includes(word) ||
89
+
item.issue.title.toLowerCase().includes(word) ||
90
+
item.issue.body.toLowerCase().includes(word)))
91
+
}));
92
+
Body.add(last);
93
+
}
94
+
});
95
+
const searchRow = new Row().with(
96
+
"search",
97
+
searchInput
98
+
);
99
+
searchRow.style(x => x.display = "none");
100
+
101
+
102
+
103
+
Body.add(new Row().with(
104
+
"List issues for repo: ",
105
+
repoUrl,
106
+
runButton,
107
+
statusText
108
+
));
109
+
Body.add(searchRow);
110
+
export class IssueList extends Column {
111
+
issues: Issue[] | null = null;
112
+
constructor() {
113
+
super();
114
+
this.class("IssueList");
115
+
}
116
+
async getIssues(repo: string) {
117
+
const rp = await GetRepo(StringRepoRef(repo));
118
+
this.issues = await GetIssues(rp);
119
+
}
120
+
}
+14
static/desktop.html
+14
static/desktop.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
<title>desktop</title>
7
+
<link rel="stylesheet" href="styles/domlink.css">
8
+
<link rel="stylesheet" href="mine.css">
9
+
</head>
10
+
<body>
11
+
<noscript>enable js or fukc off</noscript>
12
+
<script src="desktop.js" type="module"></script>
13
+
</body>
14
+
</html>
static/media/dog.jpeg
static/media/dog.jpeg
This is a binary file and will not be displayed.
+28
static/styles/domlink-windowing.css
+28
static/styles/domlink-windowing.css
···
1
+
.LWindow {
2
+
min-width: 150px;
3
+
min-height: 150px;
4
+
resize: both;
5
+
border: 1px solid black;
6
+
box-sizing: border-box;
7
+
position: absolute;
8
+
background-color: white;
9
+
overflow: auto;
10
+
display: flex;
11
+
flex-direction: column;
12
+
}
13
+
.LWindowHandle {
14
+
width: auto;
15
+
height: 1.5em;
16
+
padding-left: 0.5ch;
17
+
padding-right: 0.5ch;
18
+
cursor: grab;
19
+
background-color: lightsteelblue;
20
+
display: flex;
21
+
justify-content: space-between;
22
+
align-items: center;
23
+
overflow: hidden;
24
+
}
25
+
.LWindowContent {
26
+
flex: 1;
27
+
overflow: hidden;
28
+
}
+3
static/styles/domlink.css
+3
static/styles/domlink.css
+13
static/windowing.html
+13
static/windowing.html
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
<title>windowing</title>
7
+
<link rel="stylesheet" href="styles/domlink.css">
8
+
</head>
9
+
<body>
10
+
<noscript>enable js or fukc off</noscript>
11
+
<script src="windowing.js" type="module"></script>
12
+
</body>
13
+
</html>
+58
test/support/atproto.ts
+58
test/support/atproto.ts
···
1
+
import { ValidateNSID, ValidateDID, AtURI } from "../../src/support/atproto.ts";
2
+
import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";
3
+
4
+
Deno.test("ValidateNSID returns NSID for valid input", () => {
5
+
assertEquals(ValidateNSID("com.example.app"), "com.example.app");
6
+
assertEquals(ValidateNSID("com.example.app.v2"), "com.example.app.v2");
7
+
});
8
+
9
+
Deno.test("ValidateNSID returns null for invalid input", () => {
10
+
assertEquals(ValidateNSID("invalid-nsid"), null);
11
+
assertEquals(ValidateNSID("com.example..app"), null);
12
+
assertEquals(ValidateNSID(".com.example.app"), null);
13
+
assertEquals(ValidateNSID("com.example.app."), null);
14
+
assertEquals(ValidateNSID("com.example.app_name"), null);
15
+
assertEquals(ValidateNSID("com.example.app-name"), null);
16
+
assertEquals(ValidateNSID(""), null);
17
+
});
18
+
19
+
Deno.test("ValidateDID returns DID for valid input", () => {
20
+
assertEquals(ValidateDID("did:plc:example"), "did:plc:example");
21
+
assertEquals(ValidateDID("did:web:example.com"), "did:web:example.com");
22
+
});
23
+
24
+
Deno.test("ValidateDID returns null for invalid input", () => {
25
+
assertEquals(ValidateDID("invalid-did"), null);
26
+
assertEquals(ValidateDID("did:other:example"), null);
27
+
assertEquals(ValidateDID("did:plc"), null);
28
+
assertEquals(ValidateDID("did:web:"), null);
29
+
assertEquals(ValidateDID(""), null);
30
+
});
31
+
32
+
Deno.test("AtURI.fromString parses valid URI", () => {
33
+
const uri = AtURI.fromString("at://did:plc:example/com.example.collection/rkey123");
34
+
assertEquals(uri.authority, "did:plc:example");
35
+
assertEquals(uri.collection, "com.example.collection");
36
+
assertEquals(uri.rkey, "rkey123");
37
+
});
38
+
39
+
Deno.test("AtURI.fromString sets nulls for invalid URI", () => {
40
+
const uri = AtURI.fromString("at://invalid-did/invalid-collection/actually-fine-rkey");
41
+
assertEquals(uri.authority, null);
42
+
assertEquals(uri.collection, null);
43
+
assertEquals(uri.rkey, "actually-fine-rkey");
44
+
});
45
+
46
+
Deno.test("AtURI toString returns string form of URI properly", () => {
47
+
const uri = AtURI.fromString("at://did:plc:example/com.example.collection/rkey123");
48
+
assertEquals(uri.toString(), "at://did:plc:example/com.example.collection/rkey123");
49
+
});
50
+
51
+
Deno.test("AtURI toString returns null for invalid URI parts", () => {
52
+
let uri = AtURI.fromString("at://invalid-did/com.example.collection/rkey123");
53
+
assertEquals(uri.toString(), null);
54
+
55
+
uri = AtURI.fromString("at://did:plc:example/invalid-collection/rkey123");
56
+
assertEquals(uri.toString(), null);
57
+
58
+
});
-14
tsconfig.json
-14
tsconfig.json
···
1
-
{
2
-
"compilerOptions": {
3
-
"target": "es2017",
4
-
"module": "nodenext",
5
-
"moduleResolution": "nodenext",
6
-
"esModuleInterop": true,
7
-
"forceConsistentCasingInFileNames": true,
8
-
"strict": true,
9
-
"skipLibCheck": true,
10
-
"rootDir": "src",
11
-
"outDir": "out",
12
-
"resolveJsonModule": true
13
-
}
14
-
}