forked from pdsls.dev/pdsls
this repo has no description

Compare changes

Choose any two refs to compare.

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