+4
__generated__/lexicons.ts
+4
__generated__/lexicons.ts
+1
-1
deno.json
+1
-1
deno.json
···
2
2
"imports": {
3
3
"$lexicon/": "./__generated__/",
4
4
"@atproto/syntax": "npm:@atproto/syntax@^0.4.0",
5
-
"@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.12",
5
+
"@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.15",
6
6
"@gfx/canvas": "jsr:@gfx/canvas@^0.5.8",
7
7
"@std/path": "jsr:@std/path@^1.0.9",
8
8
"@tailwindcss/cli": "npm:@tailwindcss/cli@^4.1.4",
+210
-8
deno.lock
+210
-8
deno.lock
···
1
1
{
2
2
"version": "4",
3
3
"specifiers": {
4
-
"jsr:@bigmoves/atproto-oauth-client@0.1": "0.1.0",
5
-
"jsr:@bigmoves/bff@0.3.0-beta.12": "0.3.0-beta.12",
4
+
"jsr:@bigmoves/atproto-oauth-client@0.2": "0.2.0",
5
+
"jsr:@bigmoves/bff@0.3.0-beta.15": "0.3.0-beta.15",
6
+
"jsr:@deno/gfm@0.10": "0.10.0",
7
+
"jsr:@denosaurs/emoji@0.3": "0.3.1",
6
8
"jsr:@denosaurs/plug@1": "1.0.5",
7
9
"jsr:@denosaurs/plug@1.0.5": "1.0.5",
8
10
"jsr:@gfx/canvas@~0.5.8": "0.5.8",
9
11
"jsr:@std/assert@0.214": "0.214.0",
10
12
"jsr:@std/assert@0.217": "0.217.0",
13
+
"jsr:@std/assert@^1.0.12": "1.0.13",
11
14
"jsr:@std/assert@^1.0.13": "1.0.13",
15
+
"jsr:@std/async@^1.0.12": "1.0.12",
12
16
"jsr:@std/cache@0.2": "0.2.0",
13
17
"jsr:@std/cli@^1.0.17": "1.0.17",
18
+
"jsr:@std/data-structures@^1.0.6": "1.0.7",
14
19
"jsr:@std/encoding@0.214": "0.214.0",
15
20
"jsr:@std/encoding@0.217.0": "0.217.0",
16
21
"jsr:@std/encoding@^1.0.10": "1.0.10",
···
18
23
"jsr:@std/fmt@^1.0.8": "1.0.8",
19
24
"jsr:@std/fs@0.214": "0.214.0",
20
25
"jsr:@std/fs@0.217.0": "0.217.0",
26
+
"jsr:@std/fs@^1.0.15": "1.0.17",
27
+
"jsr:@std/fs@^1.0.16": "1.0.17",
21
28
"jsr:@std/html@^1.0.4": "1.0.4",
22
29
"jsr:@std/http@^1.0.13": "1.0.16",
23
30
"jsr:@std/internal@^1.0.6": "1.0.7",
···
29
36
"jsr:@std/path@^1.0.8": "1.0.9",
30
37
"jsr:@std/path@^1.0.9": "1.0.9",
31
38
"jsr:@std/streams@^1.0.9": "1.0.9",
39
+
"jsr:@std/testing@^1.0.11": "1.0.11",
32
40
"npm:@atproto-labs/handle-resolver-node@~0.1.14": "0.1.14",
33
41
"npm:@atproto-labs/simple-store@~0.1.2": "0.1.2",
34
42
"npm:@atproto/api@~0.14.19": "0.14.22",
···
42
50
"npm:@atproto/oauth-types@~0.2.4": "0.2.4",
43
51
"npm:@atproto/syntax@0.4": "0.4.0",
44
52
"npm:@atproto/xrpc-server@*": "0.7.15",
53
+
"npm:@skyware/jetstream@~0.2.2": "0.2.2",
45
54
"npm:@tailwindcss/cli@*": "4.1.4",
55
+
"npm:@tailwindcss/cli@^4.0.12": "4.1.4",
56
+
"npm:@tailwindcss/cli@^4.1.3": "4.1.4",
46
57
"npm:@tailwindcss/cli@^4.1.4": "4.1.4",
47
58
"npm:@types/node@*": "22.12.0",
48
59
"npm:clsx@^2.1.1": "2.1.1",
49
60
"npm:date-fns@^4.1.0": "4.1.0",
61
+
"npm:github-slugger@2": "2.0.0",
62
+
"npm:he@^1.2.0": "1.2.0",
50
63
"npm:jose@5.9.6": "5.9.6",
64
+
"npm:katex@0.16": "0.16.22",
65
+
"npm:marked-alert@2": "2.1.2_marked@12.0.2",
66
+
"npm:marked-footnote@^1.2.0": "1.2.4_marked@12.0.2",
67
+
"npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2",
68
+
"npm:marked@12": "12.0.2",
51
69
"npm:multiformats@*": "9.9.0",
52
70
"npm:multiformats@^13.3.2": "13.3.2",
53
71
"npm:popmotion@^11.0.5": "11.0.5",
54
72
"npm:preact-render-to-string@^6.5.13": "6.5.13_preact@10.26.5",
55
73
"npm:preact@^10.26.5": "10.26.5",
74
+
"npm:prismjs@^1.29.0": "1.30.0",
75
+
"npm:sanitize-html@^2.13.0": "2.15.0",
56
76
"npm:sharp@~0.34.1": "0.34.1",
57
77
"npm:tailwind-merge@^3.2.0": "3.2.0",
78
+
"npm:tailwindcss@^4.0.12": "4.1.4",
79
+
"npm:tailwindcss@^4.1.3": "4.1.4",
58
80
"npm:tailwindcss@^4.1.4": "4.1.4",
59
81
"npm:typed-htmx@~0.3.1": "0.3.1"
60
82
},
61
83
"jsr": {
62
-
"@bigmoves/atproto-oauth-client@0.1.0": {
63
-
"integrity": "d5858f534a800a46af28b1c03b447b179d15bbf164c24767601ae78513501711",
84
+
"@bigmoves/atproto-oauth-client@0.2.0": {
85
+
"integrity": "5c3ca124dd52eff51dace83790779ebe48c4b41559b799e16c8750bd415f2124",
64
86
"dependencies": [
65
87
"npm:@atproto-labs/handle-resolver-node",
66
88
"npm:@atproto-labs/simple-store",
···
70
92
"npm:jose"
71
93
]
72
94
},
73
-
"@bigmoves/bff@0.3.0-beta.12": {
74
-
"integrity": "26d404d3db39d2fa187e36a97ebd244ff648152d0820e17b1a2b993da4fbf34b",
95
+
"@bigmoves/bff@0.3.0-beta.15": {
96
+
"integrity": "934d0fab8cc73804099ccb5362fa89f5ef3cd6269a6613029131770c97cdfcb9",
75
97
"dependencies": [
76
98
"jsr:@bigmoves/atproto-oauth-client",
77
99
"jsr:@std/assert@^1.0.13",
···
91
113
"npm:tailwind-merge"
92
114
]
93
115
},
116
+
"@deno/gfm@0.10.0": {
117
+
"integrity": "51708205e3559a4aeb6afb29d07c5bfafe7941f91bb360351ef6621de9a39527",
118
+
"dependencies": [
119
+
"jsr:@denosaurs/emoji",
120
+
"npm:github-slugger",
121
+
"npm:he",
122
+
"npm:katex",
123
+
"npm:marked",
124
+
"npm:marked-alert",
125
+
"npm:marked-footnote",
126
+
"npm:marked-gfm-heading-id",
127
+
"npm:prismjs",
128
+
"npm:sanitize-html"
129
+
]
130
+
},
131
+
"@denosaurs/emoji@0.3.1": {
132
+
"integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b"
133
+
},
94
134
"@denosaurs/plug@1.0.5": {
95
135
"integrity": "04cd988da558adc226202d88c3a434d5fcc08146eaf4baf0cea0c2284b16d2bf",
96
136
"dependencies": [
···
121
161
"dependencies": [
122
162
"jsr:@std/internal"
123
163
]
164
+
},
165
+
"@std/async@1.0.12": {
166
+
"integrity": "d1bfcec459e8012846fe4e38dfc4241ab23240ecda3d8d6dfcf6d81a632e803d"
124
167
},
125
168
"@std/cache@0.2.0": {
126
169
"integrity": "63a2ccd5a9e7c03e430f7d34dfcfd0d0cfc90731a1eaf8208f4c66e418fc3035"
···
128
171
"@std/cli@1.0.17": {
129
172
"integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b"
130
173
},
174
+
"@std/data-structures@1.0.7": {
175
+
"integrity": "16932d2c8d281f65eaaa2209af2473209881e33b1ced54cd1b015e7b4cdbb0d2"
176
+
},
131
177
"@std/encoding@0.214.0": {
132
178
"integrity": "30a8713e1db22986c7e780555ffd2fefd1d4f9374d734bb41f5970f6c3352af5"
133
179
},
···
155
201
"dependencies": [
156
202
"jsr:@std/assert@0.217",
157
203
"jsr:@std/path@0.217"
204
+
]
205
+
},
206
+
"@std/fs@1.0.17": {
207
+
"integrity": "1c00c632677c1158988ef7a004cb16137f870aafdb8163b9dce86ec652f3952b",
208
+
"dependencies": [
209
+
"jsr:@std/path@^1.0.9"
158
210
]
159
211
},
160
212
"@std/html@1.0.4": {
···
199
251
},
200
252
"@std/streams@1.0.9": {
201
253
"integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035"
254
+
},
255
+
"@std/testing@1.0.11": {
256
+
"integrity": "12b3db12d34f0f385a26248933bde766c0f8c5ad8b6ab34d4d38f528ab852f48",
257
+
"dependencies": [
258
+
"jsr:@std/assert@^1.0.12",
259
+
"jsr:@std/async",
260
+
"jsr:@std/data-structures",
261
+
"jsr:@std/fs@^1.0.16",
262
+
"jsr:@std/internal",
263
+
"jsr:@std/path@^1.0.8"
264
+
]
202
265
}
203
266
},
204
267
"npm": {
268
+
"@atcute/bluesky@1.0.15_@atcute+client@2.0.9": {
269
+
"integrity": "sha512-+EFiybmKQ97aBAgtaD+cKRJER5AMn3cZMkEwEg/pDdWyzxYJ9m1UgemmLdTgI8VrxPufKqdXS2nl7uO7TY6BPA==",
270
+
"dependencies": [
271
+
"@atcute/client"
272
+
]
273
+
},
274
+
"@atcute/client@2.0.9": {
275
+
"integrity": "sha512-QNDm9gMP6x9LY77ArwY+urQOBtQW74/onEAz42c40JxRm6Rl9K9cU4ROvNKJ+5cpVmEm1sthEWVRmDr5CSZENA=="
276
+
},
205
277
"@atproto-labs/did-resolver@0.1.11": {
206
278
"integrity": "sha512-qXNzIX2GPQnxT1gl35nv/8ErDdc4Fj/+RlJE7oyE7JGkFAPUyuY03TvKJ79SmWFsWE8wyTXEpLuphr9Da1Vhkw==",
207
279
"dependencies": [
···
334
406
"@atproto/lexicon",
335
407
"@atproto/syntax",
336
408
"chalk",
337
-
"commander",
409
+
"commander@9.5.0",
338
410
"prettier",
339
411
"ts-morph",
340
412
"yesno",
···
611
683
"node-addon-api"
612
684
]
613
685
},
686
+
"@skyware/jetstream@0.2.2": {
687
+
"integrity": "sha512-d1MtWPTIFEciSzV8OClXZCJoz0DJ7aupt4EZSwpGAASYG0ZIPmZTt7RVJkoFzQyqRPHAMD7CvEwu0ut3MHX1og==",
688
+
"dependencies": [
689
+
"@atcute/bluesky",
690
+
"partysocket"
691
+
]
692
+
},
614
693
"@tailwindcss/cli@4.1.4": {
615
694
"integrity": "sha512-gP05Qihh+cZ2FqD5fa0WJXx3KEk2YWUYv/RBKAyiOg0V4vYVDr/xlLc0sacpnVEXM45BVUR9U2hsESufYs6YTA==",
616
695
"dependencies": [
···
856
935
"color-convert",
857
936
"color-string"
858
937
]
938
+
},
939
+
"commander@8.3.0": {
940
+
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
859
941
},
860
942
"commander@9.5.0": {
861
943
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="
···
884
966
"ms@2.0.0"
885
967
]
886
968
},
969
+
"deepmerge@4.3.1": {
970
+
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
971
+
},
887
972
"depd@2.0.0": {
888
973
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
889
974
},
···
896
981
"detect-libc@2.0.3": {
897
982
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
898
983
},
984
+
"dom-serializer@2.0.0": {
985
+
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
986
+
"dependencies": [
987
+
"domelementtype",
988
+
"domhandler",
989
+
"entities"
990
+
]
991
+
},
992
+
"domelementtype@2.3.0": {
993
+
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
994
+
},
995
+
"domhandler@5.0.3": {
996
+
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
997
+
"dependencies": [
998
+
"domelementtype"
999
+
]
1000
+
},
1001
+
"domutils@3.2.2": {
1002
+
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
1003
+
"dependencies": [
1004
+
"dom-serializer",
1005
+
"domelementtype",
1006
+
"domhandler"
1007
+
]
1008
+
},
899
1009
"dunder-proto@1.0.1": {
900
1010
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
901
1011
"dependencies": [
···
919
1029
"graceful-fs",
920
1030
"tapable"
921
1031
]
1032
+
},
1033
+
"entities@4.5.0": {
1034
+
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
922
1035
},
923
1036
"es-define-property@1.0.1": {
924
1037
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
···
935
1048
"escape-html@1.0.3": {
936
1049
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
937
1050
},
1051
+
"escape-string-regexp@4.0.0": {
1052
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
1053
+
},
938
1054
"etag@1.8.1": {
939
1055
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
1056
+
},
1057
+
"event-target-polyfill@0.0.4": {
1058
+
"integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="
940
1059
},
941
1060
"event-target-shim@5.0.1": {
942
1061
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
···
1044
1163
"es-object-atoms"
1045
1164
]
1046
1165
},
1166
+
"github-slugger@2.0.0": {
1167
+
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
1168
+
},
1047
1169
"gopd@1.2.0": {
1048
1170
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
1049
1171
},
···
1065
1187
"function-bind"
1066
1188
]
1067
1189
},
1190
+
"he@1.2.0": {
1191
+
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
1192
+
},
1068
1193
"hey-listen@1.0.8": {
1069
1194
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
1195
+
},
1196
+
"htmlparser2@8.0.2": {
1197
+
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
1198
+
"dependencies": [
1199
+
"domelementtype",
1200
+
"domhandler",
1201
+
"domutils",
1202
+
"entities"
1203
+
]
1070
1204
},
1071
1205
"http-errors@2.0.0": {
1072
1206
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
···
1110
1244
},
1111
1245
"is-number@7.0.0": {
1112
1246
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
1247
+
},
1248
+
"is-plain-object@5.0.0": {
1249
+
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
1113
1250
},
1114
1251
"iso-datestring-validator@2.2.2": {
1115
1252
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="
···
1120
1257
"jose@5.9.6": {
1121
1258
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="
1122
1259
},
1260
+
"katex@0.16.22": {
1261
+
"integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==",
1262
+
"dependencies": [
1263
+
"commander@8.3.0"
1264
+
]
1265
+
},
1123
1266
"lightningcss-darwin-arm64@1.29.2": {
1124
1267
"integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="
1125
1268
},
···
1169
1312
"lru-cache@10.4.3": {
1170
1313
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
1171
1314
},
1315
+
"marked-alert@2.1.2_marked@12.0.2": {
1316
+
"integrity": "sha512-EFNRZ08d8L/iEIPLTlQMDjvwIsj03gxWCczYTht6DCiHJIZhMk4NK5gtPY9UqAYb09eV5VGT+jD4lp396E0I+w==",
1317
+
"dependencies": [
1318
+
"marked"
1319
+
]
1320
+
},
1321
+
"marked-footnote@1.2.4_marked@12.0.2": {
1322
+
"integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==",
1323
+
"dependencies": [
1324
+
"marked"
1325
+
]
1326
+
},
1327
+
"marked-gfm-heading-id@3.2.0_marked@12.0.2": {
1328
+
"integrity": "sha512-Xfxpr5lXLDLY10XqzSCA9l2dDaiabQUgtYM9hw8yunyVsB/xYBRpiic6BOiY/EAJw1ik1eWr1ET1HKOAPZBhXg==",
1329
+
"dependencies": [
1330
+
"github-slugger",
1331
+
"marked"
1332
+
]
1333
+
},
1334
+
"marked@12.0.2": {
1335
+
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="
1336
+
},
1172
1337
"math-intrinsics@1.1.0": {
1173
1338
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
1174
1339
},
···
1221
1386
"multiformats@9.9.0": {
1222
1387
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="
1223
1388
},
1389
+
"nanoid@3.3.11": {
1390
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
1391
+
},
1224
1392
"negotiator@0.6.3": {
1225
1393
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
1226
1394
},
···
1245
1413
"ee-first"
1246
1414
]
1247
1415
},
1416
+
"parse-srcset@1.0.2": {
1417
+
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
1418
+
},
1248
1419
"parseurl@1.3.3": {
1249
1420
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
1421
+
},
1422
+
"partysocket@1.1.3": {
1423
+
"integrity": "sha512-87Jd/nqPoWnVfzHE6Z12WLWTJ+TAgxs0b7i2S163HfQSrVDUK5tW/FC64T5N8L5ss+gqF+EV0BwjZMWggMY3UA==",
1424
+
"dependencies": [
1425
+
"event-target-polyfill"
1426
+
]
1250
1427
},
1251
1428
"path-browserify@1.0.1": {
1252
1429
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
···
1298
1475
"tslib@2.4.0"
1299
1476
]
1300
1477
},
1478
+
"postcss@8.5.3": {
1479
+
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
1480
+
"dependencies": [
1481
+
"nanoid",
1482
+
"picocolors",
1483
+
"source-map-js"
1484
+
]
1485
+
},
1301
1486
"preact-render-to-string@6.5.13_preact@10.26.5": {
1302
1487
"integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==",
1303
1488
"dependencies": [
···
1309
1494
},
1310
1495
"prettier@3.5.3": {
1311
1496
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="
1497
+
},
1498
+
"prismjs@1.30.0": {
1499
+
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="
1312
1500
},
1313
1501
"process-warning@3.0.0": {
1314
1502
"integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
···
1378
1566
"safer-buffer@2.1.2": {
1379
1567
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1380
1568
},
1569
+
"sanitize-html@2.15.0": {
1570
+
"integrity": "sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==",
1571
+
"dependencies": [
1572
+
"deepmerge",
1573
+
"escape-string-regexp",
1574
+
"htmlparser2",
1575
+
"is-plain-object",
1576
+
"parse-srcset",
1577
+
"postcss"
1578
+
]
1579
+
},
1381
1580
"semver@7.7.1": {
1382
1581
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="
1383
1582
},
···
1486
1685
"dependencies": [
1487
1686
"atomic-sleep"
1488
1687
]
1688
+
},
1689
+
"source-map-js@1.2.1": {
1690
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
1489
1691
},
1490
1692
"split2@4.2.0": {
1491
1693
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
···
1608
1810
},
1609
1811
"workspace": {
1610
1812
"dependencies": [
1611
-
"jsr:@bigmoves/bff@0.3.0-beta.12",
1813
+
"jsr:@bigmoves/bff@0.3.0-beta.15",
1612
1814
"jsr:@gfx/canvas@~0.5.8",
1613
1815
"jsr:@std/path@^1.0.9",
1614
1816
"npm:@atproto/syntax@0.4",
+2
-10
input.css
+2
-10
input.css
···
1
1
@import "tailwindcss";
2
2
3
-
.htmx-request.htmx-indicator {
4
-
display: inline;
5
-
}
6
-
.htmx-indicator {
7
-
display: none;
8
-
}
9
-
.htmx-request #submit-button {
10
-
opacity: 0.5;
11
-
pointer-events: none;
12
-
}
3
+
/* use to test light mode */
4
+
/* @custom-variant dark (&:where(.dark, .dark *)); */
+282
-62
main.tsx
+282
-62
main.tsx
···
58
58
const PUBLIC_URL = Deno.env.get("BFF_PUBLIC_URL") ?? "http://localhost:8080";
59
59
const GOATCOUNTER_URL = Deno.env.get("GOATCOUNTER_URL");
60
60
61
-
let cssContentHash: string = "";
61
+
const staticFilesHash = new Map<string, string>();
62
62
63
63
bff({
64
64
appName: "Grain Social",
···
73
73
lexicons,
74
74
rootElement: Root,
75
75
onListen: async () => {
76
-
const cssFileContent = await Deno.readFile(
77
-
join(Deno.cwd(), "static", "styles.css"),
78
-
);
79
-
const hashBuffer = await crypto.subtle.digest("SHA-256", cssFileContent);
80
-
cssContentHash = Array.from(new Uint8Array(hashBuffer))
81
-
.map((b) => b.toString(16).padStart(2, "0"))
82
-
.join("");
76
+
for (const entry of Deno.readDirSync(join(Deno.cwd(), "static"))) {
77
+
if (
78
+
entry.isFile &&
79
+
(entry.name.endsWith(".js") || entry.name.endsWith(".css"))
80
+
) {
81
+
const fileContent = await Deno.readFile(
82
+
join(Deno.cwd(), "static", entry.name),
83
+
);
84
+
const hashBuffer = await crypto.subtle.digest("SHA-256", fileContent);
85
+
const hash = Array.from(new Uint8Array(hashBuffer))
86
+
.map((b) => b.toString(16).padStart(2, "0"))
87
+
.join("");
88
+
staticFilesHash.set(entry.name, hash);
89
+
}
90
+
}
83
91
},
84
92
onError: (err) => {
85
93
if (err instanceof UnauthorizedError) {
···
107
115
<div
108
116
id="login"
109
117
class="flex justify-center items-center w-full h-full relative"
110
-
style="background-image: url('https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/bafkreiewhwu3ro5dv7omedphb62db4koa7qtvyzfhiiypg3ru4tvuxkrjy@webp'); background-size: cover; background-position: center;"
118
+
style="background-image: url('https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/bafkreiewhwu3ro5dv7omedphb62db4koa7qtvyzfhiiypg3ru4tvuxkrjy@jpeg'); background-size: cover; background-position: center;"
111
119
>
112
120
<Login hx-target="#login" error={error} errorClass="text-white" />
113
121
<div class="absolute bottom-2 right-2 text-white text-sm">
···
139
147
if (!profile) return ctx.next();
140
148
let follow: WithBffMeta<BskyFollow> | undefined;
141
149
if (ctx.currentUser) {
142
-
follow = getFollow(
143
-
profile.did,
144
-
ctx.currentUser.did,
145
-
ctx,
146
-
);
150
+
follow = getFollow(profile.did, ctx.currentUser.did, ctx);
147
151
}
148
152
ctx.state.meta = [
149
153
{
···
187
191
...getPageMeta(galleryLink(handle, rkey)),
188
192
...getGalleryMeta(gallery),
189
193
];
190
-
ctx.state.scripts = ["photo_dialog.js", "masonry.js"];
194
+
ctx.state.scripts = ["photo_dialog.js", "masonry.js", "sortable.js"];
191
195
return ctx.render(
192
196
<GalleryPage favs={favs} gallery={gallery} currentUserDid={did} />,
193
197
);
···
219
223
createdAt: new Date().toISOString(),
220
224
},
221
225
);
222
-
return ctx.html(
223
-
<FollowButton followeeDid={did} followUri={followUri} />,
224
-
);
226
+
return ctx.html(<FollowButton followeeDid={did} followUri={followUri} />);
225
227
}),
226
228
route("/follow/:did/:rkey", ["DELETE"], async (_req, params, ctx) => {
227
229
requireAuth(ctx);
···
231
233
await ctx.deleteRecord(
232
234
`at://${ctx.currentUser.did}/app.bsky.graph.follow/${rkey}`,
233
235
);
234
-
return ctx.html(
235
-
<FollowButton followeeDid={did} followUri={undefined} />,
236
-
);
236
+
return ctx.html(<FollowButton followeeDid={did} followUri={undefined} />);
237
237
}),
238
238
route("/dialogs/gallery/new", (_req, _params, ctx) => {
239
239
requireAuth(ctx);
···
245
245
const rkey = params.rkey;
246
246
const gallery = getGallery(handle, rkey, ctx);
247
247
return ctx.html(<GalleryCreateEditDialog gallery={gallery} />);
248
+
}),
249
+
route("/dialogs/gallery/:rkey/sort", (_req, params, ctx) => {
250
+
requireAuth(ctx);
251
+
const handle = ctx.currentUser.handle;
252
+
const rkey = params.rkey;
253
+
const gallery = getGallery(handle, rkey, ctx);
254
+
if (!gallery) return ctx.next();
255
+
return ctx.html(<GallerySortDialog gallery={gallery} />);
248
256
}),
249
257
route("/onboard", (_req, _params, ctx) => {
250
258
requireAuth(ctx);
···
570
578
571
579
return ctx.redirect(`/profile/${ctx.currentUser.handle}`);
572
580
}),
581
+
route(
582
+
"/actions/gallery/:rkey/sort",
583
+
["POST"],
584
+
async (req, params, ctx) => {
585
+
requireAuth(ctx);
586
+
const galleryRkey = params.rkey;
587
+
const galleryUri =
588
+
`at://${ctx.currentUser.did}/social.grain.gallery/${galleryRkey}`;
589
+
const {
590
+
items,
591
+
} = ctx.indexService.getRecords<WithBffMeta<GalleryItem>>(
592
+
"social.grain.gallery.item",
593
+
{
594
+
where: [
595
+
{
596
+
field: "gallery",
597
+
equals: galleryUri,
598
+
},
599
+
],
600
+
},
601
+
);
602
+
const itemsMap = new Map<string, WithBffMeta<GalleryItem>>();
603
+
for (const item of items) {
604
+
itemsMap.set(item.item, item);
605
+
}
606
+
const formData = await req.formData();
607
+
const sortedItems = formData.getAll("item") as string[];
608
+
const updates = [];
609
+
let position = 0;
610
+
for (const sortedItemUri of sortedItems) {
611
+
const item = itemsMap.get(sortedItemUri);
612
+
if (!item) continue;
613
+
updates.push({
614
+
collection: "social.grain.gallery.item",
615
+
rkey: new AtUri(item.uri).rkey,
616
+
data: {
617
+
gallery: item.gallery,
618
+
item: item.item,
619
+
createdAt: item.createdAt,
620
+
position,
621
+
},
622
+
});
623
+
position++;
624
+
}
625
+
await ctx.updateRecords<WithBffMeta<GalleryItem>>(updates);
626
+
return ctx.redirect(
627
+
`/profile/${ctx.currentUser.handle}/${galleryRkey}`,
628
+
);
629
+
},
630
+
),
573
631
...photoUploadRoutes(),
574
632
...avatarUploadRoutes(),
575
633
],
···
654
712
};
655
713
656
714
function getFollow(followeeDid: string, followerDid: string, ctx: BffContext) {
657
-
const { items: [follow] } = ctx.indexService.getRecords<
658
-
WithBffMeta<BskyFollow>
659
-
>(
715
+
const {
716
+
items: [follow],
717
+
} = ctx.indexService.getRecords<WithBffMeta<BskyFollow>>(
660
718
"app.bsky.graph.follow",
661
719
{
662
720
where: [
···
688
746
const { items: galleryItems } = ctx.indexService.getRecords<
689
747
WithBffMeta<GalleryItem>
690
748
>("social.grain.gallery.item", {
691
-
orderBy: { field: "createdAt", direction: "asc" },
749
+
orderBy: { field: "position", direction: "asc" },
692
750
where: [{ field: "gallery", in: galleryUris }],
693
751
});
694
752
···
1042
1100
: null}
1043
1101
<script src="https://unpkg.com/htmx.org@1.9.10" />
1044
1102
<script src="https://unpkg.com/hyperscript.org@0.9.14" />
1103
+
<script src="https://unpkg.com/sortablejs@1.15.6" />
1045
1104
<style dangerouslySetInnerHTML={{ __html: CSS }} />
1046
-
<link rel="stylesheet" href={`/static/styles.css?${cssContentHash}`} />
1105
+
<link
1106
+
rel="stylesheet"
1107
+
href={`/static/styles.css?${staticFilesHash.get("styles.css")}`}
1108
+
/>
1047
1109
<link rel="preconnect" href="https://fonts.googleapis.com" />
1048
1110
<link
1049
1111
rel="preconnect"
···
1059
1121
href="https://unpkg.com/@fortawesome/fontawesome-free@6.7.2/css/all.min.css"
1060
1122
preload
1061
1123
/>
1062
-
{scripts?.map((file) => <script key={file} src={`/static/${file}`} />)}
1124
+
{scripts?.map((file) => (
1125
+
<script
1126
+
key={file}
1127
+
src={`/static/${file}?${staticFilesHash.get(file)}`}
1128
+
/>
1129
+
))}
1063
1130
</head>
1064
1131
<body class="h-full w-full dark:bg-zinc-950 dark:text-white">
1065
1132
<Layout id="layout" class="border-zinc-200 dark:border-zinc-800">
···
1067
1134
heading={
1068
1135
<h1 class="font-['Jersey_20'] text-4xl text-zinc-900 dark:text-white">
1069
1136
grain
1070
-
<sub class="bottom-[0.75rem] text-[1rem]">
1071
-
beta
1072
-
</sub>
1137
+
<sub class="bottom-[0.75rem] text-[1rem]">beta</sub>
1073
1138
</h1>
1074
1139
}
1075
1140
profile={profile}
···
1151
1216
<span class="text-zinc-950 dark:text-zinc-50 font-semibold text-">
1152
1217
{profile.displayName || profile.handle}
1153
1218
</span>{" "}
1154
-
<span class="truncate">
1155
-
@{profile.handle}
1156
-
</span>
1219
+
<span class="truncate">@{profile.handle}</span>
1157
1220
</a>
1158
1221
</div>
1159
1222
);
···
1269
1332
: {
1270
1333
children: (
1271
1334
<>
1272
-
<i class="fa-solid fa-plus mr-2" />Follow
1335
+
<i class="fa-solid fa-plus mr-2" />
1336
+
Follow
1273
1337
</>
1274
1338
),
1275
1339
"hx-post": `/follow/${followeeDid}`,
···
1450
1514
);
1451
1515
}
1452
1516
1453
-
function UploadPage(
1454
-
{ handle, photos, returnTo }: Readonly<
1455
-
{ handle: string; photos: PhotoView[]; returnTo?: string }
1456
-
>,
1457
-
) {
1517
+
function UploadPage({
1518
+
handle,
1519
+
photos,
1520
+
returnTo,
1521
+
}: Readonly<{ handle: string; photos: PhotoView[]; returnTo?: string }>) {
1458
1522
return (
1459
1523
<div class="flex flex-col px-4 pt-4 mb-4 space-y-4">
1460
1524
<div class="flex">
1461
1525
<div class="flex-1">
1462
1526
{returnTo
1463
1527
? (
1464
-
<a
1465
-
href={returnTo}
1466
-
class="hover:underline"
1467
-
>
1528
+
<a href={returnTo} class="hover:underline">
1468
1529
<i class="fa-solid fa-arrow-left mr-2" />
1469
1530
Back to gallery
1470
1531
</a>
···
1523
1584
}>) {
1524
1585
return (
1525
1586
<Dialog>
1526
-
<Dialog.Content class="dark:bg-zinc-950">
1587
+
<Dialog.Content class="dark:bg-zinc-950 relative">
1588
+
<Dialog.X class="fill-zinc-950 dark:fill-zinc-50" />
1527
1589
<Dialog.Title>Edit my profile</Dialog.Title>
1528
1590
<div>
1529
1591
<AvatarForm src={profile.avatar} alt={profile.handle} />
···
1545
1607
name="displayName"
1546
1608
class="dark:bg-zinc-800 dark:text-white"
1547
1609
value={profile.displayName}
1610
+
autoFocus
1548
1611
/>
1549
1612
</div>
1550
1613
<div class="mb-4 relative">
···
1658
1721
>
1659
1722
Add photos
1660
1723
</Button>
1724
+
<Button
1725
+
variant="primary"
1726
+
class="self-start w-full sm:w-fit"
1727
+
hx-get={`/dialogs/gallery/${new AtUri(gallery.uri).rkey}/sort`}
1728
+
hx-target="#layout"
1729
+
hx-swap="afterbegin"
1730
+
>
1731
+
Sort order
1732
+
</Button>
1661
1733
<ShareGalleryButton gallery={gallery} />
1662
1734
</div>
1663
1735
)
···
1675
1747
)
1676
1748
: null}
1677
1749
</div>
1750
+
<div class="flex justify-end mb-2">
1751
+
<Button
1752
+
id="justified-button"
1753
+
variant="primary"
1754
+
class="flex justify-center w-full sm:w-fit bg-zinc-100 dark:bg-zinc-800 border-zinc-100 dark:border-zinc-800 data-[selected=false]:bg-transparent data-[selected=false]:border-transparent text-zinc-950 dark:text-zinc-50"
1755
+
_="on click call toggleLayout('justified')
1756
+
set @data-selected to 'true'
1757
+
set #masonry-button's @data-selected to 'false'"
1758
+
>
1759
+
<svg
1760
+
width="24"
1761
+
height="24"
1762
+
viewBox="0 0 24 24"
1763
+
xmlns="http://www.w3.org/2000/svg"
1764
+
>
1765
+
<rect x="2" y="2" width="8" height="6" fill="currentColor" rx="1" />
1766
+
<rect
1767
+
x="12"
1768
+
y="2"
1769
+
width="10"
1770
+
height="6"
1771
+
fill="currentColor"
1772
+
rx="1"
1773
+
/>
1774
+
<rect
1775
+
x="2"
1776
+
y="10"
1777
+
width="6"
1778
+
height="6"
1779
+
fill="currentColor"
1780
+
rx="1"
1781
+
/>
1782
+
<rect
1783
+
x="10"
1784
+
y="10"
1785
+
width="12"
1786
+
height="6"
1787
+
fill="currentColor"
1788
+
rx="1"
1789
+
/>
1790
+
<rect
1791
+
x="2"
1792
+
y="18"
1793
+
width="20"
1794
+
height="4"
1795
+
fill="currentColor"
1796
+
rx="1"
1797
+
/>
1798
+
</svg>
1799
+
</Button>
1800
+
<Button
1801
+
id="masonry-button"
1802
+
variant="primary"
1803
+
data-selected="false"
1804
+
class="flex justify-center w-full sm:w-fit bg-zinc-100 dark:bg-zinc-800 border-zinc-100 dark:border-zinc-800 data-[selected=false]:bg-transparent data-[selected=false]:border-transparent text-zinc-950 dark:text-zinc-50"
1805
+
_="on click call toggleLayout('masonry')
1806
+
set @data-selected to 'true'
1807
+
set #justified-button's @data-selected to 'false'"
1808
+
>
1809
+
<svg
1810
+
width="24"
1811
+
height="24"
1812
+
viewBox="0 0 24 24"
1813
+
xmlns="http://www.w3.org/2000/svg"
1814
+
>
1815
+
<rect x="2" y="2" width="8" height="8" fill="currentColor" rx="1" />
1816
+
<rect
1817
+
x="12"
1818
+
y="2"
1819
+
width="8"
1820
+
height="4"
1821
+
fill="currentColor"
1822
+
rx="1"
1823
+
/>
1824
+
<rect
1825
+
x="12"
1826
+
y="8"
1827
+
width="8"
1828
+
height="6"
1829
+
fill="currentColor"
1830
+
rx="1"
1831
+
/>
1832
+
<rect
1833
+
x="2"
1834
+
y="12"
1835
+
width="8"
1836
+
height="8"
1837
+
fill="currentColor"
1838
+
rx="1"
1839
+
/>
1840
+
<rect
1841
+
x="12"
1842
+
y="16"
1843
+
width="8"
1844
+
height="4"
1845
+
fill="currentColor"
1846
+
rx="1"
1847
+
/>
1848
+
</svg>
1849
+
</Button>
1850
+
</div>
1678
1851
<div
1679
1852
id="masonry-container"
1680
1853
class="h-0 overflow-hidden relative mx-auto w-full"
1681
-
_="on load or htmx:afterSettle call computeMasonry()"
1854
+
_="on load or htmx:afterSettle call computeLayout()"
1682
1855
>
1683
1856
{gallery.items?.filter(isPhotoView)?.length
1684
1857
? gallery?.items
···
1731
1904
);
1732
1905
}
1733
1906
1734
-
function ShareGalleryButton({
1735
-
gallery,
1736
-
}: Readonly<{ gallery: GalleryView }>) {
1907
+
function GallerySortDialog({ gallery }: Readonly<{ gallery: GalleryView }>) {
1908
+
return (
1909
+
<Dialog>
1910
+
<Dialog.Content class="dark:bg-zinc-950 relative">
1911
+
<Dialog.X class="fill-zinc-950 dark:fill-zinc-50" />
1912
+
<Dialog.Title>Sort gallery</Dialog.Title>
1913
+
<p class="my-2 text-center">Drag photos to rearrange</p>
1914
+
<form
1915
+
hx-post={`/actions/gallery/${new AtUri(gallery.uri).rkey}/sort`}
1916
+
hx-trigger="submit"
1917
+
hx-swap="none"
1918
+
>
1919
+
<div class="sortable grid grid-cols-3 sm:grid-cols-5 gap-2 mt-2">
1920
+
{gallery?.items?.filter(isPhotoView).map((item) => (
1921
+
<div
1922
+
key={item.cid}
1923
+
class="relative aspect-square cursor-grab"
1924
+
>
1925
+
<input type="hidden" name="item" value={item.uri} />
1926
+
<img
1927
+
src={item.fullsize}
1928
+
alt={item.alt}
1929
+
class="w-full h-full absolute object-cover"
1930
+
/>
1931
+
</div>
1932
+
))}
1933
+
</div>
1934
+
<div class="flex flex-col gap-2 mt-2">
1935
+
<Button
1936
+
variant="primary"
1937
+
type="submit"
1938
+
class="w-full"
1939
+
>
1940
+
Save
1941
+
</Button>
1942
+
<Button
1943
+
variant="secondary"
1944
+
type="button"
1945
+
class="w-full"
1946
+
_={Dialog._closeOnClick}
1947
+
>
1948
+
Cancel
1949
+
</Button>
1950
+
</div>
1951
+
</form>
1952
+
</Dialog.Content>
1953
+
</Dialog>
1954
+
);
1955
+
}
1956
+
1957
+
function ShareGalleryButton({ gallery }: Readonly<{ gallery: GalleryView }>) {
1737
1958
return (
1738
1959
<>
1739
1960
<input
···
1787
2008
}: Readonly<{ gallery?: GalleryView | null }>) {
1788
2009
return (
1789
2010
<Dialog id="gallery-dialog" class="z-30">
1790
-
<Dialog.Content class="dark:bg-zinc-950">
2011
+
<Dialog.Content class="dark:bg-zinc-950 relative">
2012
+
<Dialog.X class="fill-zinc-950 dark:fill-zinc-50" />
1791
2013
<Dialog.Title>
1792
2014
{gallery ? "Edit gallery" : "Create a new gallery"}
1793
2015
</Dialog.Title>
···
1907
2129
);
1908
2130
}
1909
2131
1910
-
function AltTextButton({
1911
-
photoUri,
1912
-
}: Readonly<{ photoUri: string }>) {
2132
+
function AltTextButton({ photoUri }: Readonly<{ photoUri: string }>) {
1913
2133
return (
1914
2134
<div
1915
-
class="bg-zinc-950 dark:bg-zinc-950 py-[1px] px-[3px] absolute top-1 left-1 sm:top-1 sm:left-1 cursor-pointer flex items-center justify-center text-xs text-white font-semibold z-10"
2135
+
class="bg-zinc-950 dark:bg-zinc-950 py-[1px] px-[3px] absolute top-2 left-2 cursor-pointer flex items-center justify-center text-xs text-white font-semibold z-10"
1916
2136
hx-get={`/dialogs/photo/${new AtUri(photoUri).rkey}/alt`}
1917
2137
hx-trigger="click"
1918
2138
hx-target="#layout"
···
1937
2157
}>) {
1938
2158
return (
1939
2159
<Dialog id="photo-dialog" class="bg-zinc-950 z-30">
2160
+
<Dialog.X />
1940
2161
{nextImage
1941
2162
? (
1942
2163
<div
···
1987
2208
}>) {
1988
2209
return (
1989
2210
<Dialog id="photo-alt-dialog" class="z-30">
1990
-
<Dialog.Content class="dark:bg-zinc-950">
2211
+
<Dialog.Content class="dark:bg-zinc-950 relative">
2212
+
<Dialog.X class="fill-zinc-950 dark:fill-zinc-50" />
1991
2213
<Dialog.Title>Add alt text</Dialog.Title>
1992
2214
<div class="aspect-square relative">
1993
2215
<img
···
2035
2257
}>) {
2036
2258
return (
2037
2259
<Dialog id="photo-select-dialog" class="z-30">
2038
-
<Dialog.Content class="w-full max-w-5xl dark:bg-zinc-950 sm:min-h-screen flex flex-col">
2260
+
<Dialog.Content class="w-full max-w-5xl dark:bg-zinc-950 sm:min-h-screen flex flex-col relative">
2261
+
<Dialog.X class="fill-zinc-950 dark:fill-zinc-50" />
2039
2262
<Dialog.Title>Add photos</Dialog.Title>
2040
2263
{photos.length
2041
2264
? (
···
2047
2270
: null}
2048
2271
{photos.length
2049
2272
? (
2050
-
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4 my-4 flex-1">
2273
+
<div class="grid grid-cols-3 sm:grid-cols-5 gap-4 my-4 flex-1">
2051
2274
{photos.map((photo) => (
2052
2275
<PhotoSelectButton
2053
2276
key={photo.cid}
···
2169
2392
uri: photo.uri,
2170
2393
cid: photo.photo.ref.toString(),
2171
2394
thumb:
2172
-
`https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${photo.photo.ref.toString()}@webp`,
2395
+
`https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${photo.photo.ref.toString()}@jpeg`,
2173
2396
fullsize:
2174
-
`https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${photo.photo.ref.toString()}@webp`,
2397
+
`https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${photo.photo.ref.toString()}@jpeg`,
2175
2398
alt: photo.alt,
2176
2399
aspectRatio: photo.aspectRatio,
2177
2400
};
···
2434
2657
];
2435
2658
}
2436
2659
2437
-
function publicGalleryLink(
2438
-
handle: string,
2439
-
galleryUri: string,
2440
-
): string {
2660
+
function publicGalleryLink(handle: string, galleryUri: string): string {
2441
2661
return `${PUBLIC_URL}/profile/${handle}/${new AtUri(galleryUri).rkey}`;
2442
2662
}
+84
-2
static/masonry.js
+84
-2
static/masonry.js
···
1
1
// deno-lint-ignore-file
2
2
3
3
let masonryObserverInitialized = false;
4
+
let layoutMode = "justified";
5
+
6
+
function computeLayout() {
7
+
if (layoutMode === "masonry") {
8
+
computeMasonry();
9
+
} else {
10
+
computeJustified();
11
+
}
12
+
}
13
+
14
+
function toggleLayout(layout = "justified") {
15
+
layoutMode = layout;
16
+
computeLayout();
17
+
}
4
18
5
19
function computeMasonry() {
6
20
const container = document.getElementById("masonry-container");
···
52
66
container.style.height = `${Math.max(...columnHeights)}px`;
53
67
}
54
68
69
+
function computeJustified() {
70
+
const container = document.getElementById("masonry-container");
71
+
if (!container) return;
72
+
73
+
const spacing = 8;
74
+
const containerWidth = container.offsetWidth;
75
+
76
+
if (containerWidth === 0) {
77
+
requestAnimationFrame(computeJustified);
78
+
return;
79
+
}
80
+
81
+
const tiles = Array.from(container.querySelectorAll(".masonry-tile"));
82
+
let currentRow = [];
83
+
let rowAspectRatioSum = 0;
84
+
let yOffset = 0;
85
+
86
+
// Clear all styles before layout
87
+
tiles.forEach((tile) => {
88
+
Object.assign(tile.style, {
89
+
position: "absolute",
90
+
left: "0px",
91
+
top: "0px",
92
+
width: "auto",
93
+
height: "auto",
94
+
});
95
+
});
96
+
97
+
for (let i = 0; i < tiles.length; i++) {
98
+
const tile = tiles[i];
99
+
const imgW = parseFloat(tile.dataset.width);
100
+
const imgH = parseFloat(tile.dataset.height);
101
+
if (!imgW || !imgH) continue;
102
+
103
+
const aspectRatio = imgW / imgH;
104
+
currentRow.push({ tile, aspectRatio, imgW, imgH });
105
+
rowAspectRatioSum += aspectRatio;
106
+
107
+
// Estimate if row is "full" enough
108
+
const estimatedRowHeight =
109
+
(containerWidth - (currentRow.length - 1) * spacing) / rowAspectRatioSum;
110
+
111
+
// If height is reasonable or we're at the end, render the row
112
+
if (estimatedRowHeight < 300 || i === tiles.length - 1) {
113
+
let xOffset = 0;
114
+
115
+
for (const item of currentRow) {
116
+
const width = estimatedRowHeight * item.aspectRatio;
117
+
Object.assign(item.tile.style, {
118
+
position: "absolute",
119
+
top: `${yOffset}px`,
120
+
left: `${xOffset}px`,
121
+
width: `${width}px`,
122
+
height: `${estimatedRowHeight}px`,
123
+
});
124
+
xOffset += width + spacing;
125
+
}
126
+
127
+
yOffset += estimatedRowHeight + spacing;
128
+
currentRow = [];
129
+
rowAspectRatioSum = 0;
130
+
}
131
+
}
132
+
133
+
container.style.position = "relative";
134
+
container.style.height = `${yOffset}px`;
135
+
}
136
+
55
137
function observeMasonry() {
56
138
if (masonryObserverInitialized) return;
57
139
masonryObserverInitialized = true;
···
61
143
62
144
// Observe parent resize
63
145
if (typeof ResizeObserver !== "undefined") {
64
-
const resizeObserver = new ResizeObserver(() => computeMasonry());
146
+
const resizeObserver = new ResizeObserver(() => computeLayout());
65
147
if (container.parentElement) {
66
148
resizeObserver.observe(container.parentElement);
67
149
}
···
69
151
70
152
// Observe inner content changes (tiles being added/removed)
71
153
const mutationObserver = new MutationObserver(() => {
72
-
computeMasonry();
154
+
computeLayout();
73
155
});
74
156
75
157
mutationObserver.observe(container, {
+8
static/sortable.js
+8
static/sortable.js
+104
-25
static/styles.css
+104
-25
static/styles.css
···
207
207
.inset-0 {
208
208
inset: calc(var(--spacing) * 0);
209
209
}
210
-
.top-1 {
211
-
top: calc(var(--spacing) * 1);
212
-
}
213
210
.top-2 {
214
211
top: calc(var(--spacing) * 2);
215
212
}
···
237
234
.left-0 {
238
235
left: calc(var(--spacing) * 0);
239
236
}
240
-
.left-1 {
241
-
left: calc(var(--spacing) * 1);
237
+
.left-2 {
238
+
left: calc(var(--spacing) * 2);
242
239
}
243
240
.z-10 {
244
241
z-index: 10;
···
390
387
.shrink-0 {
391
388
flex-shrink: 0;
392
389
}
390
+
.cursor-grab {
391
+
cursor: grab;
392
+
}
393
393
.cursor-pointer {
394
394
cursor: pointer;
395
395
}
···
402
402
.grid-cols-2 {
403
403
grid-template-columns: repeat(2, minmax(0, 1fr));
404
404
}
405
+
.grid-cols-3 {
406
+
grid-template-columns: repeat(3, minmax(0, 1fr));
407
+
}
405
408
.flex-col {
406
409
flex-direction: column;
407
410
}
···
413
416
}
414
417
.justify-center {
415
418
justify-content: center;
419
+
}
420
+
.justify-end {
421
+
justify-content: flex-end;
416
422
}
417
423
.gap-2 {
418
424
gap: calc(var(--spacing) * 2);
···
463
469
border-bottom-style: var(--tw-border-style);
464
470
border-bottom-width: 1px;
465
471
}
472
+
.border-zinc-100 {
473
+
border-color: var(--color-zinc-100);
474
+
}
466
475
.border-zinc-200 {
467
476
border-color: var(--color-zinc-200);
468
477
}
···
478
487
background-color: color-mix(in oklab, var(--color-black) 80%, transparent);
479
488
}
480
489
}
490
+
.bg-sky-500 {
491
+
background-color: var(--color-sky-500);
492
+
}
481
493
.bg-zinc-100 {
482
494
background-color: var(--color-zinc-100);
483
495
}
···
489
501
}
490
502
.bg-zinc-950 {
491
503
background-color: var(--color-zinc-950);
504
+
}
505
+
.fill-zinc-950 {
506
+
fill: var(--color-zinc-950);
492
507
}
493
508
.object-contain {
494
509
object-fit: contain;
···
587
602
.ring-sky-500 {
588
603
--tw-ring-color: var(--color-sky-500);
589
604
}
605
+
.filter {
606
+
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
607
+
}
590
608
.group-data-\[added\=true\]\:block {
591
609
&:is(:where(.group)[data-added="true"] *) {
592
610
display: block;
···
610
628
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
611
629
}
612
630
}
631
+
.data-\[selected\=false\]\:border-transparent {
632
+
&[data-selected="false"] {
633
+
border-color: transparent;
634
+
}
635
+
}
636
+
.data-\[selected\=false\]\:bg-transparent {
637
+
&[data-selected="false"] {
638
+
background-color: transparent;
639
+
}
640
+
}
613
641
.data-\[state\=pending\]\:opacity-50 {
614
642
&[data-state="pending"] {
615
643
opacity: 50%;
616
644
}
617
645
}
618
-
.sm\:top-1 {
619
-
@media (width >= 40rem) {
620
-
top: calc(var(--spacing) * 1);
621
-
}
622
-
}
623
646
.sm\:right-1 {
624
647
@media (width >= 40rem) {
625
648
right: calc(var(--spacing) * 1);
···
628
651
.sm\:bottom-1 {
629
652
@media (width >= 40rem) {
630
653
bottom: calc(var(--spacing) * 1);
631
-
}
632
-
}
633
-
.sm\:left-1 {
634
-
@media (width >= 40rem) {
635
-
left: calc(var(--spacing) * 1);
636
654
}
637
655
}
638
656
.sm\:h-screen {
···
705
723
background-color: var(--color-zinc-950);
706
724
}
707
725
}
726
+
.dark\:fill-zinc-50 {
727
+
@media (prefers-color-scheme: dark) {
728
+
fill: var(--color-zinc-50);
729
+
}
730
+
}
708
731
.dark\:text-white {
709
732
@media (prefers-color-scheme: dark) {
710
733
color: var(--color-white);
···
721
744
}
722
745
}
723
746
}
724
-
.htmx-request.htmx-indicator {
725
-
display: inline;
726
-
}
727
-
.htmx-indicator {
728
-
display: none;
729
-
}
730
-
.htmx-request #submit-button {
731
-
opacity: 0.5;
732
-
pointer-events: none;
733
-
}
734
747
@property --tw-space-y-reverse {
735
748
syntax: "*";
736
749
inherits: false;
···
750
763
syntax: "*";
751
764
inherits: false;
752
765
}
766
+
@property --tw-blur {
767
+
syntax: "*";
768
+
inherits: false;
769
+
}
770
+
@property --tw-brightness {
771
+
syntax: "*";
772
+
inherits: false;
773
+
}
774
+
@property --tw-contrast {
775
+
syntax: "*";
776
+
inherits: false;
777
+
}
778
+
@property --tw-grayscale {
779
+
syntax: "*";
780
+
inherits: false;
781
+
}
782
+
@property --tw-hue-rotate {
783
+
syntax: "*";
784
+
inherits: false;
785
+
}
786
+
@property --tw-invert {
787
+
syntax: "*";
788
+
inherits: false;
789
+
}
790
+
@property --tw-opacity {
791
+
syntax: "*";
792
+
inherits: false;
793
+
}
794
+
@property --tw-saturate {
795
+
syntax: "*";
796
+
inherits: false;
797
+
}
798
+
@property --tw-sepia {
799
+
syntax: "*";
800
+
inherits: false;
801
+
}
802
+
@property --tw-drop-shadow {
803
+
syntax: "*";
804
+
inherits: false;
805
+
}
806
+
@property --tw-drop-shadow-color {
807
+
syntax: "*";
808
+
inherits: false;
809
+
}
810
+
@property --tw-drop-shadow-alpha {
811
+
syntax: "<percentage>";
812
+
inherits: false;
813
+
initial-value: 100%;
814
+
}
815
+
@property --tw-drop-shadow-size {
816
+
syntax: "*";
817
+
inherits: false;
818
+
}
753
819
@property --tw-shadow {
754
820
syntax: "*";
755
821
inherits: false;
···
822
888
--tw-space-x-reverse: 0;
823
889
--tw-border-style: solid;
824
890
--tw-font-weight: initial;
891
+
--tw-blur: initial;
892
+
--tw-brightness: initial;
893
+
--tw-contrast: initial;
894
+
--tw-grayscale: initial;
895
+
--tw-hue-rotate: initial;
896
+
--tw-invert: initial;
897
+
--tw-opacity: initial;
898
+
--tw-saturate: initial;
899
+
--tw-sepia: initial;
900
+
--tw-drop-shadow: initial;
901
+
--tw-drop-shadow-color: initial;
902
+
--tw-drop-shadow-alpha: 100%;
903
+
--tw-drop-shadow-size: initial;
825
904
--tw-shadow: 0 0 #0000;
826
905
--tw-shadow-color: initial;
827
906
--tw-shadow-alpha: 100%;