forked from pdsls.dev/pdsls
atproto explorer

Compare changes

Choose any two refs to compare.

-2
index.html
··· 11 11 <meta property="description" content="Browse the public data on atproto" /> 12 12 <link rel="manifest" href="/manifest.json" /> 13 13 <title>PDSls</title> 14 - <link rel="preconnect" href="https://rsms.me/" /> 15 - <link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> 16 14 <link rel="preconnect" href="https://fonts.bunny.net" /> 17 15 <link href="https://fonts.bunny.net/css?family=roboto-mono:400" rel="stylesheet" /> 18 16 <link href="https://fonts.cdnfonts.com/css/pecita" rel="stylesheet" />
+13 -13
package.json
··· 9 9 "serve": "vite preview" 10 10 }, 11 11 "devDependencies": { 12 - "@iconify-json/lucide": "^1.2.77", 12 + "@iconify-json/lucide": "^1.2.81", 13 13 "@iconify/tailwind4": "^1.2.0", 14 - "@tailwindcss/vite": "^4.1.17", 14 + "@tailwindcss/vite": "^4.1.18", 15 15 "prettier": "^3.7.4", 16 16 "prettier-plugin-organize-imports": "^4.3.0", 17 17 "prettier-plugin-tailwindcss": "^0.7.2", 18 - "tailwindcss": "^4.1.17", 18 + "tailwindcss": "^4.1.18", 19 19 "typescript": "^5.9.3", 20 - "vite": "^7.2.6", 20 + "vite": "^7.2.7", 21 21 "vite-plugin-solid": "^2.11.10" 22 22 }, 23 23 "dependencies": { 24 24 "@atcute/atproto": "^3.1.9", 25 - "@atcute/bluesky": "^3.2.11", 26 - "@atcute/client": "^4.1.0", 27 - "@atcute/crypto": "^2.2.6", 25 + "@atcute/bluesky": "^3.2.14", 26 + "@atcute/client": "^4.1.1", 27 + "@atcute/crypto": "^2.3.0", 28 28 "@atcute/did-plc": "^0.2.0", 29 29 "@atcute/identity": "^1.1.3", 30 - "@atcute/identity-resolver": "^1.1.4", 31 - "@atcute/leaflet": "^1.0.12", 32 - "@atcute/lexicon-doc": "^2.0.4", 30 + "@atcute/identity-resolver": "^1.2.0", 31 + "@atcute/leaflet": "^1.0.14", 32 + "@atcute/lexicon-doc": "^2.0.5", 33 33 "@atcute/lexicon-resolver": "^0.1.5", 34 34 "@atcute/lexicons": "^1.2.5", 35 35 "@atcute/multibase": "^1.1.6", 36 - "@atcute/oauth-browser-client": "^2.0.1", 36 + "@atcute/oauth-browser-client": "^2.0.3", 37 37 "@atcute/repo": "^0.1.0", 38 - "@atcute/tangled": "^1.0.12", 38 + "@atcute/tangled": "^1.0.13", 39 39 "@atcute/tid": "^1.0.3", 40 40 "@codemirror/commands": "^6.10.0", 41 41 "@codemirror/lang-json": "^6.0.2", 42 42 "@codemirror/lint": "^6.9.2", 43 43 "@codemirror/state": "^6.5.2", 44 - "@codemirror/view": "^6.38.8", 44 + "@codemirror/view": "^6.39.4", 45 45 "@fsegurai/codemirror-theme-basic-dark": "^6.2.3", 46 46 "@fsegurai/codemirror-theme-basic-light": "^6.2.3", 47 47 "@mary/exif-rm": "jsr:^0.2.2",
+194 -193
pnpm-lock.yaml
··· 12 12 specifier: ^3.1.9 13 13 version: 3.1.9 14 14 '@atcute/bluesky': 15 - specifier: ^3.2.11 16 - version: 3.2.11 15 + specifier: ^3.2.14 16 + version: 3.2.14 17 17 '@atcute/client': 18 - specifier: ^4.1.0 19 - version: 4.1.0 18 + specifier: ^4.1.1 19 + version: 4.1.1 20 20 '@atcute/crypto': 21 - specifier: ^2.2.6 22 - version: 2.2.6 21 + specifier: ^2.3.0 22 + version: 2.3.0 23 23 '@atcute/did-plc': 24 24 specifier: ^0.2.0 25 25 version: 0.2.0 ··· 27 27 specifier: ^1.1.3 28 28 version: 1.1.3 29 29 '@atcute/identity-resolver': 30 - specifier: ^1.1.4 31 - version: 1.1.4(@atcute/identity@1.1.3) 30 + specifier: ^1.2.0 31 + version: 1.2.0(@atcute/identity@1.1.3) 32 32 '@atcute/leaflet': 33 - specifier: ^1.0.12 34 - version: 1.0.12 33 + specifier: ^1.0.14 34 + version: 1.0.14 35 35 '@atcute/lexicon-doc': 36 - specifier: ^2.0.4 37 - version: 2.0.4 36 + specifier: ^2.0.5 37 + version: 2.0.5 38 38 '@atcute/lexicon-resolver': 39 39 specifier: ^0.1.5 40 - version: 0.1.5(@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.3))(@atcute/identity@1.1.3) 40 + version: 0.1.5(@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3))(@atcute/identity@1.1.3) 41 41 '@atcute/lexicons': 42 42 specifier: ^1.2.5 43 43 version: 1.2.5 ··· 45 45 specifier: ^1.1.6 46 46 version: 1.1.6 47 47 '@atcute/oauth-browser-client': 48 - specifier: ^2.0.1 49 - version: 2.0.1 48 + specifier: ^2.0.3 49 + version: 2.0.3(@atcute/identity@1.1.3) 50 50 '@atcute/repo': 51 51 specifier: ^0.1.0 52 52 version: 0.1.0 53 53 '@atcute/tangled': 54 - specifier: ^1.0.12 55 - version: 1.0.12 54 + specifier: ^1.0.13 55 + version: 1.0.13 56 56 '@atcute/tid': 57 57 specifier: ^1.0.3 58 58 version: 1.0.3 ··· 69 69 specifier: ^6.5.2 70 70 version: 6.5.2 71 71 '@codemirror/view': 72 - specifier: ^6.38.8 73 - version: 6.38.8 72 + specifier: ^6.39.4 73 + version: 6.39.4 74 74 '@fsegurai/codemirror-theme-basic-dark': 75 75 specifier: ^6.2.3 76 - version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3) 76 + version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3) 77 77 '@fsegurai/codemirror-theme-basic-light': 78 78 specifier: ^6.2.3 79 - version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3) 79 + version: 6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3) 80 80 '@mary/exif-rm': 81 81 specifier: jsr:^0.2.2 82 82 version: '@jsr/mary__exif-rm@0.2.2' ··· 97 97 version: 1.9.10 98 98 devDependencies: 99 99 '@iconify-json/lucide': 100 - specifier: ^1.2.77 101 - version: 1.2.77 100 + specifier: ^1.2.81 101 + version: 1.2.81 102 102 '@iconify/tailwind4': 103 103 specifier: ^1.2.0 104 - version: 1.2.0(tailwindcss@4.1.17) 104 + version: 1.2.0(tailwindcss@4.1.18) 105 105 '@tailwindcss/vite': 106 - specifier: ^4.1.17 107 - version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 106 + specifier: ^4.1.18 107 + version: 4.1.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 108 108 prettier: 109 109 specifier: ^3.7.4 110 110 version: 3.7.4 ··· 115 115 specifier: ^0.7.2 116 116 version: 0.7.2(prettier-plugin-organize-imports@4.3.0(prettier@3.7.4)(typescript@5.9.3))(prettier@3.7.4) 117 117 tailwindcss: 118 - specifier: ^4.1.17 119 - version: 4.1.17 118 + specifier: ^4.1.18 119 + version: 4.1.18 120 120 typescript: 121 121 specifier: ^5.9.3 122 122 version: 5.9.3 123 123 vite: 124 - specifier: ^7.2.6 125 - version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 124 + specifier: ^7.2.7 125 + version: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 126 126 vite-plugin-solid: 127 127 specifier: ^2.11.10 128 - version: 2.11.10(solid-js@1.9.10)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 128 + version: 2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 129 129 130 130 packages: 131 131 ··· 135 135 '@atcute/atproto@3.1.9': 136 136 resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==} 137 137 138 - '@atcute/bluesky@3.2.11': 139 - resolution: {integrity: sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A==} 138 + '@atcute/bluesky@3.2.14': 139 + resolution: {integrity: sha512-XlVuF55AYIyplmKvlGLlj+cUvk9ggxNRPczkTPIY991xJ4qDxDHpBJ39ekAV4dWcuBoRo2o9JynzpafPu2ljDA==} 140 140 141 141 '@atcute/car@3.1.3': 142 142 resolution: {integrity: sha512-WJ13bAEt7TjDMVi09ubjLtvhdljbWInGm9Kfy7Y6NhrmiyC/aZYaA/zHX/bHI6xv1c/h3SQduWqxOr4ae49eqA==} ··· 150 150 '@atcute/cid@2.2.6': 151 151 resolution: {integrity: sha512-bTAHHbJ24p+E//V4KCS4xdmd39o211jJswvqQOevj7vk+5IYcgDLx1ryZWZ1sEPOo9x875li/kj5gpKL14RDwQ==} 152 152 153 - '@atcute/client@4.1.0': 154 - resolution: {integrity: sha512-AYhSu3RSDA2VDkVGOmad320NRbUUUf5pCFWJcOzlk25YC/4kyzmMFfpzhf1jjjEcY+anNBXGGhav/kKB1evggQ==} 153 + '@atcute/client@4.1.1': 154 + resolution: {integrity: sha512-FROCbTTCeL5u4tO/n72jDEKyKqjdlXMB56Ehve3W/gnnLGCYWvN42sS7tvL1Mgu6sbO3yZwsXKDrmM2No4XpjA==} 155 155 156 - '@atcute/crypto@2.2.6': 157 - resolution: {integrity: sha512-vkuexF+kmrKE1/Uqzub99Qi4QpnxA2jbu60E6PTgL4XypELQ6rb59MB/J1VbY2gs0kd3ET7+L3+NWpKD5nXyfA==} 156 + '@atcute/crypto@2.3.0': 157 + resolution: {integrity: sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ==} 158 158 159 159 '@atcute/did-plc@0.2.0': 160 160 resolution: {integrity: sha512-1sGek8GRM/Ph7nLVRREm8FqM7g4shGckItvdVwJcRbUa8Rh0zOsXQa0QyYWAC0k40BhkqO9FwKXhJEaXCmF5oQ==} 161 161 162 - '@atcute/identity-resolver@1.1.4': 163 - resolution: {integrity: sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==} 162 + '@atcute/identity-resolver@1.2.0': 163 + resolution: {integrity: sha512-5UbSJfdV3JIkF8ksXz7g4nKBWasf2wROvzM66cfvTIWydWFO6/oS1KZd+zo9Eokje5Scf5+jsY9ZfgVARLepXg==} 164 164 peerDependencies: 165 165 '@atcute/identity': ^1.0.0 166 166 167 167 '@atcute/identity@1.1.3': 168 168 resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} 169 169 170 - '@atcute/leaflet@1.0.12': 171 - resolution: {integrity: sha512-T5laBTl8vwzy0eZXBy07IQSjsLqhbZmRJsffnNQ6XMSc+lnCZ/NHfuKy8TNJbDU6dc26Z7o5l0ELfWz5QESo+w==} 170 + '@atcute/leaflet@1.0.14': 171 + resolution: {integrity: sha512-TWbtB7b73GChBaYwfd7aWFyGVObZ/DqrRtwkpWGm1GO8zZmQ9eJyKDUnXim7NOAs2hmKQ1u2wk2AM4AYzkF5Gg==} 172 172 173 - '@atcute/lexicon-doc@2.0.4': 174 - resolution: {integrity: sha512-YfwlYFoYiBvRIYG0I1zsINCTFugFtS8l67uT3nQ04zdKVflzdg8uUj8cNZYRNY1V7okoOPdikhR4kPFhYGyemw==} 173 + '@atcute/lexicon-doc@2.0.5': 174 + resolution: {integrity: sha512-fNCp94ehGjWFZMIqP6pWD1F9MOJogNCyqsaMVZluPSIclZ+lDL528iXB56aW4u0eSiD6Y9WJB1OI/lElG39cSA==} 175 175 176 176 '@atcute/lexicon-resolver@0.1.5': 177 177 resolution: {integrity: sha512-0bx1/zdMQPuxvRcHW6ykAxRxktC2rEZLoAVSFoLSWDAA92Tf09F9QPK5wgXSF4MNODm1dvzMEdWSMIvlg8sr3A==} ··· 188 188 '@atcute/multibase@1.1.6': 189 189 resolution: {integrity: sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg==} 190 190 191 - '@atcute/oauth-browser-client@2.0.1': 192 - resolution: {integrity: sha512-lG021GkeORG06zfFf4bH85egObjBEKHNgAWHvbtY/E2dX4wxo88hf370pJDx8acdnuUJLJ2VKPikJtZwo4Heeg==} 191 + '@atcute/oauth-browser-client@2.0.3': 192 + resolution: {integrity: sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ==} 193 193 194 194 '@atcute/repo@0.1.0': 195 195 resolution: {integrity: sha512-INiYAuma8dydBu7cqd2WVpcXh3mzhIepYBUqFWAK5MqMulPRLTRCc/9GW3G9pxYrOdlvLCVamG2Jf8XK0nuFEw==} 196 196 197 - '@atcute/tangled@1.0.12': 198 - resolution: {integrity: sha512-JKA5sOhd8SLhDFhY+PKHqLLytQBBKSiwcaEzfYUJBeyfvqXFPNNAwvRbe3VST4IQ3izoOu3O0R9/b1mjL45UzA==} 197 + '@atcute/tangled@1.0.13': 198 + resolution: {integrity: sha512-K95jmjDXl/f1FFzOJkk07ibNbFsPmn64sdrMACxQmUibO9WcfSjzjZLPXuH6WHFnCNtIBG3x1FQ7ndQgLoZAmw==} 199 199 200 200 '@atcute/tid@1.0.3': 201 201 resolution: {integrity: sha512-wfMJx1IMdnu0CZgWl0uR4JO2s6PGT1YPhpytD4ZHzEYKKQVuqV6Eb/7vieaVo1eYNMp2FrY67FZObeR7utRl2w==} 202 202 203 - '@atcute/uint8array@1.0.5': 204 - resolution: {integrity: sha512-XLWWxoR2HNl2qU+FCr0rp1APwJXci7HnzbOQLxK55OaMNBXZ19+xNC5ii4QCsThsDxa4JS/JTzuiQLziITWf2Q==} 203 + '@atcute/uint8array@1.0.6': 204 + resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==} 205 205 206 206 '@atcute/util-fetch@1.0.4': 207 207 resolution: {integrity: sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg==} ··· 315 315 '@codemirror/state@6.5.2': 316 316 resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} 317 317 318 - '@codemirror/view@6.38.8': 319 - resolution: {integrity: sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==} 318 + '@codemirror/view@6.39.4': 319 + resolution: {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==} 320 320 321 321 '@cyberalien/svg-utils@1.0.11': 322 322 resolution: {integrity: sha512-qEE9mnyI+avfGT3emKuRs3ucYkITeaV0Xi7VlYN41f+uGnZBecQP3jwz/AF437H9J4Q7qPClHKm4NiTYpNE6hA==} ··· 637 637 '@codemirror/view': ^6.0.0 638 638 '@lezer/highlight': ^1.0.0 639 639 640 - '@iconify-json/lucide@1.2.77': 641 - resolution: {integrity: sha512-FF3Z+np6Ksb0MaoQymhCHZ4xs5Oo8992Fw7By7bCgVCbBCClYV3wxpF8KzsI1FlxHD4ZXR42NVmXuqdW8YQGgA==} 640 + '@iconify-json/lucide@1.2.81': 641 + resolution: {integrity: sha512-6Kz/+SEuD5bkg0KImi0yFem9l6njKp4e1qF1LpQbgRfk7ngsJR/qjlB4y5rM8N1iKiDR/p19cqhmwZxyCWek+w==} 642 642 643 643 '@iconify/tailwind4@1.2.0': 644 644 resolution: {integrity: sha512-+t7XqfojOB0zzZdd8gV7IQZGq1AaIHTlsxMVzagxYR0hAlJCLUD63o3iSlNKRMH3ZR7gZ8y5c9dJ7J431avRbA==} ··· 682 682 '@lezer/json@1.0.3': 683 683 resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} 684 684 685 - '@lezer/lr@1.4.4': 686 - resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} 685 + '@lezer/lr@1.4.5': 686 + resolution: {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==} 687 687 688 688 '@marijn/find-cluster-break@1.0.2': 689 689 resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} ··· 817 817 '@standard-schema/spec@1.0.0': 818 818 resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} 819 819 820 - '@tailwindcss/node@4.1.17': 821 - resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} 820 + '@tailwindcss/node@4.1.18': 821 + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} 822 822 823 - '@tailwindcss/oxide-android-arm64@4.1.17': 824 - resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} 823 + '@tailwindcss/oxide-android-arm64@4.1.18': 824 + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} 825 825 engines: {node: '>= 10'} 826 826 cpu: [arm64] 827 827 os: [android] 828 828 829 - '@tailwindcss/oxide-darwin-arm64@4.1.17': 830 - resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} 829 + '@tailwindcss/oxide-darwin-arm64@4.1.18': 830 + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} 831 831 engines: {node: '>= 10'} 832 832 cpu: [arm64] 833 833 os: [darwin] 834 834 835 - '@tailwindcss/oxide-darwin-x64@4.1.17': 836 - resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} 835 + '@tailwindcss/oxide-darwin-x64@4.1.18': 836 + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} 837 837 engines: {node: '>= 10'} 838 838 cpu: [x64] 839 839 os: [darwin] 840 840 841 - '@tailwindcss/oxide-freebsd-x64@4.1.17': 842 - resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} 841 + '@tailwindcss/oxide-freebsd-x64@4.1.18': 842 + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} 843 843 engines: {node: '>= 10'} 844 844 cpu: [x64] 845 845 os: [freebsd] 846 846 847 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': 848 - resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} 847 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': 848 + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} 849 849 engines: {node: '>= 10'} 850 850 cpu: [arm] 851 851 os: [linux] 852 852 853 - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': 854 - resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} 853 + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': 854 + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} 855 855 engines: {node: '>= 10'} 856 856 cpu: [arm64] 857 857 os: [linux] 858 858 859 - '@tailwindcss/oxide-linux-arm64-musl@4.1.17': 860 - resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} 859 + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': 860 + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} 861 861 engines: {node: '>= 10'} 862 862 cpu: [arm64] 863 863 os: [linux] 864 864 865 - '@tailwindcss/oxide-linux-x64-gnu@4.1.17': 866 - resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} 865 + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': 866 + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} 867 867 engines: {node: '>= 10'} 868 868 cpu: [x64] 869 869 os: [linux] 870 870 871 - '@tailwindcss/oxide-linux-x64-musl@4.1.17': 872 - resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} 871 + '@tailwindcss/oxide-linux-x64-musl@4.1.18': 872 + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} 873 873 engines: {node: '>= 10'} 874 874 cpu: [x64] 875 875 os: [linux] 876 876 877 - '@tailwindcss/oxide-wasm32-wasi@4.1.17': 878 - resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} 877 + '@tailwindcss/oxide-wasm32-wasi@4.1.18': 878 + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} 879 879 engines: {node: '>=14.0.0'} 880 880 cpu: [wasm32] 881 881 bundledDependencies: ··· 886 886 - '@emnapi/wasi-threads' 887 887 - tslib 888 888 889 - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': 890 - resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} 889 + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': 890 + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} 891 891 engines: {node: '>= 10'} 892 892 cpu: [arm64] 893 893 os: [win32] 894 894 895 - '@tailwindcss/oxide-win32-x64-msvc@4.1.17': 896 - resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} 895 + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': 896 + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} 897 897 engines: {node: '>= 10'} 898 898 cpu: [x64] 899 899 os: [win32] 900 900 901 - '@tailwindcss/oxide@4.1.17': 902 - resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} 901 + '@tailwindcss/oxide@4.1.18': 902 + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} 903 903 engines: {node: '>= 10'} 904 904 905 - '@tailwindcss/vite@4.1.17': 906 - resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} 905 + '@tailwindcss/vite@4.1.18': 906 + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} 907 907 peerDependencies: 908 908 vite: ^5.2.0 || ^6 || ^7 909 909 ··· 944 944 solid-js: 945 945 optional: true 946 946 947 - baseline-browser-mapping@2.9.0: 948 - resolution: {integrity: sha512-Mh++g+2LPfzZToywfE1BUzvZbfOY52Nil0rn9H1CPC5DJ7fX+Vir7nToBeoiSbB1zTNeGYbELEvJESujgGrzXw==} 947 + baseline-browser-mapping@2.9.7: 948 + resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==} 949 949 hasBin: true 950 950 951 951 boolbase@1.0.0: ··· 956 956 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 957 957 hasBin: true 958 958 959 - caniuse-lite@1.0.30001759: 960 - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} 959 + caniuse-lite@1.0.30001760: 960 + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} 961 961 962 962 codemirror@6.0.2: 963 963 resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} ··· 1023 1023 domutils@3.2.2: 1024 1024 resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} 1025 1025 1026 - electron-to-chromium@1.5.263: 1027 - resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==} 1026 + electron-to-chromium@1.5.267: 1027 + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} 1028 1028 1029 - enhanced-resolve@5.18.3: 1030 - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} 1029 + enhanced-resolve@5.18.4: 1030 + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} 1031 1031 engines: {node: '>=10.13.0'} 1032 1032 1033 1033 entities@4.5.0: ··· 1360 1360 engines: {node: '>=16'} 1361 1361 hasBin: true 1362 1362 1363 - tailwindcss@4.1.17: 1364 - resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} 1363 + tailwindcss@4.1.18: 1364 + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} 1365 1365 1366 1366 tapable@2.3.0: 1367 1367 resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} ··· 1391 1391 undici-types@7.16.0: 1392 1392 resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} 1393 1393 1394 - update-browserslist-db@1.2.1: 1395 - resolution: {integrity: sha512-R9NcHbbZ45RoWfTdhn1J9SS7zxNvlddv4YRrHTUaFdtjbmfncfedB45EC9IaqJQ97iAR1GZgOfyRQO+ExIF6EQ==} 1394 + update-browserslist-db@1.2.2: 1395 + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} 1396 1396 hasBin: true 1397 1397 peerDependencies: 1398 1398 browserslist: '>= 4.21.0' ··· 1407 1407 '@testing-library/jest-dom': 1408 1408 optional: true 1409 1409 1410 - vite@7.2.6: 1411 - resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} 1410 + vite@7.2.7: 1411 + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} 1412 1412 engines: {node: ^20.19.0 || >=22.12.0} 1413 1413 hasBin: true 1414 1414 peerDependencies: ··· 1476 1476 dependencies: 1477 1477 '@atcute/lexicons': 1.2.5 1478 1478 1479 - '@atcute/bluesky@3.2.11': 1479 + '@atcute/bluesky@3.2.14': 1480 1480 dependencies: 1481 1481 '@atcute/atproto': 3.1.9 1482 1482 '@atcute/lexicons': 1.2.5 ··· 1485 1485 dependencies: 1486 1486 '@atcute/cbor': 2.2.8 1487 1487 '@atcute/cid': 2.2.6 1488 - '@atcute/uint8array': 1.0.5 1488 + '@atcute/uint8array': 1.0.6 1489 1489 '@atcute/varint': 1.0.3 1490 1490 yocto-queue: 1.2.2 1491 1491 ··· 1493 1493 dependencies: 1494 1494 '@atcute/cbor': 2.2.8 1495 1495 '@atcute/cid': 2.2.6 1496 - '@atcute/uint8array': 1.0.5 1496 + '@atcute/uint8array': 1.0.6 1497 1497 '@atcute/varint': 1.0.3 1498 1498 1499 1499 '@atcute/cbor@2.2.8': 1500 1500 dependencies: 1501 1501 '@atcute/cid': 2.2.6 1502 1502 '@atcute/multibase': 1.1.6 1503 - '@atcute/uint8array': 1.0.5 1503 + '@atcute/uint8array': 1.0.6 1504 1504 1505 1505 '@atcute/cid@2.2.6': 1506 1506 dependencies: 1507 1507 '@atcute/multibase': 1.1.6 1508 - '@atcute/uint8array': 1.0.5 1508 + '@atcute/uint8array': 1.0.6 1509 1509 1510 - '@atcute/client@4.1.0': 1510 + '@atcute/client@4.1.1': 1511 1511 dependencies: 1512 1512 '@atcute/identity': 1.1.3 1513 1513 '@atcute/lexicons': 1.2.5 1514 1514 1515 - '@atcute/crypto@2.2.6': 1515 + '@atcute/crypto@2.3.0': 1516 1516 dependencies: 1517 1517 '@atcute/multibase': 1.1.6 1518 - '@atcute/uint8array': 1.0.5 1518 + '@atcute/uint8array': 1.0.6 1519 1519 '@noble/secp256k1': 3.0.0 1520 1520 1521 1521 '@atcute/did-plc@0.2.0': 1522 1522 dependencies: 1523 1523 '@atcute/cbor': 2.2.8 1524 1524 '@atcute/cid': 2.2.6 1525 - '@atcute/crypto': 2.2.6 1525 + '@atcute/crypto': 2.3.0 1526 1526 '@atcute/identity': 1.1.3 1527 1527 '@atcute/lexicons': 1.2.5 1528 1528 '@atcute/multibase': 1.1.6 1529 - '@atcute/uint8array': 1.0.5 1529 + '@atcute/uint8array': 1.0.6 1530 1530 '@badrap/valita': 0.4.6 1531 1531 1532 - '@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.3)': 1532 + '@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3)': 1533 1533 dependencies: 1534 1534 '@atcute/identity': 1.1.3 1535 1535 '@atcute/lexicons': 1.2.5 ··· 1541 1541 '@atcute/lexicons': 1.2.5 1542 1542 '@badrap/valita': 0.4.6 1543 1543 1544 - '@atcute/leaflet@1.0.12': 1544 + '@atcute/leaflet@1.0.14': 1545 1545 dependencies: 1546 1546 '@atcute/atproto': 3.1.9 1547 1547 '@atcute/lexicons': 1.2.5 1548 1548 1549 - '@atcute/lexicon-doc@2.0.4': 1549 + '@atcute/lexicon-doc@2.0.5': 1550 1550 dependencies: 1551 1551 '@atcute/identity': 1.1.3 1552 1552 '@atcute/lexicons': 1.2.5 1553 1553 '@badrap/valita': 0.4.6 1554 1554 1555 - '@atcute/lexicon-resolver@0.1.5(@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)': 1555 + '@atcute/lexicon-resolver@0.1.5(@atcute/identity-resolver@1.2.0(@atcute/identity@1.1.3))(@atcute/identity@1.1.3)': 1556 1556 dependencies: 1557 - '@atcute/crypto': 2.2.6 1557 + '@atcute/crypto': 2.3.0 1558 1558 '@atcute/identity': 1.1.3 1559 - '@atcute/identity-resolver': 1.1.4(@atcute/identity@1.1.3) 1560 - '@atcute/lexicon-doc': 2.0.4 1559 + '@atcute/identity-resolver': 1.2.0(@atcute/identity@1.1.3) 1560 + '@atcute/lexicon-doc': 2.0.5 1561 1561 '@atcute/lexicons': 1.2.5 1562 1562 '@atcute/repo': 0.1.0 1563 1563 '@atcute/util-fetch': 1.0.4 ··· 1572 1572 dependencies: 1573 1573 '@atcute/cbor': 2.2.8 1574 1574 '@atcute/cid': 2.2.6 1575 - '@atcute/uint8array': 1.0.5 1575 + '@atcute/uint8array': 1.0.6 1576 1576 1577 1577 '@atcute/multibase@1.1.6': 1578 1578 dependencies: 1579 - '@atcute/uint8array': 1.0.5 1579 + '@atcute/uint8array': 1.0.6 1580 1580 1581 - '@atcute/oauth-browser-client@2.0.1': 1581 + '@atcute/oauth-browser-client@2.0.3(@atcute/identity@1.1.3)': 1582 1582 dependencies: 1583 - '@atcute/client': 4.1.0 1584 - '@atcute/identity': 1.1.3 1585 - '@atcute/identity-resolver': 1.1.4(@atcute/identity@1.1.3) 1583 + '@atcute/client': 4.1.1 1584 + '@atcute/identity-resolver': 1.2.0(@atcute/identity@1.1.3) 1586 1585 '@atcute/lexicons': 1.2.5 1587 1586 '@atcute/multibase': 1.1.6 1588 - '@atcute/uint8array': 1.0.5 1587 + '@atcute/uint8array': 1.0.6 1589 1588 nanoid: 5.1.6 1589 + transitivePeerDependencies: 1590 + - '@atcute/identity' 1590 1591 1591 1592 '@atcute/repo@0.1.0': 1592 1593 dependencies: 1593 1594 '@atcute/car': 5.0.0 1594 1595 '@atcute/cbor': 2.2.8 1595 1596 '@atcute/cid': 2.2.6 1596 - '@atcute/crypto': 2.2.6 1597 + '@atcute/crypto': 2.3.0 1597 1598 '@atcute/lexicons': 1.2.5 1598 1599 '@atcute/mst': 0.1.0 1599 - '@atcute/uint8array': 1.0.5 1600 + '@atcute/uint8array': 1.0.6 1600 1601 1601 - '@atcute/tangled@1.0.12': 1602 + '@atcute/tangled@1.0.13': 1602 1603 dependencies: 1603 1604 '@atcute/atproto': 3.1.9 1604 1605 '@atcute/lexicons': 1.2.5 1605 1606 1606 1607 '@atcute/tid@1.0.3': {} 1607 1608 1608 - '@atcute/uint8array@1.0.5': {} 1609 + '@atcute/uint8array@1.0.6': {} 1609 1610 1610 1611 '@atcute/util-fetch@1.0.4': 1611 1612 dependencies: ··· 1730 1731 dependencies: 1731 1732 '@codemirror/language': 6.11.3 1732 1733 '@codemirror/state': 6.5.2 1733 - '@codemirror/view': 6.38.8 1734 + '@codemirror/view': 6.39.4 1734 1735 '@lezer/common': 1.4.0 1735 1736 1736 1737 '@codemirror/commands@6.10.0': 1737 1738 dependencies: 1738 1739 '@codemirror/language': 6.11.3 1739 1740 '@codemirror/state': 6.5.2 1740 - '@codemirror/view': 6.38.8 1741 + '@codemirror/view': 6.39.4 1741 1742 '@lezer/common': 1.4.0 1742 1743 1743 1744 '@codemirror/lang-json@6.0.2': ··· 1748 1749 '@codemirror/language@6.11.3': 1749 1750 dependencies: 1750 1751 '@codemirror/state': 6.5.2 1751 - '@codemirror/view': 6.38.8 1752 + '@codemirror/view': 6.39.4 1752 1753 '@lezer/common': 1.4.0 1753 1754 '@lezer/highlight': 1.2.3 1754 - '@lezer/lr': 1.4.4 1755 + '@lezer/lr': 1.4.5 1755 1756 style-mod: 4.1.3 1756 1757 1757 1758 '@codemirror/lint@6.9.2': 1758 1759 dependencies: 1759 1760 '@codemirror/state': 6.5.2 1760 - '@codemirror/view': 6.38.8 1761 + '@codemirror/view': 6.39.4 1761 1762 crelt: 1.0.6 1762 1763 1763 1764 '@codemirror/search@6.5.11': 1764 1765 dependencies: 1765 1766 '@codemirror/state': 6.5.2 1766 - '@codemirror/view': 6.38.8 1767 + '@codemirror/view': 6.39.4 1767 1768 crelt: 1.0.6 1768 1769 1769 1770 '@codemirror/state@6.5.2': 1770 1771 dependencies: 1771 1772 '@marijn/find-cluster-break': 1.0.2 1772 1773 1773 - '@codemirror/view@6.38.8': 1774 + '@codemirror/view@6.39.4': 1774 1775 dependencies: 1775 1776 '@codemirror/state': 6.5.2 1776 1777 crelt: 1.0.6 ··· 1931 1932 '@esbuild/win32-x64@0.25.12': 1932 1933 optional: true 1933 1934 1934 - '@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3)': 1935 + '@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)': 1935 1936 dependencies: 1936 1937 '@codemirror/language': 6.11.3 1937 1938 '@codemirror/state': 6.5.2 1938 - '@codemirror/view': 6.38.8 1939 + '@codemirror/view': 6.39.4 1939 1940 '@lezer/highlight': 1.2.3 1940 1941 1941 - '@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.8)(@lezer/highlight@1.2.3)': 1942 + '@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.39.4)(@lezer/highlight@1.2.3)': 1942 1943 dependencies: 1943 1944 '@codemirror/language': 6.11.3 1944 1945 '@codemirror/state': 6.5.2 1945 - '@codemirror/view': 6.38.8 1946 + '@codemirror/view': 6.39.4 1946 1947 '@lezer/highlight': 1.2.3 1947 1948 1948 - '@iconify-json/lucide@1.2.77': 1949 + '@iconify-json/lucide@1.2.81': 1949 1950 dependencies: 1950 1951 '@iconify/types': 2.0.0 1951 1952 1952 - '@iconify/tailwind4@1.2.0(tailwindcss@4.1.17)': 1953 + '@iconify/tailwind4@1.2.0(tailwindcss@4.1.18)': 1953 1954 dependencies: 1954 1955 '@iconify/tools': 5.0.0 1955 1956 '@iconify/types': 2.0.0 1956 1957 '@iconify/utils': 3.1.0 1957 - tailwindcss: 4.1.17 1958 + tailwindcss: 4.1.18 1958 1959 1959 1960 '@iconify/tools@5.0.0': 1960 1961 dependencies: ··· 2005 2006 dependencies: 2006 2007 '@lezer/common': 1.4.0 2007 2008 '@lezer/highlight': 1.2.3 2008 - '@lezer/lr': 1.4.4 2009 + '@lezer/lr': 1.4.5 2009 2010 2010 - '@lezer/lr@1.4.4': 2011 + '@lezer/lr@1.4.5': 2011 2012 dependencies: 2012 2013 '@lezer/common': 1.4.0 2013 2014 ··· 2097 2098 2098 2099 '@standard-schema/spec@1.0.0': {} 2099 2100 2100 - '@tailwindcss/node@4.1.17': 2101 + '@tailwindcss/node@4.1.18': 2101 2102 dependencies: 2102 2103 '@jridgewell/remapping': 2.3.5 2103 - enhanced-resolve: 5.18.3 2104 + enhanced-resolve: 5.18.4 2104 2105 jiti: 2.6.1 2105 2106 lightningcss: 1.30.2 2106 2107 magic-string: 0.30.21 2107 2108 source-map-js: 1.2.1 2108 - tailwindcss: 4.1.17 2109 + tailwindcss: 4.1.18 2109 2110 2110 - '@tailwindcss/oxide-android-arm64@4.1.17': 2111 + '@tailwindcss/oxide-android-arm64@4.1.18': 2111 2112 optional: true 2112 2113 2113 - '@tailwindcss/oxide-darwin-arm64@4.1.17': 2114 + '@tailwindcss/oxide-darwin-arm64@4.1.18': 2114 2115 optional: true 2115 2116 2116 - '@tailwindcss/oxide-darwin-x64@4.1.17': 2117 + '@tailwindcss/oxide-darwin-x64@4.1.18': 2117 2118 optional: true 2118 2119 2119 - '@tailwindcss/oxide-freebsd-x64@4.1.17': 2120 + '@tailwindcss/oxide-freebsd-x64@4.1.18': 2120 2121 optional: true 2121 2122 2122 - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': 2123 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': 2123 2124 optional: true 2124 2125 2125 - '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': 2126 + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': 2126 2127 optional: true 2127 2128 2128 - '@tailwindcss/oxide-linux-arm64-musl@4.1.17': 2129 + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': 2129 2130 optional: true 2130 2131 2131 - '@tailwindcss/oxide-linux-x64-gnu@4.1.17': 2132 + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': 2132 2133 optional: true 2133 2134 2134 - '@tailwindcss/oxide-linux-x64-musl@4.1.17': 2135 + '@tailwindcss/oxide-linux-x64-musl@4.1.18': 2135 2136 optional: true 2136 2137 2137 - '@tailwindcss/oxide-wasm32-wasi@4.1.17': 2138 + '@tailwindcss/oxide-wasm32-wasi@4.1.18': 2138 2139 optional: true 2139 2140 2140 - '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': 2141 + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': 2141 2142 optional: true 2142 2143 2143 - '@tailwindcss/oxide-win32-x64-msvc@4.1.17': 2144 + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': 2144 2145 optional: true 2145 2146 2146 - '@tailwindcss/oxide@4.1.17': 2147 + '@tailwindcss/oxide@4.1.18': 2147 2148 optionalDependencies: 2148 - '@tailwindcss/oxide-android-arm64': 4.1.17 2149 - '@tailwindcss/oxide-darwin-arm64': 4.1.17 2150 - '@tailwindcss/oxide-darwin-x64': 4.1.17 2151 - '@tailwindcss/oxide-freebsd-x64': 4.1.17 2152 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 2153 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 2154 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 2155 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 2156 - '@tailwindcss/oxide-linux-x64-musl': 4.1.17 2157 - '@tailwindcss/oxide-wasm32-wasi': 4.1.17 2158 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 2159 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 2149 + '@tailwindcss/oxide-android-arm64': 4.1.18 2150 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 2151 + '@tailwindcss/oxide-darwin-x64': 4.1.18 2152 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 2153 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 2154 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 2155 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 2156 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 2157 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 2158 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 2159 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 2160 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 2160 2161 2161 - '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))': 2162 + '@tailwindcss/vite@4.1.18(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2))': 2162 2163 dependencies: 2163 - '@tailwindcss/node': 4.1.17 2164 - '@tailwindcss/oxide': 4.1.17 2165 - tailwindcss: 4.1.17 2166 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2164 + '@tailwindcss/node': 4.1.18 2165 + '@tailwindcss/oxide': 4.1.18 2166 + tailwindcss: 4.1.18 2167 + vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2167 2168 2168 2169 '@types/babel__core@7.20.5': 2169 2170 dependencies: ··· 2211 2212 optionalDependencies: 2212 2213 solid-js: 1.9.10 2213 2214 2214 - baseline-browser-mapping@2.9.0: {} 2215 + baseline-browser-mapping@2.9.7: {} 2215 2216 2216 2217 boolbase@1.0.0: {} 2217 2218 2218 2219 browserslist@4.28.1: 2219 2220 dependencies: 2220 - baseline-browser-mapping: 2.9.0 2221 - caniuse-lite: 1.0.30001759 2222 - electron-to-chromium: 1.5.263 2221 + baseline-browser-mapping: 2.9.7 2222 + caniuse-lite: 1.0.30001760 2223 + electron-to-chromium: 1.5.267 2223 2224 node-releases: 2.0.27 2224 - update-browserslist-db: 1.2.1(browserslist@4.28.1) 2225 + update-browserslist-db: 1.2.2(browserslist@4.28.1) 2225 2226 2226 - caniuse-lite@1.0.30001759: {} 2227 + caniuse-lite@1.0.30001760: {} 2227 2228 2228 2229 codemirror@6.0.2: 2229 2230 dependencies: ··· 2233 2234 '@codemirror/lint': 6.9.2 2234 2235 '@codemirror/search': 6.5.11 2235 2236 '@codemirror/state': 6.5.2 2236 - '@codemirror/view': 6.38.8 2237 + '@codemirror/view': 6.39.4 2237 2238 2238 2239 commander@11.1.0: {} 2239 2240 ··· 2293 2294 domelementtype: 2.3.0 2294 2295 domhandler: 5.0.3 2295 2296 2296 - electron-to-chromium@1.5.263: {} 2297 + electron-to-chromium@1.5.267: {} 2297 2298 2298 - enhanced-resolve@5.18.3: 2299 + enhanced-resolve@5.18.4: 2299 2300 dependencies: 2300 2301 graceful-fs: 4.2.11 2301 2302 tapable: 2.3.0 ··· 2590 2591 picocolors: 1.1.1 2591 2592 sax: 1.4.3 2592 2593 2593 - tailwindcss@4.1.17: {} 2594 + tailwindcss@4.1.18: {} 2594 2595 2595 2596 tapable@2.3.0: {} 2596 2597 ··· 2616 2617 undici-types@7.16.0: 2617 2618 optional: true 2618 2619 2619 - update-browserslist-db@1.2.1(browserslist@4.28.1): 2620 + update-browserslist-db@1.2.2(browserslist@4.28.1): 2620 2621 dependencies: 2621 2622 browserslist: 4.28.1 2622 2623 escalade: 3.2.0 2623 2624 picocolors: 1.1.1 2624 2625 2625 - vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2626 + vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2626 2627 dependencies: 2627 2628 '@babel/core': 7.28.5 2628 2629 '@types/babel__core': 7.20.5 ··· 2630 2631 merge-anything: 5.1.7 2631 2632 solid-js: 1.9.10 2632 2633 solid-refresh: 0.6.3(solid-js@1.9.10) 2633 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2634 - vitefu: 1.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 2634 + vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2635 + vitefu: 1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)) 2635 2636 transitivePeerDependencies: 2636 2637 - supports-color 2637 2638 2638 - vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2): 2639 + vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2): 2639 2640 dependencies: 2640 2641 esbuild: 0.25.12 2641 2642 fdir: 6.5.0(picomatch@4.0.3) ··· 2650 2651 lightningcss: 1.30.2 2651 2652 tsx: 4.19.2 2652 2653 2653 - vitefu@1.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2654 + vitefu@1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)): 2654 2655 optionalDependencies: 2655 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2656 + vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2) 2656 2657 2657 2658 w3c-keyname@2.2.8: {} 2658 2659
public/favicon.ico

This is a binary file and will not be displayed.

public/fonts/Figtree[wght].woff2

This is a binary file and will not be displayed.

+5 -1
src/auth/account.tsx
··· 41 41 return ( 42 42 <MenuProvider> 43 43 <DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-md p-2"> 44 - <NavMenu href={`/at://${props.did}`} label="Go to repo" icon="lucide--user-round" /> 44 + <NavMenu 45 + href={`/at://${props.did}`} 46 + label={agent()?.sub === props.did ? "Go to repo (g)" : "Go to repo"} 47 + icon="lucide--user-round" 48 + /> 45 49 <ActionMenu 46 50 icon="lucide--settings" 47 51 label="Edit permissions"
+2 -2
src/auth/session-manager.ts
··· 1 - import { Client, CredentialManager } from "@atcute/client"; 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 2 import { Did } from "@atcute/lexicons"; 3 3 import { 4 4 finalizeAuthorization, ··· 20 20 21 21 export const getAvatar = async (did: Did): Promise<string | undefined> => { 22 22 const rpc = new Client({ 23 - handler: new CredentialManager({ service: "https://public.api.bsky.app" }), 23 + handler: simpleFetchHandler({ service: "https://public.api.bsky.app" }), 24 24 }); 25 25 const res = await rpc.get("app.bsky.actor.getProfile", { params: { actor: did } }); 26 26 if (res.ok) {
+109
src/components/create/file-upload.tsx
··· 1 + import { Client } from "@atcute/client"; 2 + import { remove } from "@mary/exif-rm"; 3 + import { createSignal, onCleanup, Show } from "solid-js"; 4 + import { agent } from "../../auth/state"; 5 + import { Button } from "../button.jsx"; 6 + import { TextInput } from "../text-input.jsx"; 7 + import { editorInstance } from "./state"; 8 + 9 + export const FileUpload = (props: { 10 + file: File; 11 + blobInput: HTMLInputElement; 12 + onClose: () => void; 13 + }) => { 14 + const [uploading, setUploading] = createSignal(false); 15 + const [error, setError] = createSignal(""); 16 + 17 + onCleanup(() => (props.blobInput.value = "")); 18 + 19 + const formatFileSize = (bytes: number) => { 20 + if (bytes === 0) return "0 Bytes"; 21 + const k = 1024; 22 + const sizes = ["Bytes", "KB", "MB", "GB"]; 23 + const i = Math.floor(Math.log(bytes) / Math.log(k)); 24 + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]; 25 + }; 26 + 27 + const uploadBlob = async () => { 28 + let blob: Blob; 29 + 30 + const mimetype = (document.getElementById("mimetype") as HTMLInputElement)?.value; 31 + (document.getElementById("mimetype") as HTMLInputElement).value = ""; 32 + if (mimetype) blob = new Blob([props.file], { type: mimetype }); 33 + else blob = props.file; 34 + 35 + if ((document.getElementById("exif-rm") as HTMLInputElement).checked) { 36 + const exifRemoved = remove(new Uint8Array(await blob.arrayBuffer())); 37 + if (exifRemoved !== null) blob = new Blob([exifRemoved], { type: blob.type }); 38 + } 39 + 40 + const rpc = new Client({ handler: agent()! }); 41 + setUploading(true); 42 + const res = await rpc.post("com.atproto.repo.uploadBlob", { 43 + input: blob, 44 + }); 45 + setUploading(false); 46 + if (!res.ok) { 47 + setError(res.data.error); 48 + return; 49 + } 50 + editorInstance.view.dispatch({ 51 + changes: { 52 + from: editorInstance.view.state.selection.main.head, 53 + insert: JSON.stringify(res.data.blob, null, 2), 54 + }, 55 + }); 56 + props.onClose(); 57 + }; 58 + 59 + return ( 60 + <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 61 + <h2 class="mb-2 font-semibold">Upload blob</h2> 62 + <div class="flex flex-col gap-2 text-sm"> 63 + <div class="flex flex-col gap-1"> 64 + <p class="flex gap-1"> 65 + <span class="truncate">{props.file.name}</span> 66 + <span class="shrink-0 text-neutral-600 dark:text-neutral-400"> 67 + ({formatFileSize(props.file.size)}) 68 + </span> 69 + </p> 70 + </div> 71 + <div class="flex items-center gap-x-2"> 72 + <label for="mimetype" class="shrink-0 select-none"> 73 + MIME type 74 + </label> 75 + <TextInput id="mimetype" placeholder={props.file.type} /> 76 + </div> 77 + <div class="flex items-center gap-1"> 78 + <input id="exif-rm" type="checkbox" checked /> 79 + <label for="exif-rm" class="select-none"> 80 + Remove EXIF data 81 + </label> 82 + </div> 83 + <p class="text-xs text-neutral-600 dark:text-neutral-400"> 84 + Metadata will be pasted after the cursor 85 + </p> 86 + <Show when={error()}> 87 + <span class="text-red-500 dark:text-red-400">Error: {error()}</span> 88 + </Show> 89 + <div class="flex justify-between gap-2"> 90 + <Button onClick={props.onClose}>Cancel</Button> 91 + <Show when={uploading()}> 92 + <div class="flex items-center gap-1"> 93 + <span class="iconify lucide--loader-circle animate-spin"></span> 94 + <span>Uploading</span> 95 + </div> 96 + </Show> 97 + <Show when={!uploading()}> 98 + <Button 99 + onClick={uploadBlob} 100 + class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400" 101 + > 102 + Upload 103 + </Button> 104 + </Show> 105 + </div> 106 + </div> 107 + </div> 108 + ); 109 + };
+87
src/components/create/handle-input.tsx
··· 1 + import { Handle } from "@atcute/lexicons"; 2 + import { createSignal, Show } from "solid-js"; 3 + import { resolveHandle } from "../../utils/api"; 4 + import { Button } from "../button.jsx"; 5 + import { TextInput } from "../text-input.jsx"; 6 + import { editorInstance } from "./state"; 7 + 8 + export const HandleInput = (props: { onClose: () => void }) => { 9 + const [resolving, setResolving] = createSignal(false); 10 + const [error, setError] = createSignal(""); 11 + let handleFormRef!: HTMLFormElement; 12 + 13 + const resolveDid = async (e: SubmitEvent) => { 14 + e.preventDefault(); 15 + const formData = new FormData(handleFormRef); 16 + const handleValue = formData.get("handle")?.toString().trim(); 17 + 18 + if (!handleValue) { 19 + setError("Please enter a handle"); 20 + return; 21 + } 22 + 23 + setResolving(true); 24 + setError(""); 25 + try { 26 + const did = await resolveHandle(handleValue as Handle); 27 + editorInstance.view.dispatch({ 28 + changes: { 29 + from: editorInstance.view.state.selection.main.head, 30 + insert: `"${did}"`, 31 + }, 32 + }); 33 + props.onClose(); 34 + handleFormRef.reset(); 35 + } catch (err: any) { 36 + setError(err.message || "Failed to resolve handle"); 37 + } finally { 38 + setResolving(false); 39 + } 40 + }; 41 + 42 + return ( 43 + <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 44 + <h2 class="mb-2 font-semibold">Insert DID from handle</h2> 45 + <form ref={handleFormRef} onSubmit={resolveDid} class="flex flex-col gap-2 text-sm"> 46 + <div class="flex flex-col gap-1"> 47 + <label for="handle-input" class="select-none"> 48 + Handle 49 + </label> 50 + <TextInput id="handle-input" name="handle" placeholder="user.bsky.social" /> 51 + </div> 52 + <p class="text-xs text-neutral-600 dark:text-neutral-400"> 53 + DID will be pasted after the cursor 54 + </p> 55 + <Show when={error()}> 56 + <span class="text-red-500 dark:text-red-400">Error: {error()}</span> 57 + </Show> 58 + <div class="flex justify-between gap-2"> 59 + <Button 60 + type="button" 61 + onClick={() => { 62 + props.onClose(); 63 + handleFormRef.reset(); 64 + setError(""); 65 + }} 66 + > 67 + Cancel 68 + </Button> 69 + <Show when={resolving()}> 70 + <div class="flex items-center gap-1"> 71 + <span class="iconify lucide--loader-circle animate-spin"></span> 72 + <span>Resolving</span> 73 + </div> 74 + </Show> 75 + <Show when={!resolving()}> 76 + <Button 77 + type="submit" 78 + class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400" 79 + > 80 + Insert 81 + </Button> 82 + </Show> 83 + </div> 84 + </form> 85 + </div> 86 + ); 87 + };
+479
src/components/create/index.tsx
··· 1 + import { Client } from "@atcute/client"; 2 + import { Did } from "@atcute/lexicons"; 3 + import { isNsid, isRecordKey } from "@atcute/lexicons/syntax"; 4 + import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 5 + import { useNavigate, useParams } from "@solidjs/router"; 6 + import { 7 + createEffect, 8 + createSignal, 9 + For, 10 + lazy, 11 + onCleanup, 12 + onMount, 13 + Show, 14 + Suspense, 15 + } from "solid-js"; 16 + import { hasUserScope } from "../../auth/scope-utils"; 17 + import { agent, sessions } from "../../auth/state"; 18 + import { Button } from "../button.jsx"; 19 + import { Modal } from "../modal.jsx"; 20 + import { addNotification, removeNotification } from "../notification.jsx"; 21 + import { TextInput } from "../text-input.jsx"; 22 + import Tooltip from "../tooltip.jsx"; 23 + import { FileUpload } from "./file-upload"; 24 + import { HandleInput } from "./handle-input"; 25 + import { MenuItem } from "./menu-item"; 26 + import { editorInstance, placeholder, setPlaceholder } from "./state"; 27 + 28 + const Editor = lazy(() => import("../editor.jsx").then((m) => ({ default: m.Editor }))); 29 + 30 + export { editorInstance, placeholder, setPlaceholder }; 31 + 32 + export const RecordEditor = (props: { create: boolean; record?: any; refetch?: any }) => { 33 + const navigate = useNavigate(); 34 + const params = useParams(); 35 + const [openDialog, setOpenDialog] = createSignal(false); 36 + const [notice, setNotice] = createSignal(""); 37 + const [openUpload, setOpenUpload] = createSignal(false); 38 + const [openInsertMenu, setOpenInsertMenu] = createSignal(false); 39 + const [openHandleDialog, setOpenHandleDialog] = createSignal(false); 40 + const [validate, setValidate] = createSignal<boolean | undefined>(undefined); 41 + const [isMaximized, setIsMaximized] = createSignal(false); 42 + const [isMinimized, setIsMinimized] = createSignal(false); 43 + const [collectionError, setCollectionError] = createSignal(""); 44 + const [rkeyError, setRkeyError] = createSignal(""); 45 + let blobInput!: HTMLInputElement; 46 + let formRef!: HTMLFormElement; 47 + let insertMenuRef!: HTMLDivElement; 48 + 49 + createEffect(() => { 50 + if (openInsertMenu()) { 51 + const handleClickOutside = (e: MouseEvent) => { 52 + if (insertMenuRef && !insertMenuRef.contains(e.target as Node)) { 53 + setOpenInsertMenu(false); 54 + } 55 + }; 56 + document.addEventListener("mousedown", handleClickOutside); 57 + onCleanup(() => document.removeEventListener("mousedown", handleClickOutside)); 58 + } 59 + }); 60 + 61 + onMount(() => { 62 + const keyEvent = (ev: KeyboardEvent) => { 63 + if (ev.target instanceof HTMLInputElement || ev.target instanceof HTMLTextAreaElement) return; 64 + if ((ev.target as HTMLElement).closest("[data-modal]")) return; 65 + 66 + const key = props.create ? "n" : "e"; 67 + if (ev.key === key) { 68 + ev.preventDefault(); 69 + 70 + if (openDialog() && isMinimized()) { 71 + setIsMinimized(false); 72 + } else if (!openDialog() && !document.querySelector("[data-modal]")) { 73 + setOpenDialog(true); 74 + } 75 + } 76 + }; 77 + 78 + window.addEventListener("keydown", keyEvent); 79 + onCleanup(() => window.removeEventListener("keydown", keyEvent)); 80 + }); 81 + 82 + const defaultPlaceholder = () => { 83 + return { 84 + $type: "app.bsky.feed.post", 85 + text: "This post was sent from PDSls", 86 + embed: { 87 + $type: "app.bsky.embed.external", 88 + external: { 89 + uri: "https://pdsls.dev", 90 + title: "PDSls", 91 + description: "Browse the public data on atproto", 92 + }, 93 + }, 94 + langs: ["en"], 95 + createdAt: new Date().toISOString(), 96 + }; 97 + }; 98 + 99 + const getValidateIcon = () => { 100 + return ( 101 + validate() === true ? "lucide--circle-check" 102 + : validate() === false ? "lucide--circle-x" 103 + : "lucide--circle" 104 + ); 105 + }; 106 + 107 + const getValidateLabel = () => { 108 + return ( 109 + validate() === true ? "True" 110 + : validate() === false ? "False" 111 + : "Unset" 112 + ); 113 + }; 114 + 115 + createEffect(() => { 116 + if (openDialog()) { 117 + setValidate(undefined); 118 + setCollectionError(""); 119 + setRkeyError(""); 120 + } 121 + }); 122 + 123 + const createRecord = async (formData: FormData) => { 124 + const repo = formData.get("repo")?.toString(); 125 + if (!repo) return; 126 + const rpc = new Client({ handler: new OAuthUserAgent(await getSession(repo as Did)) }); 127 + const collection = formData.get("collection"); 128 + const rkey = formData.get("rkey"); 129 + let record: any; 130 + try { 131 + record = JSON.parse(editorInstance.view.state.doc.toString()); 132 + } catch (e: any) { 133 + setNotice(e.message); 134 + return; 135 + } 136 + const res = await rpc.post("com.atproto.repo.createRecord", { 137 + input: { 138 + repo: repo as Did, 139 + collection: collection ? collection.toString() : record.$type, 140 + rkey: rkey?.toString().length ? rkey?.toString() : undefined, 141 + record: record, 142 + validate: validate(), 143 + }, 144 + }); 145 + if (!res.ok) { 146 + setNotice(`${res.data.error}: ${res.data.message}`); 147 + return; 148 + } 149 + setOpenDialog(false); 150 + const id = addNotification({ 151 + message: "Record created", 152 + type: "success", 153 + }); 154 + setTimeout(() => removeNotification(id), 3000); 155 + navigate(`/${res.data.uri}`); 156 + }; 157 + 158 + const editRecord = async (recreate?: boolean) => { 159 + const record = editorInstance.view.state.doc.toString(); 160 + if (!record) return; 161 + const rpc = new Client({ handler: agent()! }); 162 + try { 163 + const editedRecord = JSON.parse(record); 164 + if (recreate) { 165 + const res = await rpc.post("com.atproto.repo.applyWrites", { 166 + input: { 167 + repo: agent()!.sub, 168 + validate: validate(), 169 + writes: [ 170 + { 171 + collection: params.collection as `${string}.${string}.${string}`, 172 + rkey: params.rkey!, 173 + $type: "com.atproto.repo.applyWrites#delete", 174 + }, 175 + { 176 + collection: params.collection as `${string}.${string}.${string}`, 177 + rkey: params.rkey, 178 + $type: "com.atproto.repo.applyWrites#create", 179 + value: editedRecord, 180 + }, 181 + ], 182 + }, 183 + }); 184 + if (!res.ok) { 185 + setNotice(`${res.data.error}: ${res.data.message}`); 186 + return; 187 + } 188 + } else { 189 + const res = await rpc.post("com.atproto.repo.applyWrites", { 190 + input: { 191 + repo: agent()!.sub, 192 + validate: validate(), 193 + writes: [ 194 + { 195 + collection: params.collection as `${string}.${string}.${string}`, 196 + rkey: params.rkey!, 197 + $type: "com.atproto.repo.applyWrites#update", 198 + value: editedRecord, 199 + }, 200 + ], 201 + }, 202 + }); 203 + if (!res.ok) { 204 + setNotice(`${res.data.error}: ${res.data.message}`); 205 + return; 206 + } 207 + } 208 + setOpenDialog(false); 209 + const id = addNotification({ 210 + message: "Record edited", 211 + type: "success", 212 + }); 213 + setTimeout(() => removeNotification(id), 3000); 214 + props.refetch(); 215 + } catch (err: any) { 216 + setNotice(err.message); 217 + } 218 + }; 219 + 220 + const insertTimestamp = () => { 221 + const timestamp = new Date().toISOString(); 222 + editorInstance.view.dispatch({ 223 + changes: { 224 + from: editorInstance.view.state.selection.main.head, 225 + insert: `"${timestamp}"`, 226 + }, 227 + }); 228 + setOpenInsertMenu(false); 229 + }; 230 + 231 + const insertDidFromHandle = () => { 232 + setOpenInsertMenu(false); 233 + setOpenHandleDialog(true); 234 + }; 235 + 236 + return ( 237 + <> 238 + <Modal 239 + open={openDialog()} 240 + onClose={() => setOpenDialog(false)} 241 + closeOnClick={false} 242 + nonBlocking={isMinimized()} 243 + > 244 + <div 245 + style="transform: translateX(-50%) translateZ(0);" 246 + classList={{ 247 + "dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto absolute top-18 left-1/2 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-all duration-200 dark:border-neutral-700 starting:opacity-0": true, 248 + "w-[calc(100%-1rem)] max-w-3xl h-[65vh]": !isMaximized(), 249 + "w-[calc(100%-1rem)] max-w-7xl h-[85vh]": isMaximized(), 250 + hidden: isMinimized(), 251 + }} 252 + > 253 + <div class="mb-2 flex w-full justify-between text-base"> 254 + <div class="flex items-center gap-2"> 255 + <span class="font-semibold select-none"> 256 + {props.create ? "Creating" : "Editing"} record 257 + </span> 258 + </div> 259 + <div class="flex items-center gap-1"> 260 + <button 261 + type="button" 262 + onclick={() => setIsMinimized(true)} 263 + class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 264 + > 265 + <span class="iconify lucide--minus"></span> 266 + </button> 267 + <button 268 + type="button" 269 + onclick={() => setIsMaximized(!isMaximized())} 270 + class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 271 + > 272 + <span 273 + class={`iconify ${isMaximized() ? "lucide--minimize-2" : "lucide--maximize-2"}`} 274 + ></span> 275 + </button> 276 + <button 277 + id="close" 278 + onclick={() => setOpenDialog(false)} 279 + class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 280 + > 281 + <span class="iconify lucide--x"></span> 282 + </button> 283 + </div> 284 + </div> 285 + <form ref={formRef} class="flex min-h-0 flex-1 flex-col gap-y-2"> 286 + <Show when={props.create}> 287 + <div class="flex flex-wrap items-center gap-1 text-sm"> 288 + <span>at://</span> 289 + <select 290 + class="dark:bg-dark-100 max-w-40 truncate rounded-lg border-[0.5px] border-neutral-300 bg-white px-1 py-1 select-none focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400" 291 + name="repo" 292 + id="repo" 293 + > 294 + <For each={Object.keys(sessions)}> 295 + {(session) => ( 296 + <option value={session} selected={session === agent()?.sub}> 297 + {sessions[session].handle ?? session} 298 + </option> 299 + )} 300 + </For> 301 + </select> 302 + <span>/</span> 303 + <TextInput 304 + id="collection" 305 + name="collection" 306 + placeholder="Collection (default: $type)" 307 + class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 308 + onInput={(e) => { 309 + const value = e.currentTarget.value; 310 + if (!value || isNsid(value)) setCollectionError(""); 311 + else 312 + setCollectionError( 313 + "Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)", 314 + ); 315 + }} 316 + /> 317 + <span>/</span> 318 + <TextInput 319 + id="rkey" 320 + name="rkey" 321 + placeholder="Record key (default: TID)" 322 + class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 323 + onInput={(e) => { 324 + const value = e.currentTarget.value; 325 + if (!value || isRecordKey(value)) setRkeyError(""); 326 + else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -"); 327 + }} 328 + /> 329 + </div> 330 + <Show when={collectionError() || rkeyError()}> 331 + <div class="text-xs text-red-500 dark:text-red-400"> 332 + <div>{collectionError()}</div> 333 + <div>{rkeyError()}</div> 334 + </div> 335 + </Show> 336 + </Show> 337 + <div class="min-h-0 flex-1"> 338 + <Suspense 339 + fallback={ 340 + <div class="flex h-full items-center justify-center"> 341 + <span class="iconify lucide--loader-circle animate-spin text-xl"></span> 342 + </div> 343 + } 344 + > 345 + <Editor 346 + content={JSON.stringify( 347 + !props.create ? props.record 348 + : params.rkey ? placeholder() 349 + : defaultPlaceholder(), 350 + null, 351 + 2, 352 + )} 353 + /> 354 + </Suspense> 355 + </div> 356 + <div class="flex flex-col gap-2"> 357 + <Show when={notice()}> 358 + <div class="text-sm text-red-500 dark:text-red-400">{notice()}</div> 359 + </Show> 360 + <div class="flex justify-between gap-2"> 361 + <div class="relative" ref={insertMenuRef}> 362 + <button 363 + type="button" 364 + class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 flex w-fit rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 text-base shadow-xs hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 365 + onClick={() => setOpenInsertMenu(!openInsertMenu())} 366 + > 367 + <span class="iconify lucide--plus select-none"></span> 368 + </button> 369 + <Show when={openInsertMenu()}> 370 + <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute bottom-full left-0 z-10 mb-1 flex w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 shadow-md dark:border-neutral-700"> 371 + <MenuItem 372 + icon="lucide--id-card" 373 + label="Insert DID" 374 + onClick={insertDidFromHandle} 375 + /> 376 + <MenuItem 377 + icon="lucide--clock" 378 + label="Insert timestamp" 379 + onClick={insertTimestamp} 380 + /> 381 + <Show when={hasUserScope("blob")}> 382 + <MenuItem 383 + icon="lucide--upload" 384 + label="Upload blob" 385 + onClick={() => { 386 + setOpenInsertMenu(false); 387 + blobInput.click(); 388 + }} 389 + /> 390 + </Show> 391 + </div> 392 + </Show> 393 + <input 394 + type="file" 395 + id="blob" 396 + class="sr-only" 397 + ref={blobInput} 398 + onChange={(e) => { 399 + if (e.target.files !== null) setOpenUpload(true); 400 + }} 401 + /> 402 + </div> 403 + <Modal 404 + open={openUpload()} 405 + onClose={() => setOpenUpload(false)} 406 + closeOnClick={false} 407 + > 408 + <FileUpload 409 + file={blobInput.files![0]} 410 + blobInput={blobInput} 411 + onClose={() => setOpenUpload(false)} 412 + /> 413 + </Modal> 414 + <Modal 415 + open={openHandleDialog()} 416 + onClose={() => setOpenHandleDialog(false)} 417 + closeOnClick={false} 418 + > 419 + <HandleInput onClose={() => setOpenHandleDialog(false)} /> 420 + </Modal> 421 + <div class="flex items-center justify-end gap-2"> 422 + <button 423 + type="button" 424 + class="flex items-center gap-1 rounded-sm p-1.5 text-xs hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 425 + onClick={() => 426 + setValidate( 427 + validate() === true ? false 428 + : validate() === false ? undefined 429 + : true, 430 + ) 431 + } 432 + > 433 + <Tooltip text={getValidateLabel()}> 434 + <span class={`iconify ${getValidateIcon()}`}></span> 435 + </Tooltip> 436 + <span>Validate</span> 437 + </button> 438 + <Show when={!props.create && hasUserScope("create") && hasUserScope("delete")}> 439 + <Button onClick={() => editRecord(true)}>Recreate</Button> 440 + </Show> 441 + <Button 442 + onClick={() => 443 + props.create ? createRecord(new FormData(formRef)) : editRecord() 444 + } 445 + > 446 + {props.create ? "Create" : "Edit"} 447 + </Button> 448 + </div> 449 + </div> 450 + </div> 451 + </form> 452 + </div> 453 + </Modal> 454 + <Show when={isMinimized() && openDialog()}> 455 + <button 456 + class="dark:bg-dark-300 dark:hover:bg-dark-200 dark:active:bg-dark-100 fixed right-4 bottom-4 z-30 flex items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-3 py-2 shadow-md hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 457 + onclick={() => setIsMinimized(false)} 458 + > 459 + <span class="iconify lucide--square-pen text-lg"></span> 460 + <span class="text-sm font-medium">{props.create ? "Creating" : "Editing"} record</span> 461 + </button> 462 + </Show> 463 + <Tooltip text={props.create ? "Create record (n)" : "Edit record (e)"}> 464 + <button 465 + class={`flex items-center p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 ${props.create ? "rounded-lg" : "rounded-sm"}`} 466 + onclick={() => { 467 + setNotice(""); 468 + setOpenDialog(true); 469 + setIsMinimized(false); 470 + }} 471 + > 472 + <div 473 + class={props.create ? "iconify lucide--square-pen text-lg" : "iconify lucide--pencil"} 474 + /> 475 + </button> 476 + </Tooltip> 477 + </> 478 + ); 479 + };
+12
src/components/create/menu-item.tsx
··· 1 + export const MenuItem = (props: { icon: string; label: string; onClick: () => void }) => { 2 + return ( 3 + <button 4 + type="button" 5 + class="flex items-center gap-2 rounded-md p-2 text-left text-xs hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 6 + onClick={props.onClick} 7 + > 8 + <span class={`iconify ${props.icon}`}></span> 9 + <span>{props.label}</span> 10 + </button> 11 + ); 12 + };
+4
src/components/create/state.ts
··· 1 + import { createSignal } from "solid-js"; 2 + 3 + export const editorInstance = { view: null as any }; 4 + export const [placeholder, setPlaceholder] = createSignal<any>();
-531
src/components/create.tsx
··· 1 - import { Client } from "@atcute/client"; 2 - import { Did } from "@atcute/lexicons"; 3 - import { isNsid, isRecordKey } from "@atcute/lexicons/syntax"; 4 - import { getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 5 - import { remove } from "@mary/exif-rm"; 6 - import { useNavigate, useParams } from "@solidjs/router"; 7 - import { createEffect, createSignal, For, lazy, onCleanup, Show, Suspense } from "solid-js"; 8 - import { hasUserScope } from "../auth/scope-utils"; 9 - import { agent, sessions } from "../auth/state"; 10 - import { Button } from "./button.jsx"; 11 - import { Modal } from "./modal.jsx"; 12 - import { addNotification, removeNotification } from "./notification.jsx"; 13 - import { TextInput } from "./text-input.jsx"; 14 - import Tooltip from "./tooltip.jsx"; 15 - 16 - const Editor = lazy(() => import("../components/editor.jsx").then((m) => ({ default: m.Editor }))); 17 - 18 - export const editorInstance = { view: null as any }; 19 - export const [placeholder, setPlaceholder] = createSignal<any>(); 20 - 21 - export const RecordEditor = (props: { create: boolean; record?: any; refetch?: any }) => { 22 - const navigate = useNavigate(); 23 - const params = useParams(); 24 - const [openDialog, setOpenDialog] = createSignal(false); 25 - const [notice, setNotice] = createSignal(""); 26 - const [openUpload, setOpenUpload] = createSignal(false); 27 - const [openInsertMenu, setOpenInsertMenu] = createSignal(false); 28 - const [validate, setValidate] = createSignal<boolean | undefined>(undefined); 29 - const [isMaximized, setIsMaximized] = createSignal(false); 30 - const [isMinimized, setIsMinimized] = createSignal(false); 31 - const [collectionError, setCollectionError] = createSignal(""); 32 - const [rkeyError, setRkeyError] = createSignal(""); 33 - let blobInput!: HTMLInputElement; 34 - let formRef!: HTMLFormElement; 35 - let insertMenuRef!: HTMLDivElement; 36 - 37 - createEffect(() => { 38 - if (openInsertMenu()) { 39 - const handleClickOutside = (e: MouseEvent) => { 40 - if (insertMenuRef && !insertMenuRef.contains(e.target as Node)) { 41 - setOpenInsertMenu(false); 42 - } 43 - }; 44 - document.addEventListener("mousedown", handleClickOutside); 45 - onCleanup(() => document.removeEventListener("mousedown", handleClickOutside)); 46 - } 47 - }); 48 - 49 - const defaultPlaceholder = () => { 50 - return { 51 - $type: "app.bsky.feed.post", 52 - text: "This post was sent from PDSls", 53 - embed: { 54 - $type: "app.bsky.embed.external", 55 - external: { 56 - uri: "https://pdsls.dev", 57 - title: "PDSls", 58 - description: "Browse the public data on atproto", 59 - }, 60 - }, 61 - langs: ["en"], 62 - createdAt: new Date().toISOString(), 63 - }; 64 - }; 65 - 66 - const getValidateIcon = () => { 67 - return ( 68 - validate() === true ? "lucide--circle-check" 69 - : validate() === false ? "lucide--circle-x" 70 - : "lucide--circle" 71 - ); 72 - }; 73 - 74 - const getValidateLabel = () => { 75 - return ( 76 - validate() === true ? "True" 77 - : validate() === false ? "False" 78 - : "Unset" 79 - ); 80 - }; 81 - 82 - createEffect(() => { 83 - if (openDialog()) { 84 - setValidate(undefined); 85 - setCollectionError(""); 86 - setRkeyError(""); 87 - } 88 - }); 89 - 90 - const createRecord = async (formData: FormData) => { 91 - const repo = formData.get("repo")?.toString(); 92 - if (!repo) return; 93 - const rpc = new Client({ handler: new OAuthUserAgent(await getSession(repo as Did)) }); 94 - const collection = formData.get("collection"); 95 - const rkey = formData.get("rkey"); 96 - let record: any; 97 - try { 98 - record = JSON.parse(editorInstance.view.state.doc.toString()); 99 - } catch (e: any) { 100 - setNotice(e.message); 101 - return; 102 - } 103 - const res = await rpc.post("com.atproto.repo.createRecord", { 104 - input: { 105 - repo: repo as Did, 106 - collection: collection ? collection.toString() : record.$type, 107 - rkey: rkey?.toString().length ? rkey?.toString() : undefined, 108 - record: record, 109 - validate: validate(), 110 - }, 111 - }); 112 - if (!res.ok) { 113 - setNotice(`${res.data.error}: ${res.data.message}`); 114 - return; 115 - } 116 - setOpenDialog(false); 117 - const id = addNotification({ 118 - message: "Record created", 119 - type: "success", 120 - }); 121 - setTimeout(() => removeNotification(id), 3000); 122 - navigate(`/${res.data.uri}`); 123 - }; 124 - 125 - const editRecord = async (recreate?: boolean) => { 126 - const record = editorInstance.view.state.doc.toString(); 127 - if (!record) return; 128 - const rpc = new Client({ handler: agent()! }); 129 - try { 130 - const editedRecord = JSON.parse(record); 131 - if (recreate) { 132 - const res = await rpc.post("com.atproto.repo.applyWrites", { 133 - input: { 134 - repo: agent()!.sub, 135 - validate: validate(), 136 - writes: [ 137 - { 138 - collection: params.collection as `${string}.${string}.${string}`, 139 - rkey: params.rkey!, 140 - $type: "com.atproto.repo.applyWrites#delete", 141 - }, 142 - { 143 - collection: params.collection as `${string}.${string}.${string}`, 144 - rkey: params.rkey, 145 - $type: "com.atproto.repo.applyWrites#create", 146 - value: editedRecord, 147 - }, 148 - ], 149 - }, 150 - }); 151 - if (!res.ok) { 152 - setNotice(`${res.data.error}: ${res.data.message}`); 153 - return; 154 - } 155 - } else { 156 - const res = await rpc.post("com.atproto.repo.putRecord", { 157 - input: { 158 - repo: agent()!.sub, 159 - collection: params.collection as `${string}.${string}.${string}`, 160 - rkey: params.rkey!, 161 - record: editedRecord, 162 - validate: validate(), 163 - }, 164 - }); 165 - if (!res.ok) { 166 - setNotice(`${res.data.error}: ${res.data.message}`); 167 - return; 168 - } 169 - } 170 - setOpenDialog(false); 171 - const id = addNotification({ 172 - message: "Record edited", 173 - type: "success", 174 - }); 175 - setTimeout(() => removeNotification(id), 3000); 176 - props.refetch(); 177 - } catch (err: any) { 178 - setNotice(err.message); 179 - } 180 - }; 181 - 182 - const insertTimestamp = () => { 183 - const timestamp = new Date().toISOString(); 184 - editorInstance.view.dispatch({ 185 - changes: { 186 - from: editorInstance.view.state.selection.main.head, 187 - insert: `"${timestamp}"`, 188 - }, 189 - }); 190 - setOpenInsertMenu(false); 191 - }; 192 - 193 - const MenuItem = (props: { icon: string; label: string; onClick: () => void }) => { 194 - return ( 195 - <button 196 - type="button" 197 - class="flex items-center gap-2 rounded-md p-2 text-left text-xs hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 198 - onClick={props.onClick} 199 - > 200 - <span class={`iconify ${props.icon}`}></span> 201 - <span>{props.label}</span> 202 - </button> 203 - ); 204 - }; 205 - 206 - const FileUpload = (props: { file: File }) => { 207 - const [uploading, setUploading] = createSignal(false); 208 - const [error, setError] = createSignal(""); 209 - 210 - onCleanup(() => (blobInput.value = "")); 211 - 212 - const formatFileSize = (bytes: number) => { 213 - if (bytes === 0) return "0 Bytes"; 214 - const k = 1024; 215 - const sizes = ["Bytes", "KB", "MB", "GB"]; 216 - const i = Math.floor(Math.log(bytes) / Math.log(k)); 217 - return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]; 218 - }; 219 - 220 - const uploadBlob = async () => { 221 - let blob: Blob; 222 - 223 - const mimetype = (document.getElementById("mimetype") as HTMLInputElement)?.value; 224 - (document.getElementById("mimetype") as HTMLInputElement).value = ""; 225 - if (mimetype) blob = new Blob([props.file], { type: mimetype }); 226 - else blob = props.file; 227 - 228 - if ((document.getElementById("exif-rm") as HTMLInputElement).checked) { 229 - const exifRemoved = remove(new Uint8Array(await blob.arrayBuffer())); 230 - if (exifRemoved !== null) blob = new Blob([exifRemoved], { type: blob.type }); 231 - } 232 - 233 - const rpc = new Client({ handler: agent()! }); 234 - setUploading(true); 235 - const res = await rpc.post("com.atproto.repo.uploadBlob", { 236 - input: blob, 237 - }); 238 - setUploading(false); 239 - if (!res.ok) { 240 - setError(res.data.error); 241 - return; 242 - } 243 - editorInstance.view.dispatch({ 244 - changes: { 245 - from: editorInstance.view.state.selection.main.head, 246 - insert: JSON.stringify(res.data.blob, null, 2), 247 - }, 248 - }); 249 - setOpenUpload(false); 250 - }; 251 - 252 - return ( 253 - <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute top-70 left-[50%] w-[20rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-opacity duration-200 dark:border-neutral-700 starting:opacity-0"> 254 - <h2 class="mb-2 font-semibold">Upload blob</h2> 255 - <div class="flex flex-col gap-2 text-sm"> 256 - <div class="flex flex-col gap-1"> 257 - <p class="flex gap-1"> 258 - <span class="truncate">{props.file.name}</span> 259 - <span class="shrink-0 text-neutral-600 dark:text-neutral-400"> 260 - ({formatFileSize(props.file.size)}) 261 - </span> 262 - </p> 263 - </div> 264 - <div class="flex items-center gap-x-2"> 265 - <label for="mimetype" class="shrink-0 select-none"> 266 - MIME type 267 - </label> 268 - <TextInput id="mimetype" placeholder={props.file.type} /> 269 - </div> 270 - <div class="flex items-center gap-1"> 271 - <input id="exif-rm" type="checkbox" checked /> 272 - <label for="exif-rm" class="select-none"> 273 - Remove EXIF data 274 - </label> 275 - </div> 276 - <p class="text-xs text-neutral-600 dark:text-neutral-400"> 277 - Metadata will be pasted after the cursor 278 - </p> 279 - <Show when={error()}> 280 - <span class="text-red-500 dark:text-red-400">Error: {error()}</span> 281 - </Show> 282 - <div class="flex justify-between gap-2"> 283 - <Button onClick={() => setOpenUpload(false)}>Cancel</Button> 284 - <Show when={uploading()}> 285 - <div class="flex items-center gap-1"> 286 - <span class="iconify lucide--loader-circle animate-spin"></span> 287 - <span>Uploading</span> 288 - </div> 289 - </Show> 290 - <Show when={!uploading()}> 291 - <Button 292 - onClick={uploadBlob} 293 - class="dark:shadow-dark-700 flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1.5 text-xs text-white shadow-xs select-none hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-500 dark:active:bg-blue-400" 294 - > 295 - Upload 296 - </Button> 297 - </Show> 298 - </div> 299 - </div> 300 - </div> 301 - ); 302 - }; 303 - 304 - return ( 305 - <> 306 - <Modal 307 - open={openDialog()} 308 - onClose={() => setOpenDialog(false)} 309 - closeOnClick={false} 310 - nonBlocking={isMinimized()} 311 - > 312 - <div 313 - style="transform: translateX(-50%) translateZ(0);" 314 - classList={{ 315 - "dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto absolute top-18 left-1/2 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-4 shadow-md transition-all duration-200 dark:border-neutral-700 starting:opacity-0": true, 316 - "w-[calc(100%-1rem)] max-w-3xl h-[65vh]": !isMaximized(), 317 - "w-[calc(100%-1rem)] max-w-7xl h-[85vh]": isMaximized(), 318 - hidden: isMinimized(), 319 - }} 320 - > 321 - <div class="mb-2 flex w-full justify-between text-base"> 322 - <div class="flex items-center gap-2"> 323 - <span class="font-semibold select-none"> 324 - {props.create ? "Creating" : "Editing"} record 325 - </span> 326 - </div> 327 - <div class="flex items-center gap-1"> 328 - <button 329 - type="button" 330 - onclick={() => setIsMinimized(true)} 331 - class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 332 - > 333 - <span class="iconify lucide--minus"></span> 334 - </button> 335 - <button 336 - type="button" 337 - onclick={() => setIsMaximized(!isMaximized())} 338 - class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 339 - > 340 - <span 341 - class={`iconify ${isMaximized() ? "lucide--minimize-2" : "lucide--maximize-2"}`} 342 - ></span> 343 - </button> 344 - <button 345 - id="close" 346 - onclick={() => setOpenDialog(false)} 347 - class="flex items-center rounded-lg p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 348 - > 349 - <span class="iconify lucide--x"></span> 350 - </button> 351 - </div> 352 - </div> 353 - <form ref={formRef} class="flex min-h-0 flex-1 flex-col gap-y-2"> 354 - <Show when={props.create}> 355 - <div class="flex flex-wrap items-center gap-1 text-sm"> 356 - <span>at://</span> 357 - <select 358 - class="dark:bg-dark-100 max-w-40 truncate rounded-lg border-[0.5px] border-neutral-300 bg-white px-1 py-1 select-none focus:outline-[1px] focus:outline-neutral-600 dark:border-neutral-600 dark:focus:outline-neutral-400" 359 - name="repo" 360 - id="repo" 361 - > 362 - <For each={Object.keys(sessions)}> 363 - {(session) => ( 364 - <option value={session} selected={session === agent()?.sub}> 365 - {sessions[session].handle ?? session} 366 - </option> 367 - )} 368 - </For> 369 - </select> 370 - <span>/</span> 371 - <TextInput 372 - id="collection" 373 - name="collection" 374 - placeholder="Collection (default: $type)" 375 - class={`w-40 placeholder:text-xs lg:w-52 ${collectionError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 376 - onInput={(e) => { 377 - const value = e.currentTarget.value; 378 - if (!value || isNsid(value)) setCollectionError(""); 379 - else 380 - setCollectionError( 381 - "Invalid collection: use reverse domain format (e.g. app.bsky.feed.post)", 382 - ); 383 - }} 384 - /> 385 - <span>/</span> 386 - <TextInput 387 - id="rkey" 388 - name="rkey" 389 - placeholder="Record key (default: TID)" 390 - class={`w-40 placeholder:text-xs lg:w-52 ${rkeyError() ? "border-red-500 focus:outline-red-500 dark:border-red-400 dark:focus:outline-red-400" : ""}`} 391 - onInput={(e) => { 392 - const value = e.currentTarget.value; 393 - if (!value || isRecordKey(value)) setRkeyError(""); 394 - else setRkeyError("Invalid record key: 1-512 chars, use a-z A-Z 0-9 . _ ~ : -"); 395 - }} 396 - /> 397 - </div> 398 - <Show when={collectionError() || rkeyError()}> 399 - <div class="text-xs text-red-500 dark:text-red-400"> 400 - <div>{collectionError()}</div> 401 - <div>{rkeyError()}</div> 402 - </div> 403 - </Show> 404 - </Show> 405 - <div class="min-h-0 flex-1"> 406 - <Suspense 407 - fallback={ 408 - <div class="flex h-full items-center justify-center"> 409 - <span class="iconify lucide--loader-circle animate-spin text-xl"></span> 410 - </div> 411 - } 412 - > 413 - <Editor 414 - content={JSON.stringify( 415 - !props.create ? props.record 416 - : params.rkey ? placeholder() 417 - : defaultPlaceholder(), 418 - null, 419 - 2, 420 - )} 421 - /> 422 - </Suspense> 423 - </div> 424 - <div class="flex flex-col gap-2"> 425 - <Show when={notice()}> 426 - <div class="text-sm text-red-500 dark:text-red-400">{notice()}</div> 427 - </Show> 428 - <div class="flex justify-between gap-2"> 429 - <div class="relative" ref={insertMenuRef}> 430 - <button 431 - type="button" 432 - class="dark:hover:bg-dark-200 dark:shadow-dark-700 dark:active:bg-dark-100 flex w-fit rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 text-base shadow-xs hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800" 433 - onClick={() => setOpenInsertMenu(!openInsertMenu())} 434 - > 435 - <span class="iconify lucide--plus select-none"></span> 436 - </button> 437 - <Show when={openInsertMenu()}> 438 - <div class="dark:bg-dark-300 dark:shadow-dark-700 absolute bottom-full left-0 z-10 mb-1 flex w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-1.5 shadow-md dark:border-neutral-700"> 439 - <Show when={hasUserScope("blob")}> 440 - <MenuItem 441 - icon="lucide--upload" 442 - label="Upload blob" 443 - onClick={() => { 444 - setOpenInsertMenu(false); 445 - blobInput.click(); 446 - }} 447 - /> 448 - </Show> 449 - <MenuItem 450 - icon="lucide--clock" 451 - label="Insert timestamp" 452 - onClick={insertTimestamp} 453 - /> 454 - </div> 455 - </Show> 456 - <input 457 - type="file" 458 - id="blob" 459 - class="sr-only" 460 - ref={blobInput} 461 - onChange={(e) => { 462 - if (e.target.files !== null) setOpenUpload(true); 463 - }} 464 - /> 465 - </div> 466 - <Modal 467 - open={openUpload()} 468 - onClose={() => setOpenUpload(false)} 469 - closeOnClick={false} 470 - > 471 - <FileUpload file={blobInput.files![0]} /> 472 - </Modal> 473 - <div class="flex items-center justify-end gap-2"> 474 - <button 475 - type="button" 476 - class="flex items-center gap-1 rounded-sm p-1.5 text-xs hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 477 - onClick={() => 478 - setValidate( 479 - validate() === true ? false 480 - : validate() === false ? undefined 481 - : true, 482 - ) 483 - } 484 - > 485 - <Tooltip text={getValidateLabel()}> 486 - <span class={`iconify ${getValidateIcon()}`}></span> 487 - </Tooltip> 488 - <span>Validate</span> 489 - </button> 490 - <Show when={!props.create}> 491 - <Button onClick={() => editRecord(true)}>Recreate</Button> 492 - </Show> 493 - <Button 494 - onClick={() => 495 - props.create ? createRecord(new FormData(formRef)) : editRecord() 496 - } 497 - > 498 - {props.create ? "Create" : "Edit"} 499 - </Button> 500 - </div> 501 - </div> 502 - </div> 503 - </form> 504 - </div> 505 - </Modal> 506 - <Show when={isMinimized() && openDialog()}> 507 - <button 508 - class="dark:bg-dark-300 dark:hover:bg-dark-200 dark:active:bg-dark-100 fixed right-4 bottom-4 z-30 flex items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 px-3 py-2 shadow-md hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700" 509 - onclick={() => setIsMinimized(false)} 510 - > 511 - <span class="iconify lucide--square-pen text-lg"></span> 512 - <span class="text-sm font-medium">{props.create ? "Creating" : "Editing"} record</span> 513 - </button> 514 - </Show> 515 - <Tooltip text={`${props.create ? "Create" : "Edit"} record`}> 516 - <button 517 - class={`flex items-center p-1.5 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 ${props.create ? "rounded-lg" : "rounded-sm"}`} 518 - onclick={() => { 519 - setNotice(""); 520 - setOpenDialog(true); 521 - setIsMinimized(false); 522 - }} 523 - > 524 - <div 525 - class={props.create ? "iconify lucide--square-pen text-lg" : "iconify lucide--pencil"} 526 - /> 527 - </button> 528 - </Tooltip> 529 - </> 530 - ); 531 - };
+1 -1
src/components/editor.tsx
··· 7 7 import { basicLight } from "@fsegurai/codemirror-theme-basic-light"; 8 8 import { basicSetup, EditorView } from "codemirror"; 9 9 import { onCleanup, onMount } from "solid-js"; 10 - import { editorInstance } from "./create"; 10 + import { editorInstance } from "./create/state"; 11 11 12 12 const Editor = (props: { content: string }) => { 13 13 let editorDiv!: HTMLDivElement;
+27 -7
src/components/search.tsx
··· 1 - import { Client, CredentialManager } from "@atcute/client"; 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 2 import { Nsid } from "@atcute/lexicons"; 3 - import { A, useLocation, useNavigate } from "@solidjs/router"; 4 - import { createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js"; 3 + import { A, useNavigate } from "@solidjs/router"; 4 + import { 5 + createEffect, 6 + createResource, 7 + createSignal, 8 + For, 9 + onCleanup, 10 + onMount, 11 + Show, 12 + } from "solid-js"; 5 13 import { isTouchDevice } from "../layout"; 6 14 import { resolveLexiconAuthority } from "../utils/api"; 7 15 import { appHandleLink, appList, appName, AppUrl } from "../utils/app-urls"; ··· 38 46 39 47 if ((ev.ctrlKey || ev.metaKey) && ev.key == "k") { 40 48 ev.preventDefault(); 41 - setShowSearch(!showSearch()); 49 + 50 + if (showSearch()) { 51 + const searchInput = document.querySelector("#input") as HTMLInputElement; 52 + if (searchInput && document.activeElement !== searchInput) { 53 + searchInput.focus(); 54 + } else { 55 + setShowSearch(false); 56 + } 57 + } else { 58 + setShowSearch(true); 59 + } 42 60 } else if (ev.key == "Escape") { 43 61 ev.preventDefault(); 44 62 setShowSearch(false); ··· 67 85 const navigate = useNavigate(); 68 86 let searchInput!: HTMLInputElement; 69 87 const rpc = new Client({ 70 - handler: new CredentialManager({ service: "https://public.api.bsky.app" }), 88 + handler: simpleFetchHandler({ service: "https://public.api.bsky.app" }), 71 89 }); 72 90 73 91 onMount(() => { 74 - if (useLocation().pathname !== "/") searchInput.focus(); 75 - 76 92 const handlePaste = (e: ClipboardEvent) => { 77 93 if (e.target === searchInput) return; 78 94 if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; ··· 83 99 84 100 window.addEventListener("paste", handlePaste); 85 101 onCleanup(() => window.removeEventListener("paste", handlePaste)); 102 + }); 103 + 104 + createEffect(() => { 105 + if (showSearch()) searchInput.focus(); 86 106 }); 87 107 88 108 const fetchTypeahead = async (input: string) => {
+17 -5
src/layout.tsx
··· 1 1 import { Handle } from "@atcute/lexicons"; 2 2 import { Meta, MetaProvider } from "@solidjs/meta"; 3 3 import { A, RouteSectionProps, useLocation, useNavigate } from "@solidjs/router"; 4 - import { createEffect, ErrorBoundary, onMount, Show, Suspense } from "solid-js"; 4 + import { createEffect, ErrorBoundary, onCleanup, onMount, Show, Suspense } from "solid-js"; 5 5 import { AccountManager } from "./auth/account.jsx"; 6 6 import { hasUserScope } from "./auth/scope-utils"; 7 - import { RecordEditor } from "./components/create.jsx"; 7 + import { agent } from "./auth/state.js"; 8 + import { RecordEditor } from "./components/create"; 8 9 import { DropdownMenu, MenuProvider, MenuSeparator, NavMenu } from "./components/dropdown.jsx"; 9 10 import { NavBar } from "./components/navbar.jsx"; 10 11 import { NotificationContainer } from "./components/notification.jsx"; ··· 44 45 45 46 onMount(() => { 46 47 window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", themeEvent); 48 + 49 + const handleGoToRepo = (ev: KeyboardEvent) => { 50 + if (document.querySelector("[data-modal]")) return; 51 + if (ev.target instanceof HTMLInputElement || ev.target instanceof HTMLTextAreaElement) return; 52 + 53 + if (ev.key === "g" && agent()?.sub) { 54 + ev.preventDefault(); 55 + navigate(`/at://${agent()!.sub}`); 56 + } 57 + }; 58 + 59 + window.addEventListener("keydown", handleGoToRepo); 60 + onCleanup(() => window.removeEventListener("keydown", handleGoToRepo)); 47 61 48 62 if (localStorage.getItem("sailor") === "true") { 49 63 const style = document.createElement("style"); ··· 128 142 <span>PDSls</span> 129 143 </A> 130 144 <div class="dark:bg-dark-300/60 relative flex items-center gap-0.5 rounded-lg bg-neutral-50/60 px-1 py-0.5"> 131 - <Show when={location.pathname !== "/"}> 132 - <SearchButton /> 133 - </Show> 145 + <SearchButton /> 134 146 <Show when={hasUserScope("create")}> 135 147 <RecordEditor create={true} /> 136 148 </Show>
+7 -1
src/styles/index.css
··· 6 6 7 7 @custom-variant dark (&:where(.dark, .dark *)); 8 8 9 + @font-face { 10 + font-family: "Figtree"; 11 + src: url("/fonts/Figtree[wght].woff2") format("woff2"); 12 + font-display: swap; 13 + } 14 + 9 15 @theme { 10 - --font-sans: "Inter", sans-serif; 16 + --font-sans: "Figtree", sans-serif; 11 17 --font-mono: "Roboto Mono", monospace; 12 18 --font-pecita: "Pecita", serif; 13 19
+2 -2
src/views/blob.tsx
··· 1 - import { Client, CredentialManager } from "@atcute/client"; 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 2 import { createResource, createSignal, For, Show } from "solid-js"; 3 3 import { Button } from "../components/button"; 4 4 ··· 9 9 let rpc: Client; 10 10 11 11 const fetchBlobs = async () => { 12 - if (!rpc) rpc = new Client({ handler: new CredentialManager({ service: props.pds }) }); 12 + if (!rpc) rpc = new Client({ handler: simpleFetchHandler({ service: props.pds }) }); 13 13 const res = await rpc.get("com.atproto.sync.listBlobs", { 14 14 params: { 15 15 did: props.repo as `did:${string}:${string}`,
+18 -16
src/views/collection.tsx
··· 1 1 import { ComAtprotoRepoApplyWrites, ComAtprotoRepoGetRecord } from "@atcute/atproto"; 2 - import { Client, CredentialManager } from "@atcute/client"; 2 + import { Client, simpleFetchHandler } from "@atcute/client"; 3 3 import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons"; 4 4 import * as TID from "@atcute/tid"; 5 5 import { A, useParams } from "@solidjs/router"; ··· 89 89 90 90 const fetchRecords = async () => { 91 91 if (!pds) pds = await resolvePDS(did!); 92 - if (!rpc) rpc = new Client({ handler: new CredentialManager({ service: pds }) }); 92 + if (!rpc) rpc = new Client({ handler: simpleFetchHandler({ service: pds }) }); 93 93 const res = await rpc.get("com.atproto.repo.listRecords", { 94 94 params: { 95 95 repo: did as ActorIdentifier, ··· 230 230 </button> 231 231 } 232 232 /> 233 - <Tooltip 234 - text="Recreate" 235 - children={ 236 - <button 237 - onclick={() => { 238 - setRecreate(true); 239 - setOpenDelete(true); 240 - }} 241 - class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 242 - > 243 - <span class="iconify lucide--recycle text-lg text-green-500 dark:text-green-400"></span> 244 - </button> 245 - } 246 - /> 233 + <Show when={hasUserScope("create")}> 234 + <Tooltip 235 + text="Recreate" 236 + children={ 237 + <button 238 + onclick={() => { 239 + setRecreate(true); 240 + setOpenDelete(true); 241 + }} 242 + class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600" 243 + > 244 + <span class="iconify lucide--recycle text-lg text-green-500 dark:text-green-400"></span> 245 + </button> 246 + } 247 + /> 248 + </Show> 247 249 <Tooltip 248 250 text="Delete" 249 251 children={
+2 -2
src/views/labels.tsx
··· 1 1 import { ComAtprotoLabelDefs } from "@atcute/atproto"; 2 - import { Client, CredentialManager } from "@atcute/client"; 2 + import { Client, simpleFetchHandler } from "@atcute/client"; 3 3 import { isAtprotoDid } from "@atcute/identity"; 4 4 import { Handle } from "@atcute/lexicons"; 5 5 import { A, useSearchParams } from "@solidjs/router"; ··· 158 158 await resolvePDS(did); 159 159 if (!labelerCache[did]) throw new Error("Repository is not a labeler"); 160 160 rpc = new Client({ 161 - handler: new CredentialManager({ service: labelerCache[did] }), 161 + handler: simpleFetchHandler({ service: labelerCache[did] }), 162 162 }); 163 163 164 164 setSearchParams({ did, uriPatterns });
+2 -2
src/views/pds.tsx
··· 1 1 import { ComAtprotoServerDescribeServer, ComAtprotoSyncListRepos } from "@atcute/atproto"; 2 - import { Client, CredentialManager } from "@atcute/client"; 2 + import { Client, simpleFetchHandler } from "@atcute/client"; 3 3 import { InferXRPCBodyOutput } from "@atcute/lexicons"; 4 4 import * as TID from "@atcute/tid"; 5 5 import { A, useLocation, useParams } from "@solidjs/router"; ··· 23 23 setPDS(params.pds); 24 24 const pds = 25 25 params.pds!.startsWith("localhost") ? `http://${params.pds}` : `https://${params.pds}`; 26 - const rpc = new Client({ handler: new CredentialManager({ service: pds }) }); 26 + const rpc = new Client({ handler: simpleFetchHandler({ service: pds }) }); 27 27 28 28 const getVersion = async () => { 29 29 // @ts-expect-error: undocumented endpoint
+4 -4
src/views/record.tsx
··· 1 - import { Client, CredentialManager } from "@atcute/client"; 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 2 import { DidDocument, getPdsEndpoint } from "@atcute/identity"; 3 3 import { lexiconDoc } from "@atcute/lexicon-doc"; 4 4 import { RecordValidator } from "@atcute/lexicon-doc/validations"; ··· 12 12 import { agent } from "../auth/state"; 13 13 import { Backlinks } from "../components/backlinks.jsx"; 14 14 import { Button } from "../components/button.jsx"; 15 - import { RecordEditor, setPlaceholder } from "../components/create.jsx"; 15 + import { RecordEditor, setPlaceholder } from "../components/create"; 16 16 import { 17 17 CopyMenu, 18 18 DropdownMenu, ··· 68 68 }); 69 69 } 70 70 71 - const rpc = new Client({ handler: new CredentialManager({ service: pdsEndpoint }) }); 71 + const rpc = new Client({ handler: simpleFetchHandler({ service: pdsEndpoint }) }); 72 72 const response = await rpc.get("com.atproto.repo.getRecord", { 73 73 params: { 74 74 repo: authority, ··· 208 208 setValidSchema(undefined); 209 209 setLexiconUri(undefined); 210 210 const pds = await resolvePDS(did!); 211 - rpc = new Client({ handler: new CredentialManager({ service: pds }) }); 211 + rpc = new Client({ handler: simpleFetchHandler({ service: pds }) }); 212 212 const res = await rpc.get("com.atproto.repo.getRecord", { 213 213 params: { 214 214 repo: did as ActorIdentifier,
+2 -2
src/views/repo.tsx
··· 1 - import { Client, CredentialManager } from "@atcute/client"; 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 2 import { DidDocument } from "@atcute/identity"; 3 3 import { ActorIdentifier, Did, Handle, Nsid } from "@atcute/lexicons"; 4 4 import { A, useLocation, useNavigate, useParams } from "@solidjs/router"; ··· 139 139 return {}; 140 140 } 141 141 142 - rpc = new Client({ handler: new CredentialManager({ service: pds }) }); 142 + rpc = new Client({ handler: simpleFetchHandler({ service: pds }) }); 143 143 try { 144 144 const res = await rpc.get("com.atproto.repo.describeRepo", { 145 145 params: { repo: did as ActorIdentifier },