hmmm

Orual 94b6302a 23946661

+4464 -3873
+94 -148
Cargo.lock
··· 131 132 [[package]] 133 name = "anstyle-query" 134 - version = "1.1.4" 135 source = "registry+https://github.com/rust-lang/crates.io-index" 136 - checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 137 dependencies = [ 138 - "windows-sys 0.60.2", 139 ] 140 141 [[package]] 142 name = "anstyle-wincon" 143 - version = "3.0.10" 144 source = "registry+https://github.com/rust-lang/crates.io-index" 145 - checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 146 dependencies = [ 147 "anstyle", 148 "once_cell_polyfill", 149 - "windows-sys 0.60.2", 150 ] 151 152 [[package]] ··· 357 358 [[package]] 359 name = "axum" 360 - version = "0.8.6" 361 source = "registry+https://github.com/rust-lang/crates.io-index" 362 - checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" 363 dependencies = [ 364 "axum-core", 365 "axum-macros", ··· 710 711 [[package]] 712 name = "bytes" 713 - version = "1.10.1" 714 source = "registry+https://github.com/rust-lang/crates.io-index" 715 - checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 716 dependencies = [ 717 "serde", 718 ] ··· 763 764 [[package]] 765 name = "cc" 766 - version = "1.2.45" 767 source = "registry+https://github.com/rust-lang/crates.io-index" 768 - checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" 769 dependencies = [ 770 "find-msvc-tools", 771 "jobserver", ··· 894 895 [[package]] 896 name = "clap" 897 - version = "4.5.51" 898 source = "registry+https://github.com/rust-lang/crates.io-index" 899 - checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" 900 dependencies = [ 901 "clap_builder", 902 "clap_derive", ··· 904 905 [[package]] 906 name = "clap_builder" 907 - version = "4.5.51" 908 source = "registry+https://github.com/rust-lang/crates.io-index" 909 - checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" 910 dependencies = [ 911 "anstream", 912 "anstyle", ··· 986 dependencies = [ 987 "bytes", 988 "memchr", 989 - ] 990 - 991 - [[package]] 992 - name = "compact_string" 993 - version = "0.1.0" 994 - source = "registry+https://github.com/rust-lang/crates.io-index" 995 - checksum = "5255b88d8ea09573f588088dea17fbea682b4442abea6761a15d1da2c3a76c5c" 996 - dependencies = [ 997 - "serde", 998 - "thiserror 1.0.69", 999 ] 1000 1001 [[package]] ··· 1865 ] 1866 1867 [[package]] 1868 - name = "dioxus-free-icons" 1869 - version = "0.10.0" 1870 - source = "registry+https://github.com/rust-lang/crates.io-index" 1871 - checksum = "28d356e0f9edad0930bc1cc76744360c0ecca020cb943acaadf42cb774f28284" 1872 - dependencies = [ 1873 - "dioxus", 1874 - ] 1875 - 1876 - [[package]] 1877 name = "dioxus-fullstack" 1878 version = "0.7.1" 1879 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2109 [[package]] 2110 name = "dioxus-primitives" 2111 version = "0.0.1" 2112 - source = "git+https://github.com/DioxusLabs/components#98067ce2da493651b0c089db91e9903714c211f7" 2113 dependencies = [ 2114 "dioxus", 2115 "dioxus-time", ··· 2491 checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 2492 2493 [[package]] 2494 - name = "dynosaur" 2495 - version = "0.2.0" 2496 - source = "registry+https://github.com/rust-lang/crates.io-index" 2497 - checksum = "277b2cb52d2df4acece06bb16bc0bb0a006970c7bf504eac2d310927a6f65890" 2498 - dependencies = [ 2499 - "dynosaur_derive", 2500 - "trait-variant", 2501 - ] 2502 - 2503 - [[package]] 2504 - name = "dynosaur_derive" 2505 - version = "0.2.0" 2506 - source = "registry+https://github.com/rust-lang/crates.io-index" 2507 - checksum = "7a4102713839a8c01c77c165bc38ef2e83948f6397fa1e1dcfacec0f07b149d3" 2508 - dependencies = [ 2509 - "proc-macro2", 2510 - "quote", 2511 - "syn 2.0.110", 2512 - ] 2513 - 2514 - [[package]] 2515 name = "ecdsa" 2516 version = "0.16.9" 2517 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2777 2778 [[package]] 2779 name = "find-msvc-tools" 2780 - version = "0.1.4" 2781 source = "registry+https://github.com/rust-lang/crates.io-index" 2782 - checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 2783 2784 [[package]] 2785 name = "flate2" ··· 3463 "futures-core", 3464 "futures-sink", 3465 "http", 3466 - "indexmap 2.12.0", 3467 "slab", 3468 "tokio", 3469 "tokio-util", ··· 3520 3521 [[package]] 3522 name = "hashbrown" 3523 - version = "0.16.0" 3524 source = "registry+https://github.com/rust-lang/crates.io-index" 3525 - checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 3526 dependencies = [ 3527 "allocator-api2", 3528 "equivalent", ··· 3762 3763 [[package]] 3764 name = "hyper" 3765 - version = "1.8.0" 3766 source = "registry+https://github.com/rust-lang/crates.io-index" 3767 - checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" 3768 dependencies = [ 3769 "atomic-waker", 3770 "bytes", ··· 3801 ] 3802 3803 [[package]] 3804 - name = "hyper-tls" 3805 - version = "0.6.0" 3806 - source = "registry+https://github.com/rust-lang/crates.io-index" 3807 - checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 3808 - dependencies = [ 3809 - "bytes", 3810 - "http-body-util", 3811 - "hyper", 3812 - "hyper-util", 3813 - "native-tls", 3814 - "tokio", 3815 - "tokio-native-tls", 3816 - "tower-service", 3817 - ] 3818 - 3819 - [[package]] 3820 name = "hyper-util" 3821 - version = "0.1.17" 3822 source = "registry+https://github.com/rust-lang/crates.io-index" 3823 - checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" 3824 dependencies = [ 3825 "base64 0.22.1", 3826 "bytes", ··· 4003 4004 [[package]] 4005 name = "indexmap" 4006 - version = "2.12.0" 4007 source = "registry+https://github.com/rust-lang/crates.io-index" 4008 - checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" 4009 dependencies = [ 4010 "equivalent", 4011 - "hashbrown 0.16.0", 4012 "serde", 4013 "serde_core", 4014 ] ··· 4033 4034 [[package]] 4035 name = "insta" 4036 - version = "1.43.2" 4037 source = "registry+https://github.com/rust-lang/crates.io-index" 4038 - checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" 4039 dependencies = [ 4040 "console", 4041 "once_cell", ··· 4123 4124 [[package]] 4125 name = "jacquard" 4126 - version = "0.9.0" 4127 dependencies = [ 4128 "bytes", 4129 "getrandom 0.2.16", ··· 4137 "jose-jwk", 4138 "miette 7.6.0", 4139 "regex", 4140 "reqwest", 4141 "serde", 4142 "serde_html_form", ··· 4152 4153 [[package]] 4154 name = "jacquard-api" 4155 - version = "0.9.0" 4156 dependencies = [ 4157 "bon", 4158 "bytes", ··· 4169 4170 [[package]] 4171 name = "jacquard-axum" 4172 - version = "0.9.0" 4173 dependencies = [ 4174 "axum", 4175 "bytes", ··· 4190 4191 [[package]] 4192 name = "jacquard-common" 4193 - version = "0.9.0" 4194 dependencies = [ 4195 "base64 0.22.1", 4196 "bon", ··· 4213 "p256", 4214 "rand 0.9.2", 4215 "regex", 4216 "reqwest", 4217 "serde", 4218 "serde_html_form", ··· 4232 4233 [[package]] 4234 name = "jacquard-derive" 4235 - version = "0.9.0" 4236 dependencies = [ 4237 "heck 0.5.0", 4238 "jacquard-lexicon", ··· 4243 4244 [[package]] 4245 name = "jacquard-identity" 4246 - version = "0.9.1" 4247 dependencies = [ 4248 "bon", 4249 "bytes", ··· 4253 "jacquard-common", 4254 "jacquard-lexicon", 4255 "miette 7.6.0", 4256 - "mini-moka", 4257 "percent-encoding", 4258 "reqwest", 4259 "serde", ··· 4269 4270 [[package]] 4271 name = "jacquard-lexicon" 4272 - version = "0.9.1" 4273 dependencies = [ 4274 "cid", 4275 "dashmap", ··· 4294 4295 [[package]] 4296 name = "jacquard-oauth" 4297 - version = "0.9.0" 4298 dependencies = [ 4299 "base64 0.22.1", 4300 "bytes", ··· 4499 dependencies = [ 4500 "cssparser", 4501 "html5ever 0.29.1", 4502 - "indexmap 2.12.0", 4503 "selectors", 4504 ] 4505 ··· 4705 source = "registry+https://github.com/rust-lang/crates.io-index" 4706 checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" 4707 dependencies = [ 4708 - "hashbrown 0.16.0", 4709 ] 4710 4711 [[package]] ··· 5032 5033 [[package]] 5034 name = "mini-moka" 5035 version = "0.11.0" 5036 source = "git+https://github.com/moka-rs/mini-moka?rev=da864e849f5d034f32e02197fee9bb5d5af36d3d#da864e849f5d034f32e02197fee9bb5d5af36d3d" 5037 dependencies = [ ··· 5944 checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" 5945 dependencies = [ 5946 "base64 0.22.1", 5947 - "indexmap 2.12.0", 5948 "quick-xml 0.38.4", 5949 "serde", 5950 "time", ··· 6423 ] 6424 6425 [[package]] 6426 name = "regex-syntax" 6427 version = "0.8.8" 6428 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6448 "http-body-util", 6449 "hyper", 6450 "hyper-rustls", 6451 - "hyper-tls", 6452 "hyper-util", 6453 "js-sys", 6454 "log", 6455 "mime", 6456 "mime_guess", 6457 - "native-tls", 6458 "percent-encoding", 6459 "pin-project-lite", 6460 "quinn", ··· 6465 "serde_urlencoded", 6466 "sync_wrapper", 6467 "tokio", 6468 - "tokio-native-tls", 6469 "tokio-rustls", 6470 "tokio-util", 6471 "tower", ··· 6481 6482 [[package]] 6483 name = "resolv-conf" 6484 - version = "0.7.5" 6485 source = "registry+https://github.com/rust-lang/crates.io-index" 6486 - checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" 6487 6488 [[package]] 6489 name = "rfc6979" ··· 6559 6560 [[package]] 6561 name = "rsa" 6562 - version = "0.9.8" 6563 source = "registry+https://github.com/rust-lang/crates.io-index" 6564 - checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" 6565 dependencies = [ 6566 "const-oid", 6567 "digest", ··· 6924 checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" 6925 dependencies = [ 6926 "form_urlencoded", 6927 - "indexmap 2.12.0", 6928 "itoa", 6929 "ryu", 6930 "serde_core", ··· 6948 source = "registry+https://github.com/rust-lang/crates.io-index" 6949 checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 6950 dependencies = [ 6951 - "indexmap 2.12.0", 6952 "itoa", 6953 "memchr", 6954 "ryu", ··· 7021 7022 [[package]] 7023 name = "serde_with" 7024 - version = "3.15.1" 7025 source = "registry+https://github.com/rust-lang/crates.io-index" 7026 - checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" 7027 dependencies = [ 7028 "base64 0.22.1", 7029 "chrono", 7030 "hex", 7031 "indexmap 1.9.3", 7032 - "indexmap 2.12.0", 7033 "schemars 0.9.0", 7034 "schemars 1.1.0", 7035 "serde_core", ··· 7040 7041 [[package]] 7042 name = "serde_with_macros" 7043 - version = "3.15.1" 7044 source = "registry+https://github.com/rust-lang/crates.io-index" 7045 - checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" 7046 dependencies = [ 7047 "darling", 7048 "proc-macro2", ··· 8001 ] 8002 8003 [[package]] 8004 - name = "tokio-native-tls" 8005 - version = "0.3.1" 8006 - source = "registry+https://github.com/rust-lang/crates.io-index" 8007 - checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 8008 - dependencies = [ 8009 - "native-tls", 8010 - "tokio", 8011 - ] 8012 - 8013 - [[package]] 8014 name = "tokio-rustls" 8015 version = "0.26.4" 8016 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8112 source = "registry+https://github.com/rust-lang/crates.io-index" 8113 checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 8114 dependencies = [ 8115 - "indexmap 2.12.0", 8116 "serde", 8117 "serde_spanned 0.6.9", 8118 "toml_datetime 0.6.11", ··· 8156 source = "registry+https://github.com/rust-lang/crates.io-index" 8157 checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 8158 dependencies = [ 8159 - "indexmap 2.12.0", 8160 "toml_datetime 0.6.11", 8161 "winnow 0.5.40", 8162 ] ··· 8167 source = "registry+https://github.com/rust-lang/crates.io-index" 8168 checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" 8169 dependencies = [ 8170 - "indexmap 2.12.0", 8171 "toml_datetime 0.6.11", 8172 "winnow 0.5.40", 8173 ] ··· 8178 source = "registry+https://github.com/rust-lang/crates.io-index" 8179 checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 8180 dependencies = [ 8181 - "indexmap 2.12.0", 8182 "serde", 8183 "serde_spanned 0.6.9", 8184 "toml_datetime 0.6.11", ··· 8192 source = "registry+https://github.com/rust-lang/crates.io-index" 8193 checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" 8194 dependencies = [ 8195 - "indexmap 2.12.0", 8196 "toml_datetime 0.7.3", 8197 "toml_parser", 8198 "winnow 0.7.13", ··· 8945 "console_error_panic_hook", 8946 "dashmap", 8947 "dioxus", 8948 - "dioxus-free-icons", 8949 "dioxus-primitives", 8950 "dotenvy", 8951 "gloo-storage", ··· 8953 "humansize", 8954 "jacquard", 8955 "jacquard-axum", 8956 "jacquard-lexicon", 8957 "js-sys", 8958 "lol_alloc", 8959 "markdown-weaver", 8960 "mime-sniffer", 8961 - "mini-moka", 8962 "n0-future", 8963 "reqwest", 8964 "serde", 8965 "serde_html_form", 8966 "serde_json", 8967 "time", 8968 "tokio", ··· 8983 "clap", 8984 "dirs", 8985 "jacquard", 8986 - "jacquard-api", 8987 "kdl", 8988 "markdown-weaver", 8989 "markdown-weaver-escape", ··· 9010 "markdown-weaver-escape", 9011 "miette 7.6.0", 9012 "mime-sniffer", 9013 - "minijinja", 9014 "n0-future", 9015 - "owo-colors", 9016 "pin-project", 9017 "regex", 9018 "reqwest", 9019 "send_wrapper", 9020 "serde", 9021 - "serde_bytes", 9022 - "serde_html_form", 9023 - "serde_ipld_dagcbor", 9024 "serde_json", 9025 "thiserror 2.0.17", 9026 "tokio", ··· 9049 "futures-util", 9050 "hyper", 9051 "jacquard", 9052 - "jacquard-api", 9053 "jacquard-axum", 9054 "jose", 9055 "jose-jwk", ··· 9087 version = "0.1.0" 9088 dependencies = [ 9089 "bitflags 2.10.0", 9090 - "compact_string", 9091 "dashmap", 9092 - "dynosaur", 9093 "http", 9094 "ignore", 9095 "insta", ··· 9102 "pin-project", 9103 "pin-utils", 9104 "regex", 9105 "reqwest", 9106 "smol_str", 9107 "syntect", ··· 9395 9396 [[package]] 9397 name = "windows-registry" 9398 - version = "0.5.3" 9399 source = "registry+https://github.com/rust-lang/crates.io-index" 9400 - checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" 9401 dependencies = [ 9402 - "windows-link 0.1.3", 9403 - "windows-result 0.3.4", 9404 - "windows-strings 0.4.2", 9405 ] 9406 9407 [[package]] ··· 10006 10007 [[package]] 10008 name = "zerocopy" 10009 - version = "0.8.27" 10010 source = "registry+https://github.com/rust-lang/crates.io-index" 10011 - checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 10012 dependencies = [ 10013 "zerocopy-derive", 10014 ] 10015 10016 [[package]] 10017 name = "zerocopy-derive" 10018 - version = "0.8.27" 10019 source = "registry+https://github.com/rust-lang/crates.io-index" 10020 - checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 10021 dependencies = [ 10022 "proc-macro2", 10023 "quote",
··· 131 132 [[package]] 133 name = "anstyle-query" 134 + version = "1.1.5" 135 source = "registry+https://github.com/rust-lang/crates.io-index" 136 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 137 dependencies = [ 138 + "windows-sys 0.61.2", 139 ] 140 141 [[package]] 142 name = "anstyle-wincon" 143 + version = "3.0.11" 144 source = "registry+https://github.com/rust-lang/crates.io-index" 145 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 146 dependencies = [ 147 "anstyle", 148 "once_cell_polyfill", 149 + "windows-sys 0.61.2", 150 ] 151 152 [[package]] ··· 357 358 [[package]] 359 name = "axum" 360 + version = "0.8.7" 361 source = "registry+https://github.com/rust-lang/crates.io-index" 362 + checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" 363 dependencies = [ 364 "axum-core", 365 "axum-macros", ··· 710 711 [[package]] 712 name = "bytes" 713 + version = "1.11.0" 714 source = "registry+https://github.com/rust-lang/crates.io-index" 715 + checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 716 dependencies = [ 717 "serde", 718 ] ··· 763 764 [[package]] 765 name = "cc" 766 + version = "1.2.46" 767 source = "registry+https://github.com/rust-lang/crates.io-index" 768 + checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" 769 dependencies = [ 770 "find-msvc-tools", 771 "jobserver", ··· 894 895 [[package]] 896 name = "clap" 897 + version = "4.5.53" 898 source = "registry+https://github.com/rust-lang/crates.io-index" 899 + checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 900 dependencies = [ 901 "clap_builder", 902 "clap_derive", ··· 904 905 [[package]] 906 name = "clap_builder" 907 + version = "4.5.53" 908 source = "registry+https://github.com/rust-lang/crates.io-index" 909 + checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 910 dependencies = [ 911 "anstream", 912 "anstyle", ··· 986 dependencies = [ 987 "bytes", 988 "memchr", 989 ] 990 991 [[package]] ··· 1855 ] 1856 1857 [[package]] 1858 name = "dioxus-fullstack" 1859 version = "0.7.1" 1860 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2090 [[package]] 2091 name = "dioxus-primitives" 2092 version = "0.0.1" 2093 + source = "git+https://github.com/DioxusLabs/components#a15f329d1dc2bd76cd4030b27ecd70edb9fd8c6b" 2094 dependencies = [ 2095 "dioxus", 2096 "dioxus-time", ··· 2472 checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 2473 2474 [[package]] 2475 name = "ecdsa" 2476 version = "0.16.9" 2477 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2737 2738 [[package]] 2739 name = "find-msvc-tools" 2740 + version = "0.1.5" 2741 source = "registry+https://github.com/rust-lang/crates.io-index" 2742 + checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 2743 2744 [[package]] 2745 name = "flate2" ··· 3423 "futures-core", 3424 "futures-sink", 3425 "http", 3426 + "indexmap 2.12.1", 3427 "slab", 3428 "tokio", 3429 "tokio-util", ··· 3480 3481 [[package]] 3482 name = "hashbrown" 3483 + version = "0.16.1" 3484 source = "registry+https://github.com/rust-lang/crates.io-index" 3485 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 3486 dependencies = [ 3487 "allocator-api2", 3488 "equivalent", ··· 3722 3723 [[package]] 3724 name = "hyper" 3725 + version = "1.8.1" 3726 source = "registry+https://github.com/rust-lang/crates.io-index" 3727 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 3728 dependencies = [ 3729 "atomic-waker", 3730 "bytes", ··· 3761 ] 3762 3763 [[package]] 3764 name = "hyper-util" 3765 + version = "0.1.18" 3766 source = "registry+https://github.com/rust-lang/crates.io-index" 3767 + checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" 3768 dependencies = [ 3769 "base64 0.22.1", 3770 "bytes", ··· 3947 3948 [[package]] 3949 name = "indexmap" 3950 + version = "2.12.1" 3951 source = "registry+https://github.com/rust-lang/crates.io-index" 3952 + checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" 3953 dependencies = [ 3954 "equivalent", 3955 + "hashbrown 0.16.1", 3956 "serde", 3957 "serde_core", 3958 ] ··· 3977 3978 [[package]] 3979 name = "insta" 3980 + version = "1.44.1" 3981 source = "registry+https://github.com/rust-lang/crates.io-index" 3982 + checksum = "e8732d3774162a0851e3f2b150eb98f31a9885dd75985099421d393385a01dfd" 3983 dependencies = [ 3984 "console", 3985 "once_cell", ··· 4067 4068 [[package]] 4069 name = "jacquard" 4070 + version = "0.9.4" 4071 dependencies = [ 4072 "bytes", 4073 "getrandom 0.2.16", ··· 4081 "jose-jwk", 4082 "miette 7.6.0", 4083 "regex", 4084 + "regex-lite", 4085 "reqwest", 4086 "serde", 4087 "serde_html_form", ··· 4097 4098 [[package]] 4099 name = "jacquard-api" 4100 + version = "0.9.2" 4101 dependencies = [ 4102 "bon", 4103 "bytes", ··· 4114 4115 [[package]] 4116 name = "jacquard-axum" 4117 + version = "0.9.2" 4118 dependencies = [ 4119 "axum", 4120 "bytes", ··· 4135 4136 [[package]] 4137 name = "jacquard-common" 4138 + version = "0.9.2" 4139 dependencies = [ 4140 "base64 0.22.1", 4141 "bon", ··· 4158 "p256", 4159 "rand 0.9.2", 4160 "regex", 4161 + "regex-lite", 4162 "reqwest", 4163 "serde", 4164 "serde_html_form", ··· 4178 4179 [[package]] 4180 name = "jacquard-derive" 4181 + version = "0.9.4" 4182 dependencies = [ 4183 "heck 0.5.0", 4184 "jacquard-lexicon", ··· 4189 4190 [[package]] 4191 name = "jacquard-identity" 4192 + version = "0.9.2" 4193 dependencies = [ 4194 "bon", 4195 "bytes", ··· 4199 "jacquard-common", 4200 "jacquard-lexicon", 4201 "miette 7.6.0", 4202 + "mini-moka 0.10.99", 4203 "percent-encoding", 4204 "reqwest", 4205 "serde", ··· 4215 4216 [[package]] 4217 name = "jacquard-lexicon" 4218 + version = "0.9.2" 4219 dependencies = [ 4220 "cid", 4221 "dashmap", ··· 4240 4241 [[package]] 4242 name = "jacquard-oauth" 4243 + version = "0.9.2" 4244 dependencies = [ 4245 "base64 0.22.1", 4246 "bytes", ··· 4445 dependencies = [ 4446 "cssparser", 4447 "html5ever 0.29.1", 4448 + "indexmap 2.12.1", 4449 "selectors", 4450 ] 4451 ··· 4651 source = "registry+https://github.com/rust-lang/crates.io-index" 4652 checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" 4653 dependencies = [ 4654 + "hashbrown 0.16.1", 4655 ] 4656 4657 [[package]] ··· 4978 4979 [[package]] 4980 name = "mini-moka" 4981 + version = "0.10.99" 4982 + dependencies = [ 4983 + "crossbeam-channel", 4984 + "crossbeam-utils", 4985 + "dashmap", 4986 + "smallvec", 4987 + "tagptr", 4988 + "triomphe", 4989 + "web-time", 4990 + ] 4991 + 4992 + [[package]] 4993 + name = "mini-moka" 4994 version = "0.11.0" 4995 source = "git+https://github.com/moka-rs/mini-moka?rev=da864e849f5d034f32e02197fee9bb5d5af36d3d#da864e849f5d034f32e02197fee9bb5d5af36d3d" 4996 dependencies = [ ··· 5903 checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" 5904 dependencies = [ 5905 "base64 0.22.1", 5906 + "indexmap 2.12.1", 5907 "quick-xml 0.38.4", 5908 "serde", 5909 "time", ··· 6382 ] 6383 6384 [[package]] 6385 + name = "regex-lite" 6386 + version = "0.1.8" 6387 + source = "registry+https://github.com/rust-lang/crates.io-index" 6388 + checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 6389 + 6390 + [[package]] 6391 name = "regex-syntax" 6392 version = "0.8.8" 6393 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6413 "http-body-util", 6414 "hyper", 6415 "hyper-rustls", 6416 "hyper-util", 6417 "js-sys", 6418 "log", 6419 "mime", 6420 "mime_guess", 6421 "percent-encoding", 6422 "pin-project-lite", 6423 "quinn", ··· 6428 "serde_urlencoded", 6429 "sync_wrapper", 6430 "tokio", 6431 "tokio-rustls", 6432 "tokio-util", 6433 "tower", ··· 6443 6444 [[package]] 6445 name = "resolv-conf" 6446 + version = "0.7.6" 6447 source = "registry+https://github.com/rust-lang/crates.io-index" 6448 + checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 6449 6450 [[package]] 6451 name = "rfc6979" ··· 6521 6522 [[package]] 6523 name = "rsa" 6524 + version = "0.9.9" 6525 source = "registry+https://github.com/rust-lang/crates.io-index" 6526 + checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" 6527 dependencies = [ 6528 "const-oid", 6529 "digest", ··· 6886 checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" 6887 dependencies = [ 6888 "form_urlencoded", 6889 + "indexmap 2.12.1", 6890 "itoa", 6891 "ryu", 6892 "serde_core", ··· 6910 source = "registry+https://github.com/rust-lang/crates.io-index" 6911 checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 6912 dependencies = [ 6913 + "indexmap 2.12.1", 6914 "itoa", 6915 "memchr", 6916 "ryu", ··· 6983 6984 [[package]] 6985 name = "serde_with" 6986 + version = "3.16.0" 6987 source = "registry+https://github.com/rust-lang/crates.io-index" 6988 + checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" 6989 dependencies = [ 6990 "base64 0.22.1", 6991 "chrono", 6992 "hex", 6993 "indexmap 1.9.3", 6994 + "indexmap 2.12.1", 6995 "schemars 0.9.0", 6996 "schemars 1.1.0", 6997 "serde_core", ··· 7002 7003 [[package]] 7004 name = "serde_with_macros" 7005 + version = "3.16.0" 7006 source = "registry+https://github.com/rust-lang/crates.io-index" 7007 + checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" 7008 dependencies = [ 7009 "darling", 7010 "proc-macro2", ··· 7963 ] 7964 7965 [[package]] 7966 name = "tokio-rustls" 7967 version = "0.26.4" 7968 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 8064 source = "registry+https://github.com/rust-lang/crates.io-index" 8065 checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 8066 dependencies = [ 8067 + "indexmap 2.12.1", 8068 "serde", 8069 "serde_spanned 0.6.9", 8070 "toml_datetime 0.6.11", ··· 8108 source = "registry+https://github.com/rust-lang/crates.io-index" 8109 checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 8110 dependencies = [ 8111 + "indexmap 2.12.1", 8112 "toml_datetime 0.6.11", 8113 "winnow 0.5.40", 8114 ] ··· 8119 source = "registry+https://github.com/rust-lang/crates.io-index" 8120 checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" 8121 dependencies = [ 8122 + "indexmap 2.12.1", 8123 "toml_datetime 0.6.11", 8124 "winnow 0.5.40", 8125 ] ··· 8130 source = "registry+https://github.com/rust-lang/crates.io-index" 8131 checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 8132 dependencies = [ 8133 + "indexmap 2.12.1", 8134 "serde", 8135 "serde_spanned 0.6.9", 8136 "toml_datetime 0.6.11", ··· 8144 source = "registry+https://github.com/rust-lang/crates.io-index" 8145 checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" 8146 dependencies = [ 8147 + "indexmap 2.12.1", 8148 "toml_datetime 0.7.3", 8149 "toml_parser", 8150 "winnow 0.7.13", ··· 8897 "console_error_panic_hook", 8898 "dashmap", 8899 "dioxus", 8900 "dioxus-primitives", 8901 "dotenvy", 8902 "gloo-storage", ··· 8904 "humansize", 8905 "jacquard", 8906 "jacquard-axum", 8907 + "jacquard-identity", 8908 "jacquard-lexicon", 8909 "js-sys", 8910 "lol_alloc", 8911 "markdown-weaver", 8912 "mime-sniffer", 8913 + "mini-moka 0.11.0", 8914 "n0-future", 8915 "reqwest", 8916 "serde", 8917 "serde_html_form", 8918 + "serde_ipld_dagcbor", 8919 "serde_json", 8920 "time", 8921 "tokio", ··· 8936 "clap", 8937 "dirs", 8938 "jacquard", 8939 "kdl", 8940 "markdown-weaver", 8941 "markdown-weaver-escape", ··· 8962 "markdown-weaver-escape", 8963 "miette 7.6.0", 8964 "mime-sniffer", 8965 "n0-future", 8966 "pin-project", 8967 "regex", 8968 + "regex-lite", 8969 "reqwest", 8970 "send_wrapper", 8971 "serde", 8972 "serde_json", 8973 "thiserror 2.0.17", 8974 "tokio", ··· 8997 "futures-util", 8998 "hyper", 8999 "jacquard", 9000 "jacquard-axum", 9001 "jose", 9002 "jose-jwk", ··· 9034 version = "0.1.0" 9035 dependencies = [ 9036 "bitflags 2.10.0", 9037 "dashmap", 9038 "http", 9039 "ignore", 9040 "insta", ··· 9047 "pin-project", 9048 "pin-utils", 9049 "regex", 9050 + "regex-lite", 9051 "reqwest", 9052 "smol_str", 9053 "syntect", ··· 9341 9342 [[package]] 9343 name = "windows-registry" 9344 + version = "0.6.1" 9345 source = "registry+https://github.com/rust-lang/crates.io-index" 9346 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 9347 dependencies = [ 9348 + "windows-link 0.2.1", 9349 + "windows-result 0.4.1", 9350 + "windows-strings 0.5.1", 9351 ] 9352 9353 [[package]] ··· 9952 9953 [[package]] 9954 name = "zerocopy" 9955 + version = "0.8.28" 9956 source = "registry+https://github.com/rust-lang/crates.io-index" 9957 + checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" 9958 dependencies = [ 9959 "zerocopy-derive", 9960 ] 9961 9962 [[package]] 9963 name = "zerocopy-derive" 9964 + version = "0.8.28" 9965 source = "registry+https://github.com/rust-lang/crates.io-index" 9966 + checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" 9967 dependencies = [ 9968 "proc-macro2", 9969 "quote",
+11 -11
Cargo.toml
··· 21 22 23 [workspace.dependencies] 24 - 25 serde = { version = "1.0", features = ["derive"] } 26 bytes = "1.10" 27 - minijinja = { version = "2.9.0", default-features = false } 28 - minijinja-contrib = { version = "2.9.0", default-features = false } 29 miette = { version = "7.6" } 30 - owo-colors = { version = "4.2.0" } 31 thiserror = "2.0" 32 syntect = { version = "5.2.0", default-features = false } 33 - jane-eyre = "0.6.12" 34 n0-future = "=0.1.3" 35 tracing = { version = "0.1.41", default-features = false, features = ["std"] } 36 - lexicon_cid = { package = "cid", version = "0.10.1", features = [ 37 - "serde-codec", 38 - ] } 39 - 40 markdown-weaver = { git = "https://github.com/rsform/markdown-weaver" } 41 markdown-weaver-escape = { git = "https://github.com/rsform/markdown-weaver" } 42 43 # jacquard = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 44 - # jacquard-api = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 45 # jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 46 # jacquard-axum = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 47 # jacquard-derive = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 48 # jacquard-lexicon = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false } 49 50 - jacquard = { path = "../jacquard/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 51 jacquard-api = { path = "../jacquard/crates/jacquard-api" } 52 jacquard-common = { path = "../jacquard/crates/jacquard-common" } 53 jacquard-axum = {path = "../jacquard/crates/jacquard-axum" } 54 jacquard-derive = { path = "../jacquard/crates/jacquard-derive" } 55 jacquard-lexicon = { path = "../jacquard/crates/jacquard-lexicon", default-features = false } 56 57 [profile] 58
··· 21 22 23 [workspace.dependencies] 24 serde = { version = "1.0", features = ["derive"] } 25 bytes = "1.10" 26 miette = { version = "7.6" } 27 thiserror = "2.0" 28 syntect = { version = "5.2.0", default-features = false } 29 n0-future = "=0.1.3" 30 tracing = { version = "0.1.41", default-features = false, features = ["std"] } 31 markdown-weaver = { git = "https://github.com/rsform/markdown-weaver" } 32 markdown-weaver-escape = { git = "https://github.com/rsform/markdown-weaver" } 33 34 # jacquard = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 35 + # jacquard-identity = { git = "https://tangled.org/@nonbinary.computer/jacquard", features = ["cache"] } 36 # jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 37 # jacquard-axum = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 38 # jacquard-derive = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 39 # jacquard-lexicon = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false } 40 41 + jacquard = { path = "../jacquard/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing", "cache"] } 42 + jacquard-identity = { path = "../jacquard/crates/jacquard-identity", features = ["cache"] } 43 jacquard-api = { path = "../jacquard/crates/jacquard-api" } 44 jacquard-common = { path = "../jacquard/crates/jacquard-common" } 45 jacquard-axum = {path = "../jacquard/crates/jacquard-axum" } 46 jacquard-derive = { path = "../jacquard/crates/jacquard-derive" } 47 jacquard-lexicon = { path = "../jacquard/crates/jacquard-lexicon", default-features = false } 48 + 49 + # jacquard = { path = "../jacquard-facet/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing", "serde"] } 50 + # jacquard-identity = { path = "../jacquard-facet/crates/jacquard-identity", features = ["cache"] } 51 + # jacquard-api = { path = "../jacquard-facet/crates/jacquard-api" } 52 + # jacquard-common = { path = "../jacquard-facet/crates/jacquard-common" } 53 + # jacquard-axum = {path = "../jacquard-facet/crates/jacquard-axum" } 54 + # jacquard-derive = { path = "../jacquard-facet/crates/jacquard-derive" } 55 + # jacquard-lexicon = { path = "../jacquard-facet/crates/jacquard-lexicon", default-features = false } 56 57 [profile] 58
+5 -3
crates/weaver-api/Cargo.toml
··· 6 authors.workspace = true 7 8 [dependencies] 9 - bytes = { workspace = true, features = ["serde"] } 10 jacquard-common = { workspace = true } 11 jacquard-derive = { workspace = true } 12 jacquard-lexicon = { workspace = true } 13 miette.workspace = true 14 rustversion = "1.0" 15 serde.workspace = true 16 - serde_ipld_dagcbor = "0.6" 17 thiserror.workspace = true 18 unicode-segmentation = "1.12" 19 ··· 23 non_snake_case = "allow" 24 25 [features] 26 - default = ["sh_weaver", "com_atproto"] 27 streaming = ["jacquard-common/websocket"] 28 29 # --- generated --- 30 # Generated namespace features
··· 6 authors.workspace = true 7 8 [dependencies] 9 + bytes = { workspace = true } 10 jacquard-common = { workspace = true } 11 jacquard-derive = { workspace = true } 12 jacquard-lexicon = { workspace = true } 13 miette.workspace = true 14 rustversion = "1.0" 15 serde.workspace = true 16 + serde_ipld_dagcbor = { version = "0.6", optional = true } 17 thiserror.workspace = true 18 unicode-segmentation = "1.12" 19 ··· 23 non_snake_case = "allow" 24 25 [features] 26 + default = ["sh_weaver", "com_atproto", "serde"] 27 streaming = ["jacquard-common/websocket"] 28 + serde = ["bytes/serde", "dep:serde_ipld_dagcbor"] 29 + 30 31 # --- generated --- 32 # Generated namespace features
+15
crates/weaver-api/lexicons/tools_ozone_moderation_defs.json
··· 326 "type": "string", 327 "description": "The content of the email sent to the user." 328 }, 329 "policies": { 330 "type": "array", 331 "description": "Names/Keywords of the policies that necessitated the email.", ··· 555 "type": "string", 556 "description": "When the strike should expire. If not provided, the strike never expires.", 557 "format": "datetime" 558 } 559 } 560 },
··· 326 "type": "string", 327 "description": "The content of the email sent to the user." 328 }, 329 + "isDelivered": { 330 + "type": "boolean", 331 + "description": "Indicates whether the email was successfully delivered to the user's inbox." 332 + }, 333 "policies": { 334 "type": "array", 335 "description": "Names/Keywords of the policies that necessitated the email.", ··· 559 "type": "string", 560 "description": "When the strike should expire. If not provided, the strike never expires.", 561 "format": "datetime" 562 + }, 563 + "targetServices": { 564 + "type": "array", 565 + "description": "List of services where the takedown should be applied. If empty or not provided, takedown is applied on all configured services.", 566 + "items": { 567 + "type": "string", 568 + "knownValues": [ 569 + "appview", 570 + "pds" 571 + ] 572 + } 573 } 574 } 575 },
+7 -1
crates/weaver-api/lexicons/tools_ozone_moderation_queryStatuses.json
··· 149 }, 150 "reviewState": { 151 "type": "string", 152 - "description": "Specify when fetching subjects in a certain state" 153 }, 154 "reviewedAfter": { 155 "type": "string",
··· 149 }, 150 "reviewState": { 151 "type": "string", 152 + "description": "Specify when fetching subjects in a certain state", 153 + "knownValues": [ 154 + "tools.ozone.moderation.defs#reviewOpen", 155 + "tools.ozone.moderation.defs#reviewClosed", 156 + "tools.ozone.moderation.defs#reviewEscalated", 157 + "tools.ozone.moderation.defs#reviewNone" 158 + ] 159 }, 160 "reviewedAfter": { 161 "type": "string",
+21
crates/weaver-api/lexicons/tools_ozone_moderation_scheduleAction.json
··· 133 "type": "integer", 134 "description": "Indicates how long the takedown should be in effect before automatically expiring." 135 }, 136 "policies": { 137 "type": "array", 138 "description": "Names/Keywords of the policies that drove the decision.", ··· 140 "type": "string" 141 }, 142 "maxLength": 5 143 } 144 } 145 }
··· 133 "type": "integer", 134 "description": "Indicates how long the takedown should be in effect before automatically expiring." 135 }, 136 + "emailContent": { 137 + "type": "string", 138 + "description": "Email content to be sent to the user upon takedown." 139 + }, 140 + "emailSubject": { 141 + "type": "string", 142 + "description": "Subject of the email to be sent to the user upon takedown." 143 + }, 144 "policies": { 145 "type": "array", 146 "description": "Names/Keywords of the policies that drove the decision.", ··· 148 "type": "string" 149 }, 150 "maxLength": 5 151 + }, 152 + "severityLevel": { 153 + "type": "string", 154 + "description": "Severity level of the violation (e.g., 'sev-0', 'sev-1', 'sev-2', etc.)." 155 + }, 156 + "strikeCount": { 157 + "type": "integer", 158 + "description": "Number of strikes to assign to the user when takedown is applied." 159 + }, 160 + "strikeExpiresAt": { 161 + "type": "string", 162 + "description": "When the strike should expire. If not provided, the strike never expires.", 163 + "format": "datetime" 164 } 165 } 166 }
+52 -50
crates/weaver-api/src/app_bsky/actor.rs
··· 3502 /// Groups of users to apply the muted word to. If undefined, applies to all users. 3503 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3504 #[serde(borrow)] 3505 - pub actor_target: Option<jacquard_common::CowStr<'a>>, 3506 /// The date and time at which the muted word will expire and no longer be applied. 3507 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3508 - pub expires_at: Option<jacquard_common::types::string::Datetime>, 3509 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3510 #[serde(borrow)] 3511 - pub id: Option<jacquard_common::CowStr<'a>>, 3512 /// The intended targets of the muted word. 3513 #[serde(borrow)] 3514 pub targets: Vec<crate::app_bsky::actor::MutedWordTarget<'a>>, ··· 3998 /// Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters. 3999 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4000 #[serde(borrow)] 4001 - pub data: Option<jacquard_common::CowStr<'a>>, 4002 /// The date and time at which the NUX will expire and should be considered completed. 4003 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4004 - pub expires_at: Option<jacquard_common::types::string::Datetime>, 4005 #[serde(borrow)] 4006 pub id: jacquard_common::CowStr<'a>, 4007 } ··· 4551 pub struct ProfileView<'a> { 4552 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4553 #[serde(borrow)] 4554 - pub associated: Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 4555 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4556 #[serde(borrow)] 4557 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 4558 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4559 - pub created_at: Option<jacquard_common::types::string::Datetime>, 4560 /// Debug information for internal development 4561 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4562 #[serde(borrow)] 4563 - pub debug: Option<jacquard_common::types::value::Data<'a>>, 4564 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4565 #[serde(borrow)] 4566 - pub description: Option<jacquard_common::CowStr<'a>>, 4567 #[serde(borrow)] 4568 pub did: jacquard_common::types::string::Did<'a>, 4569 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4570 #[serde(borrow)] 4571 - pub display_name: Option<jacquard_common::CowStr<'a>>, 4572 #[serde(borrow)] 4573 pub handle: jacquard_common::types::string::Handle<'a>, 4574 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4575 - pub indexed_at: Option<jacquard_common::types::string::Datetime>, 4576 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4577 #[serde(borrow)] 4578 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 4579 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4580 #[serde(borrow)] 4581 - pub pronouns: Option<jacquard_common::CowStr<'a>>, 4582 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4583 #[serde(borrow)] 4584 - pub status: Option<crate::app_bsky::actor::StatusView<'a>>, 4585 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4586 #[serde(borrow)] 4587 - pub verification: Option<crate::app_bsky::actor::VerificationState<'a>>, 4588 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4589 #[serde(borrow)] 4590 - pub viewer: Option<crate::app_bsky::actor::ViewerState<'a>>, 4591 } 4592 4593 pub mod profile_view_state { ··· 5097 pub struct ProfileViewBasic<'a> { 5098 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5099 #[serde(borrow)] 5100 - pub associated: Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 5101 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5102 #[serde(borrow)] 5103 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 5104 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5105 - pub created_at: Option<jacquard_common::types::string::Datetime>, 5106 /// Debug information for internal development 5107 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5108 #[serde(borrow)] 5109 - pub debug: Option<jacquard_common::types::value::Data<'a>>, 5110 #[serde(borrow)] 5111 pub did: jacquard_common::types::string::Did<'a>, 5112 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5113 #[serde(borrow)] 5114 - pub display_name: Option<jacquard_common::CowStr<'a>>, 5115 #[serde(borrow)] 5116 pub handle: jacquard_common::types::string::Handle<'a>, 5117 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5118 #[serde(borrow)] 5119 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 5120 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5121 #[serde(borrow)] 5122 - pub pronouns: Option<jacquard_common::CowStr<'a>>, 5123 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5124 #[serde(borrow)] 5125 - pub status: Option<crate::app_bsky::actor::StatusView<'a>>, 5126 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5127 #[serde(borrow)] 5128 - pub verification: Option<crate::app_bsky::actor::VerificationState<'a>>, 5129 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5130 #[serde(borrow)] 5131 - pub viewer: Option<crate::app_bsky::actor::ViewerState<'a>>, 5132 } 5133 5134 pub mod profile_view_basic_state { ··· 5562 pub struct ProfileViewDetailed<'a> { 5563 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5564 #[serde(borrow)] 5565 - pub associated: Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 5566 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5567 #[serde(borrow)] 5568 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 5569 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5570 #[serde(borrow)] 5571 - pub banner: Option<jacquard_common::types::string::Uri<'a>>, 5572 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5573 - pub created_at: Option<jacquard_common::types::string::Datetime>, 5574 /// Debug information for internal development 5575 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5576 #[serde(borrow)] 5577 - pub debug: Option<jacquard_common::types::value::Data<'a>>, 5578 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5579 #[serde(borrow)] 5580 - pub description: Option<jacquard_common::CowStr<'a>>, 5581 #[serde(borrow)] 5582 pub did: jacquard_common::types::string::Did<'a>, 5583 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5584 #[serde(borrow)] 5585 - pub display_name: Option<jacquard_common::CowStr<'a>>, 5586 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5587 - pub followers_count: Option<i64>, 5588 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5589 - pub follows_count: Option<i64>, 5590 #[serde(borrow)] 5591 pub handle: jacquard_common::types::string::Handle<'a>, 5592 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5593 - pub indexed_at: Option<jacquard_common::types::string::Datetime>, 5594 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5595 #[serde(borrow)] 5596 - pub joined_via_starter_pack: Option< 5597 crate::app_bsky::graph::StarterPackViewBasic<'a>, 5598 >, 5599 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5600 #[serde(borrow)] 5601 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 5602 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5603 #[serde(borrow)] 5604 - pub pinned_post: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 5605 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5606 - pub posts_count: Option<i64>, 5607 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5608 #[serde(borrow)] 5609 - pub pronouns: Option<jacquard_common::CowStr<'a>>, 5610 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5611 #[serde(borrow)] 5612 - pub status: Option<crate::app_bsky::actor::StatusView<'a>>, 5613 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5614 #[serde(borrow)] 5615 - pub verification: Option<crate::app_bsky::actor::VerificationState<'a>>, 5616 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5617 #[serde(borrow)] 5618 - pub viewer: Option<crate::app_bsky::actor::ViewerState<'a>>, 5619 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5620 #[serde(borrow)] 5621 - pub website: Option<jacquard_common::types::string::Uri<'a>>, 5622 } 5623 5624 pub mod profile_view_detailed_state { ··· 6528 #[serde(borrow)] 6529 pub saved: Vec<jacquard_common::types::string::AtUri<'a>>, 6530 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6531 - pub timeline_index: Option<i64>, 6532 } 6533 6534 pub mod saved_feeds_pref_state { ··· 6857 /// An optional embed associated with the status. 6858 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6859 #[serde(borrow)] 6860 - pub embed: Option<crate::app_bsky::embed::external::View<'a>>, 6861 /// The date when this status will expire. The application might choose to no longer return the status after expiration. 6862 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6863 - pub expires_at: Option<jacquard_common::types::string::Datetime>, 6864 /// True if the status is not expired, false if it is expired. Only present if expiration was set. 6865 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6866 - pub is_active: Option<bool>, 6867 #[serde(borrow)] 6868 pub record: jacquard_common::types::value::Data<'a>, 6869 /// The status for the account.
··· 3502 /// Groups of users to apply the muted word to. If undefined, applies to all users. 3503 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3504 #[serde(borrow)] 3505 + pub actor_target: std::option::Option<jacquard_common::CowStr<'a>>, 3506 /// The date and time at which the muted word will expire and no longer be applied. 3507 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3508 + pub expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 3509 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3510 #[serde(borrow)] 3511 + pub id: std::option::Option<jacquard_common::CowStr<'a>>, 3512 /// The intended targets of the muted word. 3513 #[serde(borrow)] 3514 pub targets: Vec<crate::app_bsky::actor::MutedWordTarget<'a>>, ··· 3998 /// Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters. 3999 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4000 #[serde(borrow)] 4001 + pub data: std::option::Option<jacquard_common::CowStr<'a>>, 4002 /// The date and time at which the NUX will expire and should be considered completed. 4003 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4004 + pub expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 4005 #[serde(borrow)] 4006 pub id: jacquard_common::CowStr<'a>, 4007 } ··· 4551 pub struct ProfileView<'a> { 4552 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4553 #[serde(borrow)] 4554 + pub associated: std::option::Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 4555 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4556 #[serde(borrow)] 4557 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 4558 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4559 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 4560 /// Debug information for internal development 4561 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4562 #[serde(borrow)] 4563 + pub debug: std::option::Option<jacquard_common::types::value::Data<'a>>, 4564 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4565 #[serde(borrow)] 4566 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 4567 #[serde(borrow)] 4568 pub did: jacquard_common::types::string::Did<'a>, 4569 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4570 #[serde(borrow)] 4571 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 4572 #[serde(borrow)] 4573 pub handle: jacquard_common::types::string::Handle<'a>, 4574 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4575 + pub indexed_at: std::option::Option<jacquard_common::types::string::Datetime>, 4576 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4577 #[serde(borrow)] 4578 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 4579 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4580 #[serde(borrow)] 4581 + pub pronouns: std::option::Option<jacquard_common::CowStr<'a>>, 4582 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4583 #[serde(borrow)] 4584 + pub status: std::option::Option<crate::app_bsky::actor::StatusView<'a>>, 4585 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4586 #[serde(borrow)] 4587 + pub verification: std::option::Option<crate::app_bsky::actor::VerificationState<'a>>, 4588 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4589 #[serde(borrow)] 4590 + pub viewer: std::option::Option<crate::app_bsky::actor::ViewerState<'a>>, 4591 } 4592 4593 pub mod profile_view_state { ··· 5097 pub struct ProfileViewBasic<'a> { 5098 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5099 #[serde(borrow)] 5100 + pub associated: std::option::Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 5101 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5102 #[serde(borrow)] 5103 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 5104 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5105 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 5106 /// Debug information for internal development 5107 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5108 #[serde(borrow)] 5109 + pub debug: std::option::Option<jacquard_common::types::value::Data<'a>>, 5110 #[serde(borrow)] 5111 pub did: jacquard_common::types::string::Did<'a>, 5112 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5113 #[serde(borrow)] 5114 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 5115 #[serde(borrow)] 5116 pub handle: jacquard_common::types::string::Handle<'a>, 5117 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5118 #[serde(borrow)] 5119 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 5120 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5121 #[serde(borrow)] 5122 + pub pronouns: std::option::Option<jacquard_common::CowStr<'a>>, 5123 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5124 #[serde(borrow)] 5125 + pub status: std::option::Option<crate::app_bsky::actor::StatusView<'a>>, 5126 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5127 #[serde(borrow)] 5128 + pub verification: std::option::Option<crate::app_bsky::actor::VerificationState<'a>>, 5129 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5130 #[serde(borrow)] 5131 + pub viewer: std::option::Option<crate::app_bsky::actor::ViewerState<'a>>, 5132 } 5133 5134 pub mod profile_view_basic_state { ··· 5562 pub struct ProfileViewDetailed<'a> { 5563 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5564 #[serde(borrow)] 5565 + pub associated: std::option::Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 5566 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5567 #[serde(borrow)] 5568 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 5569 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5570 #[serde(borrow)] 5571 + pub banner: std::option::Option<jacquard_common::types::string::Uri<'a>>, 5572 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5573 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 5574 /// Debug information for internal development 5575 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5576 #[serde(borrow)] 5577 + pub debug: std::option::Option<jacquard_common::types::value::Data<'a>>, 5578 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5579 #[serde(borrow)] 5580 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 5581 #[serde(borrow)] 5582 pub did: jacquard_common::types::string::Did<'a>, 5583 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5584 #[serde(borrow)] 5585 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 5586 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5587 + pub followers_count: std::option::Option<i64>, 5588 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5589 + pub follows_count: std::option::Option<i64>, 5590 #[serde(borrow)] 5591 pub handle: jacquard_common::types::string::Handle<'a>, 5592 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5593 + pub indexed_at: std::option::Option<jacquard_common::types::string::Datetime>, 5594 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5595 #[serde(borrow)] 5596 + pub joined_via_starter_pack: std::option::Option< 5597 crate::app_bsky::graph::StarterPackViewBasic<'a>, 5598 >, 5599 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5600 #[serde(borrow)] 5601 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 5602 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5603 #[serde(borrow)] 5604 + pub pinned_post: std::option::Option< 5605 + crate::com_atproto::repo::strong_ref::StrongRef<'a>, 5606 + >, 5607 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5608 + pub posts_count: std::option::Option<i64>, 5609 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5610 #[serde(borrow)] 5611 + pub pronouns: std::option::Option<jacquard_common::CowStr<'a>>, 5612 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5613 #[serde(borrow)] 5614 + pub status: std::option::Option<crate::app_bsky::actor::StatusView<'a>>, 5615 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5616 #[serde(borrow)] 5617 + pub verification: std::option::Option<crate::app_bsky::actor::VerificationState<'a>>, 5618 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5619 #[serde(borrow)] 5620 + pub viewer: std::option::Option<crate::app_bsky::actor::ViewerState<'a>>, 5621 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5622 #[serde(borrow)] 5623 + pub website: std::option::Option<jacquard_common::types::string::Uri<'a>>, 5624 } 5625 5626 pub mod profile_view_detailed_state { ··· 6530 #[serde(borrow)] 6531 pub saved: Vec<jacquard_common::types::string::AtUri<'a>>, 6532 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6533 + pub timeline_index: std::option::Option<i64>, 6534 } 6535 6536 pub mod saved_feeds_pref_state { ··· 6859 /// An optional embed associated with the status. 6860 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6861 #[serde(borrow)] 6862 + pub embed: std::option::Option<crate::app_bsky::embed::external::View<'a>>, 6863 /// The date when this status will expire. The application might choose to no longer return the status after expiration. 6864 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6865 + pub expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 6866 /// True if the status is not expired, false if it is expired. Only present if expiration was set. 6867 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6868 + pub is_active: std::option::Option<bool>, 6869 #[serde(borrow)] 6870 pub record: jacquard_common::types::value::Data<'a>, 6871 /// The status for the account.
+12 -10
crates/weaver-api/src/app_bsky/actor/profile.rs
··· 21 /// Small image to be displayed next to posts from account. AKA, 'profile picture' 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 - pub avatar: Option<jacquard_common::types::blob::BlobRef<'a>>, 25 /// Larger horizontal image to display behind profile view. 26 #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 #[serde(borrow)] 28 - pub banner: Option<jacquard_common::types::blob::BlobRef<'a>>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 - pub created_at: Option<jacquard_common::types::string::Datetime>, 31 /// Free-form profile description text. 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 - pub description: Option<jacquard_common::CowStr<'a>>, 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 - pub display_name: Option<jacquard_common::CowStr<'a>>, 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 - pub joined_via_starter_pack: Option< 41 crate::com_atproto::repo::strong_ref::StrongRef<'a>, 42 >, 43 /// Self-label values, specific to the Bluesky application, on the overall account. 44 #[serde(skip_serializing_if = "std::option::Option::is_none")] 45 #[serde(borrow)] 46 - pub labels: Option<crate::com_atproto::label::SelfLabels<'a>>, 47 #[serde(skip_serializing_if = "std::option::Option::is_none")] 48 #[serde(borrow)] 49 - pub pinned_post: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 50 /// Free-form pronouns text. 51 #[serde(skip_serializing_if = "std::option::Option::is_none")] 52 #[serde(borrow)] 53 - pub pronouns: Option<jacquard_common::CowStr<'a>>, 54 #[serde(skip_serializing_if = "std::option::Option::is_none")] 55 #[serde(borrow)] 56 - pub website: Option<jacquard_common::types::string::Uri<'a>>, 57 } 58 59 pub mod profile_state {
··· 21 /// Small image to be displayed next to posts from account. AKA, 'profile picture' 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 + pub avatar: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 25 /// Larger horizontal image to display behind profile view. 26 #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 #[serde(borrow)] 28 + pub banner: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 31 /// Free-form profile description text. 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 + pub joined_via_starter_pack: std::option::Option< 41 crate::com_atproto::repo::strong_ref::StrongRef<'a>, 42 >, 43 /// Self-label values, specific to the Bluesky application, on the overall account. 44 #[serde(skip_serializing_if = "std::option::Option::is_none")] 45 #[serde(borrow)] 46 + pub labels: std::option::Option<crate::com_atproto::label::SelfLabels<'a>>, 47 #[serde(skip_serializing_if = "std::option::Option::is_none")] 48 #[serde(borrow)] 49 + pub pinned_post: std::option::Option< 50 + crate::com_atproto::repo::strong_ref::StrongRef<'a>, 51 + >, 52 /// Free-form pronouns text. 53 #[serde(skip_serializing_if = "std::option::Option::is_none")] 54 #[serde(borrow)] 55 + pub pronouns: std::option::Option<jacquard_common::CowStr<'a>>, 56 #[serde(skip_serializing_if = "std::option::Option::is_none")] 57 #[serde(borrow)] 58 + pub website: std::option::Option<jacquard_common::types::string::Uri<'a>>, 59 } 60 61 pub mod profile_state {
+2 -2
crates/weaver-api/src/app_bsky/actor/status.rs
··· 39 pub created_at: jacquard_common::types::string::Datetime, 40 /// The duration of the status in minutes. Applications can choose to impose minimum and maximum limits. 41 #[serde(skip_serializing_if = "std::option::Option::is_none")] 42 - pub duration_minutes: Option<i64>, 43 /// An optional embed associated with the status. 44 #[serde(skip_serializing_if = "std::option::Option::is_none")] 45 #[serde(borrow)] 46 - pub embed: Option<crate::app_bsky::embed::external::ExternalRecord<'a>>, 47 /// The status for the account. 48 #[serde(borrow)] 49 pub status: jacquard_common::CowStr<'a>,
··· 39 pub created_at: jacquard_common::types::string::Datetime, 40 /// The duration of the status in minutes. Applications can choose to impose minimum and maximum limits. 41 #[serde(skip_serializing_if = "std::option::Option::is_none")] 42 + pub duration_minutes: std::option::Option<i64>, 43 /// An optional embed associated with the status. 44 #[serde(skip_serializing_if = "std::option::Option::is_none")] 45 #[serde(borrow)] 46 + pub embed: std::option::Option<crate::app_bsky::embed::external::ExternalRecord<'a>>, 47 /// The status for the account. 48 #[serde(borrow)] 49 pub status: jacquard_common::CowStr<'a>,
+1 -1
crates/weaver-api/src/app_bsky/bookmark.rs
··· 263 #[serde(rename_all = "camelCase")] 264 pub struct BookmarkView<'a> { 265 #[serde(skip_serializing_if = "std::option::Option::is_none")] 266 - pub created_at: Option<jacquard_common::types::string::Datetime>, 267 #[serde(borrow)] 268 pub item: BookmarkViewItem<'a>, 269 /// A strong ref to the bookmarked record.
··· 263 #[serde(rename_all = "camelCase")] 264 pub struct BookmarkView<'a> { 265 #[serde(skip_serializing_if = "std::option::Option::is_none")] 266 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 267 #[serde(borrow)] 268 pub item: BookmarkViewItem<'a>, 269 /// A strong ref to the bookmarked record.
+2 -2
crates/weaver-api/src/app_bsky/embed/external.rs
··· 21 pub description: jacquard_common::CowStr<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 - pub thumb: Option<jacquard_common::types::blob::BlobRef<'a>>, 25 #[serde(borrow)] 26 pub title: jacquard_common::CowStr<'a>, 27 #[serde(borrow)] ··· 767 pub description: jacquard_common::CowStr<'a>, 768 #[serde(skip_serializing_if = "std::option::Option::is_none")] 769 #[serde(borrow)] 770 - pub thumb: Option<jacquard_common::types::string::Uri<'a>>, 771 #[serde(borrow)] 772 pub title: jacquard_common::CowStr<'a>, 773 #[serde(borrow)]
··· 21 pub description: jacquard_common::CowStr<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 + pub thumb: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 25 #[serde(borrow)] 26 pub title: jacquard_common::CowStr<'a>, 27 #[serde(borrow)] ··· 767 pub description: jacquard_common::CowStr<'a>, 768 #[serde(skip_serializing_if = "std::option::Option::is_none")] 769 #[serde(borrow)] 770 + pub thumb: std::option::Option<jacquard_common::types::string::Uri<'a>>, 771 #[serde(borrow)] 772 pub title: jacquard_common::CowStr<'a>, 773 #[serde(borrow)]
+2 -2
crates/weaver-api/src/app_bsky/embed/images.rs
··· 22 pub alt: jacquard_common::CowStr<'a>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub aspect_ratio: Option<crate::app_bsky::embed::AspectRatio<'a>>, 26 #[serde(borrow)] 27 pub image: jacquard_common::types::blob::BlobRef<'a>, 28 } ··· 741 pub alt: jacquard_common::CowStr<'a>, 742 #[serde(skip_serializing_if = "std::option::Option::is_none")] 743 #[serde(borrow)] 744 - pub aspect_ratio: Option<crate::app_bsky::embed::AspectRatio<'a>>, 745 /// Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. 746 #[serde(borrow)] 747 pub fullsize: jacquard_common::types::string::Uri<'a>,
··· 22 pub alt: jacquard_common::CowStr<'a>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub aspect_ratio: std::option::Option<crate::app_bsky::embed::AspectRatio<'a>>, 26 #[serde(borrow)] 27 pub image: jacquard_common::types::blob::BlobRef<'a>, 28 } ··· 741 pub alt: jacquard_common::CowStr<'a>, 742 #[serde(skip_serializing_if = "std::option::Option::is_none")] 743 #[serde(borrow)] 744 + pub aspect_ratio: std::option::Option<crate::app_bsky::embed::AspectRatio<'a>>, 745 /// Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. 746 #[serde(borrow)] 747 pub fullsize: jacquard_common::types::string::Uri<'a>,
+6 -6
crates/weaver-api/src/app_bsky/embed/record.rs
··· 1273 pub cid: jacquard_common::types::string::Cid<'a>, 1274 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1275 #[serde(borrow)] 1276 - pub embeds: Option<Vec<ViewRecordEmbedsItem<'a>>>, 1277 pub indexed_at: jacquard_common::types::string::Datetime, 1278 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1279 #[serde(borrow)] 1280 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 1281 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1282 - pub like_count: Option<i64>, 1283 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1284 - pub quote_count: Option<i64>, 1285 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1286 - pub reply_count: Option<i64>, 1287 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1288 - pub repost_count: Option<i64>, 1289 #[serde(borrow)] 1290 pub uri: jacquard_common::types::string::AtUri<'a>, 1291 /// The record data itself.
··· 1273 pub cid: jacquard_common::types::string::Cid<'a>, 1274 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1275 #[serde(borrow)] 1276 + pub embeds: std::option::Option<Vec<ViewRecordEmbedsItem<'a>>>, 1277 pub indexed_at: jacquard_common::types::string::Datetime, 1278 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1279 #[serde(borrow)] 1280 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 1281 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1282 + pub like_count: std::option::Option<i64>, 1283 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1284 + pub quote_count: std::option::Option<i64>, 1285 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1286 + pub reply_count: std::option::Option<i64>, 1287 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1288 + pub repost_count: std::option::Option<i64>, 1289 #[serde(borrow)] 1290 pub uri: jacquard_common::types::string::AtUri<'a>, 1291 /// The record data itself.
+6 -6
crates/weaver-api/src/app_bsky/embed/video.rs
··· 414 /// Alt text description of the video, for accessibility. 415 #[serde(skip_serializing_if = "std::option::Option::is_none")] 416 #[serde(borrow)] 417 - pub alt: Option<jacquard_common::CowStr<'a>>, 418 #[serde(skip_serializing_if = "std::option::Option::is_none")] 419 #[serde(borrow)] 420 - pub aspect_ratio: Option<crate::app_bsky::embed::AspectRatio<'a>>, 421 #[serde(skip_serializing_if = "std::option::Option::is_none")] 422 #[serde(borrow)] 423 - pub captions: Option<Vec<crate::app_bsky::embed::video::Caption<'a>>>, 424 /// The mp4 video file. May be up to 100mb, formerly limited to 50mb. 425 #[serde(borrow)] 426 pub video: jacquard_common::types::blob::BlobRef<'a>, ··· 664 pub struct View<'a> { 665 #[serde(skip_serializing_if = "std::option::Option::is_none")] 666 #[serde(borrow)] 667 - pub alt: Option<jacquard_common::CowStr<'a>>, 668 #[serde(skip_serializing_if = "std::option::Option::is_none")] 669 #[serde(borrow)] 670 - pub aspect_ratio: Option<crate::app_bsky::embed::AspectRatio<'a>>, 671 #[serde(borrow)] 672 pub cid: jacquard_common::types::string::Cid<'a>, 673 #[serde(borrow)] 674 pub playlist: jacquard_common::types::string::Uri<'a>, 675 #[serde(skip_serializing_if = "std::option::Option::is_none")] 676 #[serde(borrow)] 677 - pub thumbnail: Option<jacquard_common::types::string::Uri<'a>>, 678 } 679 680 pub mod view_state {
··· 414 /// Alt text description of the video, for accessibility. 415 #[serde(skip_serializing_if = "std::option::Option::is_none")] 416 #[serde(borrow)] 417 + pub alt: std::option::Option<jacquard_common::CowStr<'a>>, 418 #[serde(skip_serializing_if = "std::option::Option::is_none")] 419 #[serde(borrow)] 420 + pub aspect_ratio: std::option::Option<crate::app_bsky::embed::AspectRatio<'a>>, 421 #[serde(skip_serializing_if = "std::option::Option::is_none")] 422 #[serde(borrow)] 423 + pub captions: std::option::Option<Vec<crate::app_bsky::embed::video::Caption<'a>>>, 424 /// The mp4 video file. May be up to 100mb, formerly limited to 50mb. 425 #[serde(borrow)] 426 pub video: jacquard_common::types::blob::BlobRef<'a>, ··· 664 pub struct View<'a> { 665 #[serde(skip_serializing_if = "std::option::Option::is_none")] 666 #[serde(borrow)] 667 + pub alt: std::option::Option<jacquard_common::CowStr<'a>>, 668 #[serde(skip_serializing_if = "std::option::Option::is_none")] 669 #[serde(borrow)] 670 + pub aspect_ratio: std::option::Option<crate::app_bsky::embed::AspectRatio<'a>>, 671 #[serde(borrow)] 672 pub cid: jacquard_common::types::string::Cid<'a>, 673 #[serde(borrow)] 674 pub playlist: jacquard_common::types::string::Uri<'a>, 675 #[serde(skip_serializing_if = "std::option::Option::is_none")] 676 #[serde(borrow)] 677 + pub thumbnail: std::option::Option<jacquard_common::types::string::Uri<'a>>, 678 } 679 680 pub mod view_state {
+35 -31
crates/weaver-api/src/app_bsky/feed.rs
··· 46 pub did: jacquard_common::types::string::Did<'a>, 47 #[serde(skip_serializing_if = "std::option::Option::is_none")] 48 #[serde(borrow)] 49 - pub viewer: Option<crate::app_bsky::actor::ViewerState<'a>>, 50 } 51 52 pub mod blocked_author_state { ··· 1916 /// Context provided by feed generator that may be passed back alongside interactions. 1917 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1918 #[serde(borrow)] 1919 - pub feed_context: Option<jacquard_common::CowStr<'a>>, 1920 #[serde(borrow)] 1921 pub post: crate::app_bsky::feed::PostView<'a>, 1922 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1923 #[serde(borrow)] 1924 - pub reason: Option<FeedViewPostReason<'a>>, 1925 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1926 #[serde(borrow)] 1927 - pub reply: Option<crate::app_bsky::feed::ReplyRef<'a>>, 1928 /// Unique identifier per request that may be passed back alongside interactions. 1929 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1930 #[serde(borrow)] 1931 - pub req_id: Option<jacquard_common::CowStr<'a>>, 1932 } 1933 1934 pub mod feed_view_post_state { ··· 2188 #[serde(rename_all = "camelCase")] 2189 pub struct GeneratorView<'a> { 2190 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2191 - pub accepts_interactions: Option<bool>, 2192 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2193 #[serde(borrow)] 2194 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 2195 #[serde(borrow)] 2196 pub cid: jacquard_common::types::string::Cid<'a>, 2197 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2198 #[serde(borrow)] 2199 - pub content_mode: Option<jacquard_common::CowStr<'a>>, 2200 #[serde(borrow)] 2201 pub creator: crate::app_bsky::actor::ProfileView<'a>, 2202 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2203 #[serde(borrow)] 2204 - pub description: Option<jacquard_common::CowStr<'a>>, 2205 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2206 #[serde(borrow)] 2207 - pub description_facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 2208 #[serde(borrow)] 2209 pub did: jacquard_common::types::string::Did<'a>, 2210 #[serde(borrow)] ··· 2212 pub indexed_at: jacquard_common::types::string::Datetime, 2213 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2214 #[serde(borrow)] 2215 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 2216 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2217 - pub like_count: Option<i64>, 2218 #[serde(borrow)] 2219 pub uri: jacquard_common::types::string::AtUri<'a>, 2220 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2221 #[serde(borrow)] 2222 - pub viewer: Option<crate::app_bsky::feed::GeneratorViewerState<'a>>, 2223 } 2224 2225 pub mod generator_view_state { ··· 3161 #[serde(borrow)] 3162 pub author: crate::app_bsky::actor::ProfileViewBasic<'a>, 3163 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3164 - pub bookmark_count: Option<i64>, 3165 #[serde(borrow)] 3166 pub cid: jacquard_common::types::string::Cid<'a>, 3167 /// Debug information for internal development 3168 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3169 #[serde(borrow)] 3170 - pub debug: Option<jacquard_common::types::value::Data<'a>>, 3171 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3172 #[serde(borrow)] 3173 - pub embed: Option<PostViewEmbed<'a>>, 3174 pub indexed_at: jacquard_common::types::string::Datetime, 3175 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3176 #[serde(borrow)] 3177 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 3178 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3179 - pub like_count: Option<i64>, 3180 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3181 - pub quote_count: Option<i64>, 3182 #[serde(borrow)] 3183 pub record: jacquard_common::types::value::Data<'a>, 3184 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3185 - pub reply_count: Option<i64>, 3186 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3187 - pub repost_count: Option<i64>, 3188 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3189 #[serde(borrow)] 3190 - pub threadgate: Option<crate::app_bsky::feed::ThreadgateView<'a>>, 3191 #[serde(borrow)] 3192 pub uri: jacquard_common::types::string::AtUri<'a>, 3193 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3194 #[serde(borrow)] 3195 - pub viewer: Option<crate::app_bsky::feed::ViewerState<'a>>, 3196 } 3197 3198 pub mod post_view_state { ··· 3740 pub by: crate::app_bsky::actor::ProfileViewBasic<'a>, 3741 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3742 #[serde(borrow)] 3743 - pub cid: Option<jacquard_common::types::string::Cid<'a>>, 3744 pub indexed_at: jacquard_common::types::string::Datetime, 3745 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3746 #[serde(borrow)] 3747 - pub uri: Option<jacquard_common::types::string::AtUri<'a>>, 3748 } 3749 3750 pub mod reason_repost_state { ··· 3963 /// When parent is a reply to another post, this is the author of that post. 3964 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3965 #[serde(borrow)] 3966 - pub grandparent_author: Option<crate::app_bsky::actor::ProfileViewBasic<'a>>, 3967 #[serde(borrow)] 3968 pub parent: ReplyRefParent<'a>, 3969 #[serde(borrow)] ··· 4242 /// Context that will be passed through to client and may be passed to feed generator back alongside interactions. 4243 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4244 #[serde(borrow)] 4245 - pub feed_context: Option<jacquard_common::CowStr<'a>>, 4246 #[serde(borrow)] 4247 pub post: jacquard_common::types::string::AtUri<'a>, 4248 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4249 #[serde(borrow)] 4250 - pub reason: Option<SkeletonFeedPostReason<'a>>, 4251 } 4252 4253 pub mod skeleton_feed_post_state { ··· 4665 pub struct ThreadViewPost<'a> { 4666 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4667 #[serde(borrow)] 4668 - pub parent: Option<ThreadViewPostParent<'a>>, 4669 #[serde(borrow)] 4670 pub post: crate::app_bsky::feed::PostView<'a>, 4671 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4672 #[serde(borrow)] 4673 - pub replies: Option<Vec<ThreadViewPostRepliesItem<'a>>>, 4674 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4675 #[serde(borrow)] 4676 - pub thread_context: Option<crate::app_bsky::feed::ThreadContext<'a>>, 4677 } 4678 4679 pub mod thread_view_post_state {
··· 46 pub did: jacquard_common::types::string::Did<'a>, 47 #[serde(skip_serializing_if = "std::option::Option::is_none")] 48 #[serde(borrow)] 49 + pub viewer: std::option::Option<crate::app_bsky::actor::ViewerState<'a>>, 50 } 51 52 pub mod blocked_author_state { ··· 1916 /// Context provided by feed generator that may be passed back alongside interactions. 1917 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1918 #[serde(borrow)] 1919 + pub feed_context: std::option::Option<jacquard_common::CowStr<'a>>, 1920 #[serde(borrow)] 1921 pub post: crate::app_bsky::feed::PostView<'a>, 1922 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1923 #[serde(borrow)] 1924 + pub reason: std::option::Option<FeedViewPostReason<'a>>, 1925 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1926 #[serde(borrow)] 1927 + pub reply: std::option::Option<crate::app_bsky::feed::ReplyRef<'a>>, 1928 /// Unique identifier per request that may be passed back alongside interactions. 1929 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1930 #[serde(borrow)] 1931 + pub req_id: std::option::Option<jacquard_common::CowStr<'a>>, 1932 } 1933 1934 pub mod feed_view_post_state { ··· 2188 #[serde(rename_all = "camelCase")] 2189 pub struct GeneratorView<'a> { 2190 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2191 + pub accepts_interactions: std::option::Option<bool>, 2192 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2193 #[serde(borrow)] 2194 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 2195 #[serde(borrow)] 2196 pub cid: jacquard_common::types::string::Cid<'a>, 2197 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2198 #[serde(borrow)] 2199 + pub content_mode: std::option::Option<jacquard_common::CowStr<'a>>, 2200 #[serde(borrow)] 2201 pub creator: crate::app_bsky::actor::ProfileView<'a>, 2202 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2203 #[serde(borrow)] 2204 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 2205 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2206 #[serde(borrow)] 2207 + pub description_facets: std::option::Option< 2208 + Vec<crate::app_bsky::richtext::facet::Facet<'a>>, 2209 + >, 2210 #[serde(borrow)] 2211 pub did: jacquard_common::types::string::Did<'a>, 2212 #[serde(borrow)] ··· 2214 pub indexed_at: jacquard_common::types::string::Datetime, 2215 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2216 #[serde(borrow)] 2217 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 2218 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2219 + pub like_count: std::option::Option<i64>, 2220 #[serde(borrow)] 2221 pub uri: jacquard_common::types::string::AtUri<'a>, 2222 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2223 #[serde(borrow)] 2224 + pub viewer: std::option::Option<crate::app_bsky::feed::GeneratorViewerState<'a>>, 2225 } 2226 2227 pub mod generator_view_state { ··· 3163 #[serde(borrow)] 3164 pub author: crate::app_bsky::actor::ProfileViewBasic<'a>, 3165 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3166 + pub bookmark_count: std::option::Option<i64>, 3167 #[serde(borrow)] 3168 pub cid: jacquard_common::types::string::Cid<'a>, 3169 /// Debug information for internal development 3170 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3171 #[serde(borrow)] 3172 + pub debug: std::option::Option<jacquard_common::types::value::Data<'a>>, 3173 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3174 #[serde(borrow)] 3175 + pub embed: std::option::Option<PostViewEmbed<'a>>, 3176 pub indexed_at: jacquard_common::types::string::Datetime, 3177 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3178 #[serde(borrow)] 3179 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 3180 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3181 + pub like_count: std::option::Option<i64>, 3182 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3183 + pub quote_count: std::option::Option<i64>, 3184 #[serde(borrow)] 3185 pub record: jacquard_common::types::value::Data<'a>, 3186 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3187 + pub reply_count: std::option::Option<i64>, 3188 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3189 + pub repost_count: std::option::Option<i64>, 3190 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3191 #[serde(borrow)] 3192 + pub threadgate: std::option::Option<crate::app_bsky::feed::ThreadgateView<'a>>, 3193 #[serde(borrow)] 3194 pub uri: jacquard_common::types::string::AtUri<'a>, 3195 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3196 #[serde(borrow)] 3197 + pub viewer: std::option::Option<crate::app_bsky::feed::ViewerState<'a>>, 3198 } 3199 3200 pub mod post_view_state { ··· 3742 pub by: crate::app_bsky::actor::ProfileViewBasic<'a>, 3743 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3744 #[serde(borrow)] 3745 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 3746 pub indexed_at: jacquard_common::types::string::Datetime, 3747 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3748 #[serde(borrow)] 3749 + pub uri: std::option::Option<jacquard_common::types::string::AtUri<'a>>, 3750 } 3751 3752 pub mod reason_repost_state { ··· 3965 /// When parent is a reply to another post, this is the author of that post. 3966 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3967 #[serde(borrow)] 3968 + pub grandparent_author: std::option::Option< 3969 + crate::app_bsky::actor::ProfileViewBasic<'a>, 3970 + >, 3971 #[serde(borrow)] 3972 pub parent: ReplyRefParent<'a>, 3973 #[serde(borrow)] ··· 4246 /// Context that will be passed through to client and may be passed to feed generator back alongside interactions. 4247 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4248 #[serde(borrow)] 4249 + pub feed_context: std::option::Option<jacquard_common::CowStr<'a>>, 4250 #[serde(borrow)] 4251 pub post: jacquard_common::types::string::AtUri<'a>, 4252 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4253 #[serde(borrow)] 4254 + pub reason: std::option::Option<SkeletonFeedPostReason<'a>>, 4255 } 4256 4257 pub mod skeleton_feed_post_state { ··· 4669 pub struct ThreadViewPost<'a> { 4670 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4671 #[serde(borrow)] 4672 + pub parent: std::option::Option<ThreadViewPostParent<'a>>, 4673 #[serde(borrow)] 4674 pub post: crate::app_bsky::feed::PostView<'a>, 4675 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4676 #[serde(borrow)] 4677 + pub replies: std::option::Option<Vec<ThreadViewPostRepliesItem<'a>>>, 4678 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4679 #[serde(borrow)] 4680 + pub thread_context: std::option::Option<crate::app_bsky::feed::ThreadContext<'a>>, 4681 } 4682 4683 pub mod thread_view_post_state {
+8 -6
crates/weaver-api/src/app_bsky/feed/generator.rs
··· 20 pub struct Generator<'a> { 21 /// Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 - pub accepts_interactions: Option<bool>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub avatar: Option<jacquard_common::types::blob::BlobRef<'a>>, 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub content_mode: Option<jacquard_common::CowStr<'a>>, 30 pub created_at: jacquard_common::types::string::Datetime, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 - pub description: Option<jacquard_common::CowStr<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 - pub description_facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 37 #[serde(borrow)] 38 pub did: jacquard_common::types::string::Did<'a>, 39 #[serde(borrow)] ··· 41 /// Self-label values 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 - pub labels: Option<crate::com_atproto::label::SelfLabels<'a>>, 45 } 46 47 pub mod generator_state {
··· 20 pub struct Generator<'a> { 21 /// Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 + pub accepts_interactions: std::option::Option<bool>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub avatar: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub content_mode: std::option::Option<jacquard_common::CowStr<'a>>, 30 pub created_at: jacquard_common::types::string::Datetime, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub description_facets: std::option::Option< 37 + Vec<crate::app_bsky::richtext::facet::Facet<'a>>, 38 + >, 39 #[serde(borrow)] 40 pub did: jacquard_common::types::string::Did<'a>, 41 #[serde(borrow)] ··· 43 /// Self-label values 44 #[serde(skip_serializing_if = "std::option::Option::is_none")] 45 #[serde(borrow)] 46 + pub labels: std::option::Option<crate::com_atproto::label::SelfLabels<'a>>, 47 } 48 49 pub mod generator_state {
+1 -1
crates/weaver-api/src/app_bsky/feed/like.rs
··· 23 pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub via: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 27 } 28 29 pub mod like_state {
··· 23 pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub via: std::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 27 } 28 29 pub mod like_state {
+7 -7
crates/weaver-api/src/app_bsky/feed/post.rs
··· 584 pub created_at: jacquard_common::types::string::Datetime, 585 #[serde(skip_serializing_if = "std::option::Option::is_none")] 586 #[serde(borrow)] 587 - pub embed: Option<PostEmbed<'a>>, 588 /// DEPRECATED: replaced by app.bsky.richtext.facet. 589 #[serde(skip_serializing_if = "std::option::Option::is_none")] 590 #[serde(borrow)] 591 - pub entities: Option<Vec<crate::app_bsky::feed::post::Entity<'a>>>, 592 /// Annotations of text (mentions, URLs, hashtags, etc) 593 #[serde(skip_serializing_if = "std::option::Option::is_none")] 594 #[serde(borrow)] 595 - pub facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 596 /// Self-label values for this post. Effectively content warnings. 597 #[serde(skip_serializing_if = "std::option::Option::is_none")] 598 #[serde(borrow)] 599 - pub labels: Option<crate::com_atproto::label::SelfLabels<'a>>, 600 /// Indicates human language of post primary text content. 601 #[serde(skip_serializing_if = "std::option::Option::is_none")] 602 - pub langs: Option<Vec<jacquard_common::types::string::Language>>, 603 #[serde(skip_serializing_if = "std::option::Option::is_none")] 604 #[serde(borrow)] 605 - pub reply: Option<crate::app_bsky::feed::post::ReplyRef<'a>>, 606 /// Additional hashtags, in addition to any included in post text and facets. 607 #[serde(skip_serializing_if = "std::option::Option::is_none")] 608 #[serde(borrow)] 609 - pub tags: Option<Vec<jacquard_common::CowStr<'a>>>, 610 /// The primary post content. May be an empty string, if there are embeds. 611 #[serde(borrow)] 612 pub text: jacquard_common::CowStr<'a>,
··· 584 pub created_at: jacquard_common::types::string::Datetime, 585 #[serde(skip_serializing_if = "std::option::Option::is_none")] 586 #[serde(borrow)] 587 + pub embed: std::option::Option<PostEmbed<'a>>, 588 /// DEPRECATED: replaced by app.bsky.richtext.facet. 589 #[serde(skip_serializing_if = "std::option::Option::is_none")] 590 #[serde(borrow)] 591 + pub entities: std::option::Option<Vec<crate::app_bsky::feed::post::Entity<'a>>>, 592 /// Annotations of text (mentions, URLs, hashtags, etc) 593 #[serde(skip_serializing_if = "std::option::Option::is_none")] 594 #[serde(borrow)] 595 + pub facets: std::option::Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 596 /// Self-label values for this post. Effectively content warnings. 597 #[serde(skip_serializing_if = "std::option::Option::is_none")] 598 #[serde(borrow)] 599 + pub labels: std::option::Option<crate::com_atproto::label::SelfLabels<'a>>, 600 /// Indicates human language of post primary text content. 601 #[serde(skip_serializing_if = "std::option::Option::is_none")] 602 + pub langs: std::option::Option<Vec<jacquard_common::types::string::Language>>, 603 #[serde(skip_serializing_if = "std::option::Option::is_none")] 604 #[serde(borrow)] 605 + pub reply: std::option::Option<crate::app_bsky::feed::post::ReplyRef<'a>>, 606 /// Additional hashtags, in addition to any included in post text and facets. 607 #[serde(skip_serializing_if = "std::option::Option::is_none")] 608 #[serde(borrow)] 609 + pub tags: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 610 /// The primary post content. May be an empty string, if there are embeds. 611 #[serde(borrow)] 612 pub text: jacquard_common::CowStr<'a>,
+6 -2
crates/weaver-api/src/app_bsky/feed/postgate.rs
··· 200 /// List of AT-URIs embedding this post that the author has detached from. 201 #[serde(skip_serializing_if = "std::option::Option::is_none")] 202 #[serde(borrow)] 203 - pub detached_embedding_uris: Option<Vec<jacquard_common::types::string::AtUri<'a>>>, 204 /// List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed. 205 #[serde(skip_serializing_if = "std::option::Option::is_none")] 206 #[serde(borrow)] 207 - pub embedding_rules: Option<Vec<crate::app_bsky::feed::postgate::DisableRule<'a>>>, 208 /// Reference (AT-URI) to the post record. 209 #[serde(borrow)] 210 pub post: jacquard_common::types::string::AtUri<'a>,
··· 200 /// List of AT-URIs embedding this post that the author has detached from. 201 #[serde(skip_serializing_if = "std::option::Option::is_none")] 202 #[serde(borrow)] 203 + pub detached_embedding_uris: std::option::Option< 204 + Vec<jacquard_common::types::string::AtUri<'a>>, 205 + >, 206 /// List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed. 207 #[serde(skip_serializing_if = "std::option::Option::is_none")] 208 #[serde(borrow)] 209 + pub embedding_rules: std::option::Option< 210 + Vec<crate::app_bsky::feed::postgate::DisableRule<'a>>, 211 + >, 212 /// Reference (AT-URI) to the post record. 213 #[serde(borrow)] 214 pub post: jacquard_common::types::string::AtUri<'a>,
+1 -1
crates/weaver-api/src/app_bsky/feed/repost.rs
··· 23 pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub via: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 27 } 28 29 pub mod repost_state {
··· 23 pub subject: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub via: std::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 27 } 28 29 pub mod repost_state {
+4 -2
crates/weaver-api/src/app_bsky/feed/threadgate.rs
··· 440 /// List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply. 441 #[serde(skip_serializing_if = "std::option::Option::is_none")] 442 #[serde(borrow)] 443 - pub allow: Option<Vec<ThreadgateAllowItem<'a>>>, 444 pub created_at: jacquard_common::types::string::Datetime, 445 /// List of hidden reply URIs. 446 #[serde(skip_serializing_if = "std::option::Option::is_none")] 447 #[serde(borrow)] 448 - pub hidden_replies: Option<Vec<jacquard_common::types::string::AtUri<'a>>>, 449 /// Reference (AT-URI) to the post record. 450 #[serde(borrow)] 451 pub post: jacquard_common::types::string::AtUri<'a>,
··· 440 /// List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply. 441 #[serde(skip_serializing_if = "std::option::Option::is_none")] 442 #[serde(borrow)] 443 + pub allow: std::option::Option<Vec<ThreadgateAllowItem<'a>>>, 444 pub created_at: jacquard_common::types::string::Datetime, 445 /// List of hidden reply URIs. 446 #[serde(skip_serializing_if = "std::option::Option::is_none")] 447 #[serde(borrow)] 448 + pub hidden_replies: std::option::Option< 449 + Vec<jacquard_common::types::string::AtUri<'a>>, 450 + >, 451 /// Reference (AT-URI) to the post record. 452 #[serde(borrow)] 453 pub post: jacquard_common::types::string::AtUri<'a>,
+27 -23
crates/weaver-api/src/app_bsky/graph.rs
··· 1237 pub struct ListView<'a> { 1238 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1239 #[serde(borrow)] 1240 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 1241 #[serde(borrow)] 1242 pub cid: jacquard_common::types::string::Cid<'a>, 1243 #[serde(borrow)] 1244 pub creator: crate::app_bsky::actor::ProfileView<'a>, 1245 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1246 #[serde(borrow)] 1247 - pub description: Option<jacquard_common::CowStr<'a>>, 1248 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1249 #[serde(borrow)] 1250 - pub description_facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 1251 pub indexed_at: jacquard_common::types::string::Datetime, 1252 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1253 #[serde(borrow)] 1254 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 1255 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1256 - pub list_item_count: Option<i64>, 1257 #[serde(borrow)] 1258 pub name: jacquard_common::CowStr<'a>, 1259 #[serde(borrow)] ··· 1262 pub uri: jacquard_common::types::string::AtUri<'a>, 1263 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1264 #[serde(borrow)] 1265 - pub viewer: Option<crate::app_bsky::graph::ListViewerState<'a>>, 1266 } 1267 1268 pub mod list_view_state { ··· 1802 pub struct ListViewBasic<'a> { 1803 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1804 #[serde(borrow)] 1805 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 1806 #[serde(borrow)] 1807 pub cid: jacquard_common::types::string::Cid<'a>, 1808 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1809 - pub indexed_at: Option<jacquard_common::types::string::Datetime>, 1810 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1811 #[serde(borrow)] 1812 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 1813 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1814 - pub list_item_count: Option<i64>, 1815 #[serde(borrow)] 1816 pub name: jacquard_common::CowStr<'a>, 1817 #[serde(borrow)] ··· 1820 pub uri: jacquard_common::types::string::AtUri<'a>, 1821 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1822 #[serde(borrow)] 1823 - pub viewer: Option<crate::app_bsky::graph::ListViewerState<'a>>, 1824 } 1825 1826 pub mod list_view_basic_state { ··· 2473 /// if the actor is followed by this DID, contains the AT-URI of the follow record 2474 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2475 #[serde(borrow)] 2476 - pub followed_by: Option<jacquard_common::types::string::AtUri<'a>>, 2477 /// if the actor follows this DID, this is the AT-URI of the follow record 2478 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2479 #[serde(borrow)] 2480 - pub following: Option<jacquard_common::types::string::AtUri<'a>>, 2481 } 2482 2483 pub mod relationship_state { ··· 2664 pub creator: crate::app_bsky::actor::ProfileViewBasic<'a>, 2665 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2666 #[serde(borrow)] 2667 - pub feeds: Option<Vec<crate::app_bsky::feed::GeneratorView<'a>>>, 2668 pub indexed_at: jacquard_common::types::string::Datetime, 2669 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2670 - pub joined_all_time_count: Option<i64>, 2671 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2672 - pub joined_week_count: Option<i64>, 2673 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2674 #[serde(borrow)] 2675 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 2676 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2677 #[serde(borrow)] 2678 - pub list: Option<crate::app_bsky::graph::ListViewBasic<'a>>, 2679 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2680 #[serde(borrow)] 2681 - pub list_items_sample: Option<Vec<crate::app_bsky::graph::ListItemView<'a>>>, 2682 #[serde(borrow)] 2683 pub record: jacquard_common::types::value::Data<'a>, 2684 #[serde(borrow)] ··· 3155 pub creator: crate::app_bsky::actor::ProfileViewBasic<'a>, 3156 pub indexed_at: jacquard_common::types::string::Datetime, 3157 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3158 - pub joined_all_time_count: Option<i64>, 3159 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3160 - pub joined_week_count: Option<i64>, 3161 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3162 #[serde(borrow)] 3163 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 3164 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3165 - pub list_item_count: Option<i64>, 3166 #[serde(borrow)] 3167 pub record: jacquard_common::types::value::Data<'a>, 3168 #[serde(borrow)]
··· 1237 pub struct ListView<'a> { 1238 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1239 #[serde(borrow)] 1240 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 1241 #[serde(borrow)] 1242 pub cid: jacquard_common::types::string::Cid<'a>, 1243 #[serde(borrow)] 1244 pub creator: crate::app_bsky::actor::ProfileView<'a>, 1245 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1246 #[serde(borrow)] 1247 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 1248 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1249 #[serde(borrow)] 1250 + pub description_facets: std::option::Option< 1251 + Vec<crate::app_bsky::richtext::facet::Facet<'a>>, 1252 + >, 1253 pub indexed_at: jacquard_common::types::string::Datetime, 1254 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1255 #[serde(borrow)] 1256 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 1257 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1258 + pub list_item_count: std::option::Option<i64>, 1259 #[serde(borrow)] 1260 pub name: jacquard_common::CowStr<'a>, 1261 #[serde(borrow)] ··· 1264 pub uri: jacquard_common::types::string::AtUri<'a>, 1265 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1266 #[serde(borrow)] 1267 + pub viewer: std::option::Option<crate::app_bsky::graph::ListViewerState<'a>>, 1268 } 1269 1270 pub mod list_view_state { ··· 1804 pub struct ListViewBasic<'a> { 1805 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1806 #[serde(borrow)] 1807 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 1808 #[serde(borrow)] 1809 pub cid: jacquard_common::types::string::Cid<'a>, 1810 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1811 + pub indexed_at: std::option::Option<jacquard_common::types::string::Datetime>, 1812 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1813 #[serde(borrow)] 1814 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 1815 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1816 + pub list_item_count: std::option::Option<i64>, 1817 #[serde(borrow)] 1818 pub name: jacquard_common::CowStr<'a>, 1819 #[serde(borrow)] ··· 1822 pub uri: jacquard_common::types::string::AtUri<'a>, 1823 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1824 #[serde(borrow)] 1825 + pub viewer: std::option::Option<crate::app_bsky::graph::ListViewerState<'a>>, 1826 } 1827 1828 pub mod list_view_basic_state { ··· 2475 /// if the actor is followed by this DID, contains the AT-URI of the follow record 2476 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2477 #[serde(borrow)] 2478 + pub followed_by: std::option::Option<jacquard_common::types::string::AtUri<'a>>, 2479 /// if the actor follows this DID, this is the AT-URI of the follow record 2480 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2481 #[serde(borrow)] 2482 + pub following: std::option::Option<jacquard_common::types::string::AtUri<'a>>, 2483 } 2484 2485 pub mod relationship_state { ··· 2666 pub creator: crate::app_bsky::actor::ProfileViewBasic<'a>, 2667 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2668 #[serde(borrow)] 2669 + pub feeds: std::option::Option<Vec<crate::app_bsky::feed::GeneratorView<'a>>>, 2670 pub indexed_at: jacquard_common::types::string::Datetime, 2671 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2672 + pub joined_all_time_count: std::option::Option<i64>, 2673 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2674 + pub joined_week_count: std::option::Option<i64>, 2675 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2676 #[serde(borrow)] 2677 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 2678 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2679 #[serde(borrow)] 2680 + pub list: std::option::Option<crate::app_bsky::graph::ListViewBasic<'a>>, 2681 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2682 #[serde(borrow)] 2683 + pub list_items_sample: std::option::Option< 2684 + Vec<crate::app_bsky::graph::ListItemView<'a>>, 2685 + >, 2686 #[serde(borrow)] 2687 pub record: jacquard_common::types::value::Data<'a>, 2688 #[serde(borrow)] ··· 3159 pub creator: crate::app_bsky::actor::ProfileViewBasic<'a>, 3160 pub indexed_at: jacquard_common::types::string::Datetime, 3161 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3162 + pub joined_all_time_count: std::option::Option<i64>, 3163 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3164 + pub joined_week_count: std::option::Option<i64>, 3165 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3166 #[serde(borrow)] 3167 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 3168 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3169 + pub list_item_count: std::option::Option<i64>, 3170 #[serde(borrow)] 3171 pub record: jacquard_common::types::value::Data<'a>, 3172 #[serde(borrow)]
+1 -1
crates/weaver-api/src/app_bsky/graph/follow.rs
··· 23 pub subject: jacquard_common::types::string::Did<'a>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub via: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 27 } 28 29 pub mod follow_state {
··· 23 pub subject: jacquard_common::types::string::Did<'a>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub via: std::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 27 } 28 29 pub mod follow_state {
+1 -1
crates/weaver-api/src/app_bsky/graph/get_lists_with_membership.rs
··· 22 pub list: crate::app_bsky::graph::ListView<'a>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub list_item: Option<crate::app_bsky::graph::ListItemView<'a>>, 26 } 27 28 pub mod list_with_membership_state {
··· 22 pub list: crate::app_bsky::graph::ListView<'a>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub list_item: std::option::Option<crate::app_bsky::graph::ListItemView<'a>>, 26 } 27 28 pub mod list_with_membership_state {
+1 -1
crates/weaver-api/src/app_bsky/graph/get_starter_packs_with_membership.rs
··· 234 pub struct StarterPackWithMembership<'a> { 235 #[serde(skip_serializing_if = "std::option::Option::is_none")] 236 #[serde(borrow)] 237 - pub list_item: Option<crate::app_bsky::graph::ListItemView<'a>>, 238 #[serde(borrow)] 239 pub starter_pack: crate::app_bsky::graph::StarterPackView<'a>, 240 }
··· 234 pub struct StarterPackWithMembership<'a> { 235 #[serde(skip_serializing_if = "std::option::Option::is_none")] 236 #[serde(borrow)] 237 + pub list_item: std::option::Option<crate::app_bsky::graph::ListItemView<'a>>, 238 #[serde(borrow)] 239 pub starter_pack: crate::app_bsky::graph::StarterPackView<'a>, 240 }
+6 -4
crates/weaver-api/src/app_bsky/graph/list.rs
··· 20 pub struct List<'a> { 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 - pub avatar: Option<jacquard_common::types::blob::BlobRef<'a>>, 24 pub created_at: jacquard_common::types::string::Datetime, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 - pub description: Option<jacquard_common::CowStr<'a>>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 - pub description_facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 - pub labels: Option<crate::com_atproto::label::SelfLabels<'a>>, 34 /// Display name for list; can not be empty. 35 #[serde(borrow)] 36 pub name: jacquard_common::CowStr<'a>,
··· 20 pub struct List<'a> { 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 + pub avatar: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 24 pub created_at: jacquard_common::types::string::Datetime, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 + pub description_facets: std::option::Option< 31 + Vec<crate::app_bsky::richtext::facet::Facet<'a>>, 32 + >, 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 #[serde(borrow)] 35 + pub labels: std::option::Option<crate::com_atproto::label::SelfLabels<'a>>, 36 /// Display name for list; can not be empty. 37 #[serde(borrow)] 38 pub name: jacquard_common::CowStr<'a>,
+7 -3
crates/weaver-api/src/app_bsky/graph/starterpack.rs
··· 337 pub created_at: jacquard_common::types::string::Datetime, 338 #[serde(skip_serializing_if = "std::option::Option::is_none")] 339 #[serde(borrow)] 340 - pub description: Option<jacquard_common::CowStr<'a>>, 341 #[serde(skip_serializing_if = "std::option::Option::is_none")] 342 #[serde(borrow)] 343 - pub description_facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 344 #[serde(skip_serializing_if = "std::option::Option::is_none")] 345 #[serde(borrow)] 346 - pub feeds: Option<Vec<crate::app_bsky::graph::starterpack::FeedItem<'a>>>, 347 /// Reference (AT-URI) to the list record. 348 #[serde(borrow)] 349 pub list: jacquard_common::types::string::AtUri<'a>,
··· 337 pub created_at: jacquard_common::types::string::Datetime, 338 #[serde(skip_serializing_if = "std::option::Option::is_none")] 339 #[serde(borrow)] 340 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 341 #[serde(skip_serializing_if = "std::option::Option::is_none")] 342 #[serde(borrow)] 343 + pub description_facets: std::option::Option< 344 + Vec<crate::app_bsky::richtext::facet::Facet<'a>>, 345 + >, 346 #[serde(skip_serializing_if = "std::option::Option::is_none")] 347 #[serde(borrow)] 348 + pub feeds: std::option::Option< 349 + Vec<crate::app_bsky::graph::starterpack::FeedItem<'a>>, 350 + >, 351 /// Reference (AT-URI) to the list record. 352 #[serde(borrow)] 353 pub list: jacquard_common::types::string::AtUri<'a>,
+16 -10
crates/weaver-api/src/app_bsky/labeler.rs
··· 23 /// Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub label_value_definitions: Option< 27 Vec<crate::com_atproto::label::LabelValueDefinition<'a>>, 28 >, 29 /// The label values which this labeler publishes. May include global or custom labels. ··· 612 pub indexed_at: jacquard_common::types::string::Datetime, 613 #[serde(skip_serializing_if = "std::option::Option::is_none")] 614 #[serde(borrow)] 615 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 616 #[serde(skip_serializing_if = "std::option::Option::is_none")] 617 - pub like_count: Option<i64>, 618 #[serde(borrow)] 619 pub uri: jacquard_common::types::string::AtUri<'a>, 620 #[serde(skip_serializing_if = "std::option::Option::is_none")] 621 #[serde(borrow)] 622 - pub viewer: Option<crate::app_bsky::labeler::LabelerViewerState<'a>>, 623 } 624 625 pub mod labeler_view_state { ··· 945 pub indexed_at: jacquard_common::types::string::Datetime, 946 #[serde(skip_serializing_if = "std::option::Option::is_none")] 947 #[serde(borrow)] 948 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 949 #[serde(skip_serializing_if = "std::option::Option::is_none")] 950 - pub like_count: Option<i64>, 951 #[serde(borrow)] 952 pub policies: crate::app_bsky::labeler::LabelerPolicies<'a>, 953 /// The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. 954 #[serde(skip_serializing_if = "std::option::Option::is_none")] 955 #[serde(borrow)] 956 - pub reason_types: Option<Vec<crate::com_atproto::moderation::ReasonType<'a>>>, 957 /// Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. 958 #[serde(skip_serializing_if = "std::option::Option::is_none")] 959 #[serde(borrow)] 960 - pub subject_collections: Option<Vec<jacquard_common::types::string::Nsid<'a>>>, 961 /// The set of subject types (account, record, etc) this service accepts reports on. 962 #[serde(skip_serializing_if = "std::option::Option::is_none")] 963 #[serde(borrow)] 964 - pub subject_types: Option<Vec<crate::com_atproto::moderation::SubjectType<'a>>>, 965 #[serde(borrow)] 966 pub uri: jacquard_common::types::string::AtUri<'a>, 967 #[serde(skip_serializing_if = "std::option::Option::is_none")] 968 #[serde(borrow)] 969 - pub viewer: Option<crate::app_bsky::labeler::LabelerViewerState<'a>>, 970 } 971 972 pub mod labeler_view_detailed_state {
··· 23 /// Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub label_value_definitions: std::option::Option< 27 Vec<crate::com_atproto::label::LabelValueDefinition<'a>>, 28 >, 29 /// The label values which this labeler publishes. May include global or custom labels. ··· 612 pub indexed_at: jacquard_common::types::string::Datetime, 613 #[serde(skip_serializing_if = "std::option::Option::is_none")] 614 #[serde(borrow)] 615 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 616 #[serde(skip_serializing_if = "std::option::Option::is_none")] 617 + pub like_count: std::option::Option<i64>, 618 #[serde(borrow)] 619 pub uri: jacquard_common::types::string::AtUri<'a>, 620 #[serde(skip_serializing_if = "std::option::Option::is_none")] 621 #[serde(borrow)] 622 + pub viewer: std::option::Option<crate::app_bsky::labeler::LabelerViewerState<'a>>, 623 } 624 625 pub mod labeler_view_state { ··· 945 pub indexed_at: jacquard_common::types::string::Datetime, 946 #[serde(skip_serializing_if = "std::option::Option::is_none")] 947 #[serde(borrow)] 948 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 949 #[serde(skip_serializing_if = "std::option::Option::is_none")] 950 + pub like_count: std::option::Option<i64>, 951 #[serde(borrow)] 952 pub policies: crate::app_bsky::labeler::LabelerPolicies<'a>, 953 /// The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. 954 #[serde(skip_serializing_if = "std::option::Option::is_none")] 955 #[serde(borrow)] 956 + pub reason_types: std::option::Option< 957 + Vec<crate::com_atproto::moderation::ReasonType<'a>>, 958 + >, 959 /// Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. 960 #[serde(skip_serializing_if = "std::option::Option::is_none")] 961 #[serde(borrow)] 962 + pub subject_collections: std::option::Option< 963 + Vec<jacquard_common::types::string::Nsid<'a>>, 964 + >, 965 /// The set of subject types (account, record, etc) this service accepts reports on. 966 #[serde(skip_serializing_if = "std::option::Option::is_none")] 967 #[serde(borrow)] 968 + pub subject_types: std::option::Option< 969 + Vec<crate::com_atproto::moderation::SubjectType<'a>>, 970 + >, 971 #[serde(borrow)] 972 pub uri: jacquard_common::types::string::AtUri<'a>, 973 #[serde(skip_serializing_if = "std::option::Option::is_none")] 974 #[serde(borrow)] 975 + pub viewer: std::option::Option<crate::app_bsky::labeler::LabelerViewerState<'a>>, 976 } 977 978 pub mod labeler_view_detailed_state {
+10 -4
crates/weaver-api/src/app_bsky/labeler/service.rs
··· 21 pub created_at: jacquard_common::types::string::Datetime, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 - pub labels: Option<crate::com_atproto::label::SelfLabels<'a>>, 25 #[serde(borrow)] 26 pub policies: crate::app_bsky::labeler::LabelerPolicies<'a>, 27 /// The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 - pub reason_types: Option<Vec<crate::com_atproto::moderation::ReasonType<'a>>>, 31 /// Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 - pub subject_collections: Option<Vec<jacquard_common::types::string::Nsid<'a>>>, 35 /// The set of subject types (account, record, etc) this service accepts reports on. 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 #[serde(borrow)] 38 - pub subject_types: Option<Vec<crate::com_atproto::moderation::SubjectType<'a>>>, 39 } 40 41 pub mod service_state {
··· 21 pub created_at: jacquard_common::types::string::Datetime, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 + pub labels: std::option::Option<crate::com_atproto::label::SelfLabels<'a>>, 25 #[serde(borrow)] 26 pub policies: crate::app_bsky::labeler::LabelerPolicies<'a>, 27 /// The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 + pub reason_types: std::option::Option< 31 + Vec<crate::com_atproto::moderation::ReasonType<'a>>, 32 + >, 33 /// Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub subject_collections: std::option::Option< 37 + Vec<jacquard_common::types::string::Nsid<'a>>, 38 + >, 39 /// The set of subject types (account, record, etc) this service accepts reports on. 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 + pub subject_types: std::option::Option< 43 + Vec<crate::com_atproto::moderation::SubjectType<'a>>, 44 + >, 45 } 46 47 pub mod service_state {
+2 -2
crates/weaver-api/src/app_bsky/notification/list_notifications.rs
··· 248 pub is_read: bool, 249 #[serde(skip_serializing_if = "std::option::Option::is_none")] 250 #[serde(borrow)] 251 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 252 /// The reason why this notification was delivered - e.g. your post was liked, or you received a new follower. 253 #[serde(borrow)] 254 pub reason: jacquard_common::CowStr<'a>, 255 #[serde(skip_serializing_if = "std::option::Option::is_none")] 256 #[serde(borrow)] 257 - pub reason_subject: Option<jacquard_common::types::string::AtUri<'a>>, 258 #[serde(borrow)] 259 pub record: jacquard_common::types::value::Data<'a>, 260 #[serde(borrow)]
··· 248 pub is_read: bool, 249 #[serde(skip_serializing_if = "std::option::Option::is_none")] 250 #[serde(borrow)] 251 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 252 /// The reason why this notification was delivered - e.g. your post was liked, or you received a new follower. 253 #[serde(borrow)] 254 pub reason: jacquard_common::CowStr<'a>, 255 #[serde(skip_serializing_if = "std::option::Option::is_none")] 256 #[serde(borrow)] 257 + pub reason_subject: std::option::Option<jacquard_common::types::string::AtUri<'a>>, 258 #[serde(borrow)] 259 pub record: jacquard_common::types::value::Data<'a>, 260 #[serde(borrow)]
+1 -1
crates/weaver-api/src/app_bsky/notification/register_push.rs
··· 19 pub struct RegisterPush<'a> { 20 /// Set to true when the actor is age restricted 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 - pub age_restricted: Option<bool>, 23 #[serde(borrow)] 24 pub app_id: jacquard_common::CowStr<'a>, 25 #[serde(borrow)]
··· 19 pub struct RegisterPush<'a> { 20 /// Set to true when the actor is age restricted 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 + pub age_restricted: std::option::Option<bool>, 23 #[serde(borrow)] 24 pub app_id: jacquard_common::CowStr<'a>, 25 #[serde(borrow)]
+9 -9
crates/weaver-api/src/app_bsky/unspecced.rs
··· 47 /// The IP address used when completing the AA flow. 48 #[serde(skip_serializing_if = "std::option::Option::is_none")] 49 #[serde(borrow)] 50 - pub complete_ip: Option<jacquard_common::CowStr<'a>>, 51 /// The user agent used when completing the AA flow. 52 #[serde(skip_serializing_if = "std::option::Option::is_none")] 53 #[serde(borrow)] 54 - pub complete_ua: Option<jacquard_common::CowStr<'a>>, 55 /// The date and time of this write operation. 56 pub created_at: jacquard_common::types::string::Datetime, 57 /// The email used for AA. 58 #[serde(skip_serializing_if = "std::option::Option::is_none")] 59 #[serde(borrow)] 60 - pub email: Option<jacquard_common::CowStr<'a>>, 61 /// The IP address used when initiating the AA flow. 62 #[serde(skip_serializing_if = "std::option::Option::is_none")] 63 #[serde(borrow)] 64 - pub init_ip: Option<jacquard_common::CowStr<'a>>, 65 /// The user agent used when initiating the AA flow. 66 #[serde(skip_serializing_if = "std::option::Option::is_none")] 67 #[serde(borrow)] 68 - pub init_ua: Option<jacquard_common::CowStr<'a>>, 69 /// The status of the age assurance process. 70 #[serde(borrow)] 71 pub status: jacquard_common::CowStr<'a>, ··· 1717 pub struct SkeletonTrend<'a> { 1718 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1719 #[serde(borrow)] 1720 - pub category: Option<jacquard_common::CowStr<'a>>, 1721 #[serde(borrow)] 1722 pub dids: Vec<jacquard_common::types::string::Did<'a>>, 1723 #[serde(borrow)] ··· 1728 pub started_at: jacquard_common::types::string::Datetime, 1729 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1730 #[serde(borrow)] 1731 - pub status: Option<jacquard_common::CowStr<'a>>, 1732 #[serde(borrow)] 1733 pub topic: jacquard_common::CowStr<'a>, 1734 } ··· 2645 pub actors: Vec<crate::app_bsky::actor::ProfileViewBasic<'a>>, 2646 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2647 #[serde(borrow)] 2648 - pub category: Option<jacquard_common::CowStr<'a>>, 2649 #[serde(borrow)] 2650 pub display_name: jacquard_common::CowStr<'a>, 2651 #[serde(borrow)] ··· 2654 pub started_at: jacquard_common::types::string::Datetime, 2655 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2656 #[serde(borrow)] 2657 - pub status: Option<jacquard_common::CowStr<'a>>, 2658 #[serde(borrow)] 2659 pub topic: jacquard_common::CowStr<'a>, 2660 }
··· 47 /// The IP address used when completing the AA flow. 48 #[serde(skip_serializing_if = "std::option::Option::is_none")] 49 #[serde(borrow)] 50 + pub complete_ip: std::option::Option<jacquard_common::CowStr<'a>>, 51 /// The user agent used when completing the AA flow. 52 #[serde(skip_serializing_if = "std::option::Option::is_none")] 53 #[serde(borrow)] 54 + pub complete_ua: std::option::Option<jacquard_common::CowStr<'a>>, 55 /// The date and time of this write operation. 56 pub created_at: jacquard_common::types::string::Datetime, 57 /// The email used for AA. 58 #[serde(skip_serializing_if = "std::option::Option::is_none")] 59 #[serde(borrow)] 60 + pub email: std::option::Option<jacquard_common::CowStr<'a>>, 61 /// The IP address used when initiating the AA flow. 62 #[serde(skip_serializing_if = "std::option::Option::is_none")] 63 #[serde(borrow)] 64 + pub init_ip: std::option::Option<jacquard_common::CowStr<'a>>, 65 /// The user agent used when initiating the AA flow. 66 #[serde(skip_serializing_if = "std::option::Option::is_none")] 67 #[serde(borrow)] 68 + pub init_ua: std::option::Option<jacquard_common::CowStr<'a>>, 69 /// The status of the age assurance process. 70 #[serde(borrow)] 71 pub status: jacquard_common::CowStr<'a>, ··· 1717 pub struct SkeletonTrend<'a> { 1718 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1719 #[serde(borrow)] 1720 + pub category: std::option::Option<jacquard_common::CowStr<'a>>, 1721 #[serde(borrow)] 1722 pub dids: Vec<jacquard_common::types::string::Did<'a>>, 1723 #[serde(borrow)] ··· 1728 pub started_at: jacquard_common::types::string::Datetime, 1729 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1730 #[serde(borrow)] 1731 + pub status: std::option::Option<jacquard_common::CowStr<'a>>, 1732 #[serde(borrow)] 1733 pub topic: jacquard_common::CowStr<'a>, 1734 } ··· 2645 pub actors: Vec<crate::app_bsky::actor::ProfileViewBasic<'a>>, 2646 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2647 #[serde(borrow)] 2648 + pub category: std::option::Option<jacquard_common::CowStr<'a>>, 2649 #[serde(borrow)] 2650 pub display_name: jacquard_common::CowStr<'a>, 2651 #[serde(borrow)] ··· 2654 pub started_at: jacquard_common::types::string::Datetime, 2655 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2656 #[serde(borrow)] 2657 + pub status: std::option::Option<jacquard_common::CowStr<'a>>, 2658 #[serde(borrow)] 2659 pub topic: jacquard_common::CowStr<'a>, 2660 }
+4 -4
crates/weaver-api/src/app_bsky/video.rs
··· 23 pub struct JobStatus<'a> { 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub blob: Option<jacquard_common::types::blob::BlobRef<'a>>, 27 #[serde(borrow)] 28 pub did: jacquard_common::types::string::Did<'a>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 #[serde(borrow)] 31 - pub error: Option<jacquard_common::CowStr<'a>>, 32 #[serde(borrow)] 33 pub job_id: jacquard_common::CowStr<'a>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 - pub message: Option<jacquard_common::CowStr<'a>>, 37 /// Progress within the current processing state. 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 - pub progress: Option<i64>, 40 /// The state of the video processing job. All values not listed as a known value indicate that the job is in process. 41 #[serde(borrow)] 42 pub state: jacquard_common::CowStr<'a>,
··· 23 pub struct JobStatus<'a> { 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub blob: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 27 #[serde(borrow)] 28 pub did: jacquard_common::types::string::Did<'a>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 #[serde(borrow)] 31 + pub error: std::option::Option<jacquard_common::CowStr<'a>>, 32 #[serde(borrow)] 33 pub job_id: jacquard_common::CowStr<'a>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub message: std::option::Option<jacquard_common::CowStr<'a>>, 37 /// Progress within the current processing state. 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 + pub progress: std::option::Option<i64>, 40 /// The state of the video processing job. All values not listed as a known value indicate that the job is in process. 41 #[serde(borrow)] 42 pub state: jacquard_common::CowStr<'a>,
+7 -7
crates/weaver-api/src/chat_bsky/actor.rs
··· 23 pub struct ProfileViewBasic<'a> { 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub associated: Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 30 /// Set to true when the actor cannot actively participate in conversations 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 - pub chat_disabled: Option<bool>, 33 #[serde(borrow)] 34 pub did: jacquard_common::types::string::Did<'a>, 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 - pub display_name: Option<jacquard_common::CowStr<'a>>, 38 #[serde(borrow)] 39 pub handle: jacquard_common::types::string::Handle<'a>, 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 #[serde(borrow)] 45 - pub verification: Option<crate::app_bsky::actor::VerificationState<'a>>, 46 #[serde(skip_serializing_if = "std::option::Option::is_none")] 47 #[serde(borrow)] 48 - pub viewer: Option<crate::app_bsky::actor::ViewerState<'a>>, 49 } 50 51 pub mod profile_view_basic_state {
··· 23 pub struct ProfileViewBasic<'a> { 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub associated: std::option::Option<crate::app_bsky::actor::ProfileAssociated<'a>>, 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 30 /// Set to true when the actor cannot actively participate in conversations 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 + pub chat_disabled: std::option::Option<bool>, 33 #[serde(borrow)] 34 pub did: jacquard_common::types::string::Did<'a>, 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 38 #[serde(borrow)] 39 pub handle: jacquard_common::types::string::Handle<'a>, 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 #[serde(borrow)] 45 + pub verification: std::option::Option<crate::app_bsky::actor::VerificationState<'a>>, 46 #[serde(skip_serializing_if = "std::option::Option::is_none")] 47 #[serde(borrow)] 48 + pub viewer: std::option::Option<crate::app_bsky::actor::ViewerState<'a>>, 49 } 50 51 pub mod profile_view_basic_state {
+8 -6
crates/weaver-api/src/chat_bsky/convo.rs
··· 39 pub id: jacquard_common::CowStr<'a>, 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 - pub last_message: Option<ConvoViewLastMessage<'a>>, 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 #[serde(borrow)] 45 - pub last_reaction: Option<crate::chat_bsky::convo::MessageAndReactionView<'a>>, 46 #[serde(borrow)] 47 pub members: Vec<crate::chat_bsky::actor::ProfileViewBasic<'a>>, 48 pub muted: bool, ··· 50 pub rev: jacquard_common::CowStr<'a>, 51 #[serde(skip_serializing_if = "std::option::Option::is_none")] 52 #[serde(borrow)] 53 - pub status: Option<jacquard_common::CowStr<'a>>, 54 pub unread_count: i64, 55 } 56 ··· 3781 pub struct MessageView<'a> { 3782 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3783 #[serde(borrow)] 3784 - pub embed: Option<crate::app_bsky::embed::record::View<'a>>, 3785 /// Annotations of text (mentions, URLs, hashtags, etc) 3786 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3787 #[serde(borrow)] 3788 - pub facets: Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 3789 #[serde(borrow)] 3790 pub id: jacquard_common::CowStr<'a>, 3791 /// Reactions to this message, in ascending order of creation time. 3792 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3793 #[serde(borrow)] 3794 - pub reactions: Option<Vec<crate::chat_bsky::convo::ReactionView<'a>>>, 3795 #[serde(borrow)] 3796 pub rev: jacquard_common::CowStr<'a>, 3797 #[serde(borrow)]
··· 39 pub id: jacquard_common::CowStr<'a>, 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 + pub last_message: std::option::Option<ConvoViewLastMessage<'a>>, 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 #[serde(borrow)] 45 + pub last_reaction: std::option::Option< 46 + crate::chat_bsky::convo::MessageAndReactionView<'a>, 47 + >, 48 #[serde(borrow)] 49 pub members: Vec<crate::chat_bsky::actor::ProfileViewBasic<'a>>, 50 pub muted: bool, ··· 52 pub rev: jacquard_common::CowStr<'a>, 53 #[serde(skip_serializing_if = "std::option::Option::is_none")] 54 #[serde(borrow)] 55 + pub status: std::option::Option<jacquard_common::CowStr<'a>>, 56 pub unread_count: i64, 57 } 58 ··· 3783 pub struct MessageView<'a> { 3784 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3785 #[serde(borrow)] 3786 + pub embed: std::option::Option<crate::app_bsky::embed::record::View<'a>>, 3787 /// Annotations of text (mentions, URLs, hashtags, etc) 3788 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3789 #[serde(borrow)] 3790 + pub facets: std::option::Option<Vec<crate::app_bsky::richtext::facet::Facet<'a>>>, 3791 #[serde(borrow)] 3792 pub id: jacquard_common::CowStr<'a>, 3793 /// Reactions to this message, in ascending order of creation time. 3794 #[serde(skip_serializing_if = "std::option::Option::is_none")] 3795 #[serde(borrow)] 3796 + pub reactions: std::option::Option<Vec<crate::chat_bsky::convo::ReactionView<'a>>>, 3797 #[serde(borrow)] 3798 pub rev: jacquard_common::CowStr<'a>, 3799 #[serde(borrow)]
+1 -1
crates/weaver-api/src/chat_bsky/moderation/update_actor_access.rs
··· 22 pub allow_access: bool, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub r#ref: Option<jacquard_common::CowStr<'a>>, 26 } 27 28 pub mod update_actor_access_state {
··· 22 pub allow_access: bool, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub r#ref: std::option::Option<jacquard_common::CowStr<'a>>, 26 } 27 28 pub mod update_actor_access_state {
+17 -11
crates/weaver-api/src/com_atproto/admin.rs
··· 34 #[serde(rename_all = "camelCase")] 35 pub struct AccountView<'a> { 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 - pub deactivated_at: Option<jacquard_common::types::string::Datetime>, 38 #[serde(borrow)] 39 pub did: jacquard_common::types::string::Did<'a>, 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 - pub email: Option<jacquard_common::CowStr<'a>>, 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 - pub email_confirmed_at: Option<jacquard_common::types::string::Datetime>, 45 #[serde(borrow)] 46 pub handle: jacquard_common::types::string::Handle<'a>, 47 pub indexed_at: jacquard_common::types::string::Datetime, 48 #[serde(skip_serializing_if = "std::option::Option::is_none")] 49 #[serde(borrow)] 50 - pub invite_note: Option<jacquard_common::CowStr<'a>>, 51 #[serde(skip_serializing_if = "std::option::Option::is_none")] 52 #[serde(borrow)] 53 - pub invited_by: Option<crate::com_atproto::server::InviteCode<'a>>, 54 #[serde(skip_serializing_if = "std::option::Option::is_none")] 55 #[serde(borrow)] 56 - pub invites: Option<Vec<crate::com_atproto::server::InviteCode<'a>>>, 57 #[serde(skip_serializing_if = "std::option::Option::is_none")] 58 - pub invites_disabled: Option<bool>, 59 #[serde(skip_serializing_if = "std::option::Option::is_none")] 60 #[serde(borrow)] 61 - pub related_records: Option<Vec<jacquard_common::types::value::Data<'a>>>, 62 #[serde(skip_serializing_if = "std::option::Option::is_none")] 63 #[serde(borrow)] 64 - pub threat_signatures: Option<Vec<crate::com_atproto::admin::ThreatSignature<'a>>>, 65 } 66 67 pub mod account_view_state { ··· 888 pub did: jacquard_common::types::string::Did<'a>, 889 #[serde(skip_serializing_if = "std::option::Option::is_none")] 890 #[serde(borrow)] 891 - pub record_uri: Option<jacquard_common::types::string::AtUri<'a>>, 892 } 893 894 pub mod repo_blob_ref_state { ··· 1223 pub applied: bool, 1224 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1225 #[serde(borrow)] 1226 - pub r#ref: Option<jacquard_common::CowStr<'a>>, 1227 } 1228 1229 pub mod status_attr_state {
··· 34 #[serde(rename_all = "camelCase")] 35 pub struct AccountView<'a> { 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 + pub deactivated_at: std::option::Option<jacquard_common::types::string::Datetime>, 38 #[serde(borrow)] 39 pub did: jacquard_common::types::string::Did<'a>, 40 #[serde(skip_serializing_if = "std::option::Option::is_none")] 41 #[serde(borrow)] 42 + pub email: std::option::Option<jacquard_common::CowStr<'a>>, 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 + pub email_confirmed_at: std::option::Option< 45 + jacquard_common::types::string::Datetime, 46 + >, 47 #[serde(borrow)] 48 pub handle: jacquard_common::types::string::Handle<'a>, 49 pub indexed_at: jacquard_common::types::string::Datetime, 50 #[serde(skip_serializing_if = "std::option::Option::is_none")] 51 #[serde(borrow)] 52 + pub invite_note: std::option::Option<jacquard_common::CowStr<'a>>, 53 #[serde(skip_serializing_if = "std::option::Option::is_none")] 54 #[serde(borrow)] 55 + pub invited_by: std::option::Option<crate::com_atproto::server::InviteCode<'a>>, 56 #[serde(skip_serializing_if = "std::option::Option::is_none")] 57 #[serde(borrow)] 58 + pub invites: std::option::Option<Vec<crate::com_atproto::server::InviteCode<'a>>>, 59 #[serde(skip_serializing_if = "std::option::Option::is_none")] 60 + pub invites_disabled: std::option::Option<bool>, 61 #[serde(skip_serializing_if = "std::option::Option::is_none")] 62 #[serde(borrow)] 63 + pub related_records: std::option::Option< 64 + Vec<jacquard_common::types::value::Data<'a>>, 65 + >, 66 #[serde(skip_serializing_if = "std::option::Option::is_none")] 67 #[serde(borrow)] 68 + pub threat_signatures: std::option::Option< 69 + Vec<crate::com_atproto::admin::ThreatSignature<'a>>, 70 + >, 71 } 72 73 pub mod account_view_state { ··· 894 pub did: jacquard_common::types::string::Did<'a>, 895 #[serde(skip_serializing_if = "std::option::Option::is_none")] 896 #[serde(borrow)] 897 + pub record_uri: std::option::Option<jacquard_common::types::string::AtUri<'a>>, 898 } 899 900 pub mod repo_blob_ref_state { ··· 1229 pub applied: bool, 1230 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1231 #[serde(borrow)] 1232 + pub r#ref: std::option::Option<jacquard_common::CowStr<'a>>, 1233 } 1234 1235 pub mod status_attr_state {
+1 -1
crates/weaver-api/src/com_atproto/admin/disable_account_invites.rs
··· 22 /// Optional reason for disabled invites. 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub note: Option<jacquard_common::CowStr<'a>>, 26 } 27 28 pub mod disable_account_invites_state {
··· 22 /// Optional reason for disabled invites. 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub note: std::option::Option<jacquard_common::CowStr<'a>>, 26 } 27 28 pub mod disable_account_invites_state {
+1 -1
crates/weaver-api/src/com_atproto/admin/enable_account_invites.rs
··· 22 /// Optional reason for enabled invites. 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub note: Option<jacquard_common::CowStr<'a>>, 26 } 27 28 pub mod enable_account_invites_state {
··· 22 /// Optional reason for enabled invites. 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub note: std::option::Option<jacquard_common::CowStr<'a>>, 26 } 27 28 pub mod enable_account_invites_state {
+2 -2
crates/weaver-api/src/com_atproto/admin/send_email.rs
··· 20 /// Additional comment by the sender that won't be used in the email itself but helpful to provide more context for moderators/reviewers 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 - pub comment: Option<jacquard_common::CowStr<'a>>, 24 #[serde(borrow)] 25 pub content: jacquard_common::CowStr<'a>, 26 #[serde(borrow)] ··· 29 pub sender_did: jacquard_common::types::string::Did<'a>, 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 #[serde(borrow)] 32 - pub subject: Option<jacquard_common::CowStr<'a>>, 33 } 34 35 pub mod send_email_state {
··· 20 /// Additional comment by the sender that won't be used in the email itself but helpful to provide more context for moderators/reviewers 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 24 #[serde(borrow)] 25 pub content: jacquard_common::CowStr<'a>, 26 #[serde(borrow)] ··· 29 pub sender_did: jacquard_common::types::string::Did<'a>, 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 #[serde(borrow)] 32 + pub subject: std::option::Option<jacquard_common::CowStr<'a>>, 33 } 34 35 pub mod send_email_state {
+2 -2
crates/weaver-api/src/com_atproto/admin/update_subject_status.rs
··· 19 pub struct UpdateSubjectStatus<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 - pub deactivated: Option<crate::com_atproto::admin::StatusAttr<'a>>, 23 #[serde(borrow)] 24 pub subject: UpdateSubjectStatusSubject<'a>, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 - pub takedown: Option<crate::com_atproto::admin::StatusAttr<'a>>, 28 } 29 30 pub mod update_subject_status_state {
··· 19 pub struct UpdateSubjectStatus<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 + pub deactivated: std::option::Option<crate::com_atproto::admin::StatusAttr<'a>>, 23 #[serde(borrow)] 24 pub subject: UpdateSubjectStatusSubject<'a>, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 + pub takedown: std::option::Option<crate::com_atproto::admin::StatusAttr<'a>>, 28 } 29 30 pub mod update_subject_status_state {
+7 -7
crates/weaver-api/src/com_atproto/label.rs
··· 26 /// Optionally, CID specifying the specific version of 'uri' resource this label applies to. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub cid: Option<jacquard_common::types::string::Cid<'a>>, 30 /// Timestamp when this label was created. 31 pub cts: jacquard_common::types::string::Datetime, 32 /// Timestamp at which this label expires (no longer applies). 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 - pub exp: Option<jacquard_common::types::string::Datetime>, 35 /// If true, this is a negation label, overwriting a previous label. 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 - pub neg: Option<bool>, 38 /// Signature of dag-cbor encoded label. 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 - pub sig: Option<bytes::Bytes>, 41 /// DID of the actor who created this label. 42 #[serde(borrow)] 43 pub src: jacquard_common::types::string::Did<'a>, ··· 49 pub val: jacquard_common::CowStr<'a>, 50 /// The AT Protocol version of the label object. 51 #[serde(skip_serializing_if = "std::option::Option::is_none")] 52 - pub ver: Option<i64>, 53 } 54 55 pub mod label_state { ··· 1024 pub struct LabelValueDefinition<'a> { 1025 /// Does the user need to have adult content enabled in order to configure this label? 1026 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1027 - pub adult_only: Option<bool>, 1028 /// What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. 1029 #[serde(borrow)] 1030 pub blurs: jacquard_common::CowStr<'a>, 1031 /// The default setting for this label. 1032 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1033 #[serde(borrow)] 1034 - pub default_setting: Option<jacquard_common::CowStr<'a>>, 1035 /// The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). 1036 #[serde(borrow)] 1037 pub identifier: jacquard_common::CowStr<'a>,
··· 26 /// Optionally, CID specifying the specific version of 'uri' resource this label applies to. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 30 /// Timestamp when this label was created. 31 pub cts: jacquard_common::types::string::Datetime, 32 /// Timestamp at which this label expires (no longer applies). 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 + pub exp: std::option::Option<jacquard_common::types::string::Datetime>, 35 /// If true, this is a negation label, overwriting a previous label. 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 + pub neg: std::option::Option<bool>, 38 /// Signature of dag-cbor encoded label. 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 + pub sig: std::option::Option<bytes::Bytes>, 41 /// DID of the actor who created this label. 42 #[serde(borrow)] 43 pub src: jacquard_common::types::string::Did<'a>, ··· 49 pub val: jacquard_common::CowStr<'a>, 50 /// The AT Protocol version of the label object. 51 #[serde(skip_serializing_if = "std::option::Option::is_none")] 52 + pub ver: std::option::Option<i64>, 53 } 54 55 pub mod label_state { ··· 1024 pub struct LabelValueDefinition<'a> { 1025 /// Does the user need to have adult content enabled in order to configure this label? 1026 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1027 + pub adult_only: std::option::Option<bool>, 1028 /// What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. 1029 #[serde(borrow)] 1030 pub blurs: jacquard_common::CowStr<'a>, 1031 /// The default setting for this label. 1032 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1033 #[serde(borrow)] 1034 + pub default_setting: std::option::Option<jacquard_common::CowStr<'a>>, 1035 /// The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). 1036 #[serde(borrow)] 1037 pub identifier: jacquard_common::CowStr<'a>,
+4 -2
crates/weaver-api/src/com_atproto/moderation/create_report.rs
··· 19 pub struct CreateReport<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 - pub mod_tool: Option<crate::com_atproto::moderation::create_report::ModTool<'a>>, 23 /// Additional context about the content and violation. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub reason: Option<jacquard_common::CowStr<'a>>, 27 /// Indicates the broad category of violation the report is for. 28 #[serde(borrow)] 29 pub reason_type: crate::com_atproto::moderation::ReasonType<'a>,
··· 19 pub struct CreateReport<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 + pub mod_tool: std::option::Option< 23 + crate::com_atproto::moderation::create_report::ModTool<'a>, 24 + >, 25 /// Additional context about the content and violation. 26 #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 #[serde(borrow)] 28 + pub reason: std::option::Option<jacquard_common::CowStr<'a>>, 29 /// Indicates the broad category of violation the report is for. 30 #[serde(borrow)] 31 pub reason_type: crate::com_atproto::moderation::ReasonType<'a>,
+5 -5
crates/weaver-api/src/com_atproto/repo/apply_writes.rs
··· 23 /// NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub rkey: Option< 27 jacquard_common::types::string::RecordKey< 28 jacquard_common::types::string::Rkey<'a>, 29 >, ··· 715 pub uri: jacquard_common::types::string::AtUri<'a>, 716 #[serde(skip_serializing_if = "std::option::Option::is_none")] 717 #[serde(borrow)] 718 - pub validation_status: Option<jacquard_common::CowStr<'a>>, 719 } 720 721 pub mod create_result_state { ··· 1131 /// If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. 1132 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1133 #[serde(borrow)] 1134 - pub swap_commit: Option<jacquard_common::types::string::Cid<'a>>, 1135 /// Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons. 1136 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1137 - pub validate: Option<bool>, 1138 #[serde(borrow)] 1139 pub writes: Vec<ApplyWritesWritesItem<'a>>, 1140 } ··· 1686 pub uri: jacquard_common::types::string::AtUri<'a>, 1687 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1688 #[serde(borrow)] 1689 - pub validation_status: Option<jacquard_common::CowStr<'a>>, 1690 } 1691 1692 pub mod update_result_state {
··· 23 /// NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub rkey: std::option::Option< 27 jacquard_common::types::string::RecordKey< 28 jacquard_common::types::string::Rkey<'a>, 29 >, ··· 715 pub uri: jacquard_common::types::string::AtUri<'a>, 716 #[serde(skip_serializing_if = "std::option::Option::is_none")] 717 #[serde(borrow)] 718 + pub validation_status: std::option::Option<jacquard_common::CowStr<'a>>, 719 } 720 721 pub mod create_result_state { ··· 1131 /// If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. 1132 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1133 #[serde(borrow)] 1134 + pub swap_commit: std::option::Option<jacquard_common::types::string::Cid<'a>>, 1135 /// Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons. 1136 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1137 + pub validate: std::option::Option<bool>, 1138 #[serde(borrow)] 1139 pub writes: Vec<ApplyWritesWritesItem<'a>>, 1140 } ··· 1686 pub uri: jacquard_common::types::string::AtUri<'a>, 1687 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1688 #[serde(borrow)] 1689 + pub validation_status: std::option::Option<jacquard_common::CowStr<'a>>, 1690 } 1691 1692 pub mod update_result_state {
+3 -3
crates/weaver-api/src/com_atproto/repo/create_record.rs
··· 29 /// The Record Key. 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 #[serde(borrow)] 32 - pub rkey: Option< 33 jacquard_common::types::string::RecordKey< 34 jacquard_common::types::string::Rkey<'a>, 35 >, ··· 37 /// Compare and swap with the previous commit by CID. 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 - pub swap_commit: Option<jacquard_common::types::string::Cid<'a>>, 41 /// Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 - pub validate: Option<bool>, 44 } 45 46 pub mod create_record_state {
··· 29 /// The Record Key. 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 #[serde(borrow)] 32 + pub rkey: std::option::Option< 33 jacquard_common::types::string::RecordKey< 34 jacquard_common::types::string::Rkey<'a>, 35 >, ··· 37 /// Compare and swap with the previous commit by CID. 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 + pub swap_commit: std::option::Option<jacquard_common::types::string::Cid<'a>>, 41 /// Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 + pub validate: std::option::Option<bool>, 44 } 45 46 pub mod create_record_state {
+2 -2
crates/weaver-api/src/com_atproto/repo/delete_record.rs
··· 31 /// Compare and swap with the previous commit by CID. 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 - pub swap_commit: Option<jacquard_common::types::string::Cid<'a>>, 35 /// Compare and swap with the previous record by CID. 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 #[serde(borrow)] 38 - pub swap_record: Option<jacquard_common::types::string::Cid<'a>>, 39 } 40 41 pub mod delete_record_state {
··· 31 /// Compare and swap with the previous commit by CID. 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 + pub swap_commit: std::option::Option<jacquard_common::types::string::Cid<'a>>, 35 /// Compare and swap with the previous record by CID. 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 #[serde(borrow)] 38 + pub swap_record: std::option::Option<jacquard_common::types::string::Cid<'a>>, 39 } 40 41 pub mod delete_record_state {
+3 -3
crates/weaver-api/src/com_atproto/repo/put_record.rs
··· 34 /// Compare and swap with the previous commit by CID. 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 - pub swap_commit: Option<jacquard_common::types::string::Cid<'a>>, 38 /// Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 #[serde(borrow)] 41 - pub swap_record: Option<jacquard_common::types::string::Cid<'a>>, 42 /// Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 - pub validate: Option<bool>, 45 } 46 47 pub mod put_record_state {
··· 34 /// Compare and swap with the previous commit by CID. 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 + pub swap_commit: std::option::Option<jacquard_common::types::string::Cid<'a>>, 38 /// Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 #[serde(borrow)] 41 + pub swap_record: std::option::Option<jacquard_common::types::string::Cid<'a>>, 42 /// Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 + pub validate: std::option::Option<bool>, 45 } 46 47 pub mod put_record_state {
+8 -8
crates/weaver-api/src/com_atproto/server/create_account.rs
··· 20 /// Pre-existing atproto DID, being imported to a new account. 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 - pub did: Option<jacquard_common::types::string::Did<'a>>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub email: Option<jacquard_common::CowStr<'a>>, 27 /// Requested handle for the account. 28 #[serde(borrow)] 29 pub handle: jacquard_common::types::string::Handle<'a>, 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 #[serde(borrow)] 32 - pub invite_code: Option<jacquard_common::CowStr<'a>>, 33 /// Initial account password. May need to meet instance-specific password strength requirements. 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 - pub password: Option<jacquard_common::CowStr<'a>>, 37 /// A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented. 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 - pub plc_op: Option<jacquard_common::types::value::Data<'a>>, 41 /// DID PLC rotation key (aka, recovery key) to be included in PLC creation operation. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 - pub recovery_key: Option<jacquard_common::CowStr<'a>>, 45 #[serde(skip_serializing_if = "std::option::Option::is_none")] 46 #[serde(borrow)] 47 - pub verification_code: Option<jacquard_common::CowStr<'a>>, 48 #[serde(skip_serializing_if = "std::option::Option::is_none")] 49 #[serde(borrow)] 50 - pub verification_phone: Option<jacquard_common::CowStr<'a>>, 51 } 52 53 pub mod create_account_state {
··· 20 /// Pre-existing atproto DID, being imported to a new account. 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 + pub did: std::option::Option<jacquard_common::types::string::Did<'a>>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub email: std::option::Option<jacquard_common::CowStr<'a>>, 27 /// Requested handle for the account. 28 #[serde(borrow)] 29 pub handle: jacquard_common::types::string::Handle<'a>, 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 #[serde(borrow)] 32 + pub invite_code: std::option::Option<jacquard_common::CowStr<'a>>, 33 /// Initial account password. May need to meet instance-specific password strength requirements. 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub password: std::option::Option<jacquard_common::CowStr<'a>>, 37 /// A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented. 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 + pub plc_op: std::option::Option<jacquard_common::types::value::Data<'a>>, 41 /// DID PLC rotation key (aka, recovery key) to be included in PLC creation operation. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 + pub recovery_key: std::option::Option<jacquard_common::CowStr<'a>>, 45 #[serde(skip_serializing_if = "std::option::Option::is_none")] 46 #[serde(borrow)] 47 + pub verification_code: std::option::Option<jacquard_common::CowStr<'a>>, 48 #[serde(skip_serializing_if = "std::option::Option::is_none")] 49 #[serde(borrow)] 50 + pub verification_phone: std::option::Option<jacquard_common::CowStr<'a>>, 51 } 52 53 pub mod create_account_state {
+1 -1
crates/weaver-api/src/com_atproto/server/create_app_password.rs
··· 23 #[serde(borrow)] 24 pub password: jacquard_common::CowStr<'a>, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 - pub privileged: Option<bool>, 27 } 28 29 pub mod app_password_state {
··· 23 #[serde(borrow)] 24 pub password: jacquard_common::CowStr<'a>, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 + pub privileged: std::option::Option<bool>, 27 } 28 29 pub mod app_password_state {
+1 -1
crates/weaver-api/src/com_atproto/server/create_invite_code.rs
··· 19 pub struct CreateInviteCode<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 - pub for_account: Option<jacquard_common::types::string::Did<'a>>, 23 pub use_count: i64, 24 } 25
··· 19 pub struct CreateInviteCode<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 + pub for_account: std::option::Option<jacquard_common::types::string::Did<'a>>, 23 pub use_count: i64, 24 } 25
+1 -1
crates/weaver-api/src/com_atproto/server/create_invite_codes.rs
··· 347 pub code_count: i64, 348 #[serde(skip_serializing_if = "std::option::Option::is_none")] 349 #[serde(borrow)] 350 - pub for_accounts: Option<Vec<jacquard_common::types::string::Did<'a>>>, 351 pub use_count: i64, 352 } 353
··· 347 pub code_count: i64, 348 #[serde(skip_serializing_if = "std::option::Option::is_none")] 349 #[serde(borrow)] 350 + pub for_accounts: std::option::Option<Vec<jacquard_common::types::string::Did<'a>>>, 351 pub use_count: i64, 352 } 353
+1 -1
crates/weaver-api/src/com_atproto/server/list_app_passwords.rs
··· 21 #[serde(borrow)] 22 pub name: jacquard_common::CowStr<'a>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 - pub privileged: Option<bool>, 25 } 26 27 pub mod app_password_state {
··· 21 #[serde(borrow)] 22 pub name: jacquard_common::CowStr<'a>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 + pub privileged: std::option::Option<bool>, 25 } 26 27 pub mod app_password_state {
+2 -2
crates/weaver-api/src/com_atproto/sync/list_repos.rs
··· 171 #[serde(rename_all = "camelCase")] 172 pub struct Repo<'a> { 173 #[serde(skip_serializing_if = "std::option::Option::is_none")] 174 - pub active: Option<bool>, 175 #[serde(borrow)] 176 pub did: jacquard_common::types::string::Did<'a>, 177 /// Current repo commit CID ··· 181 /// If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted. 182 #[serde(skip_serializing_if = "std::option::Option::is_none")] 183 #[serde(borrow)] 184 - pub status: Option<jacquard_common::CowStr<'a>>, 185 } 186 187 pub mod repo_state {
··· 171 #[serde(rename_all = "camelCase")] 172 pub struct Repo<'a> { 173 #[serde(skip_serializing_if = "std::option::Option::is_none")] 174 + pub active: std::option::Option<bool>, 175 #[serde(borrow)] 176 pub did: jacquard_common::types::string::Did<'a>, 177 /// Current repo commit CID ··· 181 /// If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted. 182 #[serde(skip_serializing_if = "std::option::Option::is_none")] 183 #[serde(borrow)] 184 + pub status: std::option::Option<jacquard_common::CowStr<'a>>, 185 } 186 187 pub mod repo_state {
+4 -4
crates/weaver-api/src/com_atproto/sync/subscribe_repos.rs
··· 26 /// If active=false, this optional field indicates a reason for why the account is not active. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub status: Option<jacquard_common::CowStr<'a>>, 30 pub time: jacquard_common::types::string::Datetime, 31 } 32 ··· 938 /// The root CID of the MST tree for the previous commit from this repo (indicated by the 'since' revision field in this message). Corresponds to the 'data' field in the repo commit object. NOTE: this field is effectively required for the 'inductive' version of firehose. 939 #[serde(skip_serializing_if = "std::option::Option::is_none")] 940 #[serde(borrow)] 941 - pub prev_data: Option<jacquard_common::types::cid::CidLink<'a>>, 942 /// DEPRECATED -- unused 943 pub rebase: bool, 944 /// The repo this event comes from. Note that all other message types name this field 'did'. ··· 1586 /// The current handle for the account, or 'handle.invalid' if validation fails. This field is optional, might have been validated or passed-through from an upstream source. Semantics and behaviors for PDS vs Relay may evolve in the future; see atproto specs for more details. 1587 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1588 #[serde(borrow)] 1589 - pub handle: Option<jacquard_common::types::string::Handle<'a>>, 1590 pub seq: i64, 1591 pub time: jacquard_common::types::string::Datetime, 1592 } ··· 2085 /// For updates and deletes, the previous record CID (required for inductive firehose). For creations, field should not be defined. 2086 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2087 #[serde(borrow)] 2088 - pub prev: Option<jacquard_common::types::cid::CidLink<'a>>, 2089 } 2090 2091 pub mod repo_op_state {
··· 26 /// If active=false, this optional field indicates a reason for why the account is not active. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub status: std::option::Option<jacquard_common::CowStr<'a>>, 30 pub time: jacquard_common::types::string::Datetime, 31 } 32 ··· 938 /// The root CID of the MST tree for the previous commit from this repo (indicated by the 'since' revision field in this message). Corresponds to the 'data' field in the repo commit object. NOTE: this field is effectively required for the 'inductive' version of firehose. 939 #[serde(skip_serializing_if = "std::option::Option::is_none")] 940 #[serde(borrow)] 941 + pub prev_data: std::option::Option<jacquard_common::types::cid::CidLink<'a>>, 942 /// DEPRECATED -- unused 943 pub rebase: bool, 944 /// The repo this event comes from. Note that all other message types name this field 'did'. ··· 1586 /// The current handle for the account, or 'handle.invalid' if validation fails. This field is optional, might have been validated or passed-through from an upstream source. Semantics and behaviors for PDS vs Relay may evolve in the future; see atproto specs for more details. 1587 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1588 #[serde(borrow)] 1589 + pub handle: std::option::Option<jacquard_common::types::string::Handle<'a>>, 1590 pub seq: i64, 1591 pub time: jacquard_common::types::string::Datetime, 1592 } ··· 2085 /// For updates and deletes, the previous record CID (required for inductive firehose). For creations, field should not be defined. 2086 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2087 #[serde(borrow)] 2088 + pub prev: std::option::Option<jacquard_common::types::cid::CidLink<'a>>, 2089 } 2090 2091 pub mod repo_op_state {
+30 -28
crates/weaver-api/src/sh_weaver/actor.rs
··· 25 pub did: jacquard_common::types::string::Did<'a>, 26 /// signed bytes of the corresponding notebook record in the author's repo 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 - pub signature: Option<bytes::Bytes>, 29 } 30 31 pub mod author_state { ··· 1256 pub struct ProfileView<'a> { 1257 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1258 #[serde(borrow)] 1259 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 1260 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1261 #[serde(borrow)] 1262 - pub banner: Option<jacquard_common::types::string::Uri<'a>>, 1263 /// Include link to this account on Bluesky. 1264 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1265 - pub bluesky: Option<bool>, 1266 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1267 - pub created_at: Option<jacquard_common::types::string::Datetime>, 1268 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1269 #[serde(borrow)] 1270 - pub description: Option<jacquard_common::CowStr<'a>>, 1271 #[serde(borrow)] 1272 pub did: jacquard_common::types::string::Did<'a>, 1273 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1274 #[serde(borrow)] 1275 - pub display_name: Option<jacquard_common::CowStr<'a>>, 1276 #[serde(borrow)] 1277 pub handle: jacquard_common::types::string::Handle<'a>, 1278 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1279 - pub indexed_at: Option<jacquard_common::types::string::Datetime>, 1280 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1281 #[serde(borrow)] 1282 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 1283 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1284 #[serde(borrow)] 1285 - pub links: Option<Vec<jacquard_common::types::string::Uri<'a>>>, 1286 /// Free-form location text. 1287 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1288 #[serde(borrow)] 1289 - pub location: Option<jacquard_common::CowStr<'a>>, 1290 /// Notebooks or other records pinned for display. 1291 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1292 #[serde(borrow)] 1293 - pub pinned: Option<crate::sh_weaver::actor::PinnedList<'a>>, 1294 /// Pronouns to use in user-generated content. 1295 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1296 #[serde(borrow)] 1297 - pub pronouns: Option<crate::sh_weaver::actor::PronounsList<'a>>, 1298 /// Include link to this account on stream.place. 1299 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1300 - pub streamplace: Option<bool>, 1301 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1302 - pub subscribed_count: Option<i64>, 1303 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1304 - pub subscriber_count: Option<i64>, 1305 /// Include link to this account on Tangled. 1306 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1307 - pub tangled: Option<bool>, 1308 } 1309 1310 pub mod profile_view_state { ··· 1930 pub struct ProfileViewBasic<'a> { 1931 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1932 #[serde(borrow)] 1933 - pub avatar: Option<jacquard_common::types::string::Uri<'a>>, 1934 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1935 - pub created_at: Option<jacquard_common::types::string::Datetime>, 1936 #[serde(borrow)] 1937 pub did: jacquard_common::types::string::Did<'a>, 1938 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1939 #[serde(borrow)] 1940 - pub display_name: Option<jacquard_common::CowStr<'a>>, 1941 #[serde(borrow)] 1942 pub handle: jacquard_common::types::string::Handle<'a>, 1943 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1944 - pub indexed_at: Option<jacquard_common::types::string::Datetime>, 1945 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1946 #[serde(borrow)] 1947 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 1948 /// Pronouns to use in user-generated content. 1949 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1950 #[serde(borrow)] 1951 - pub pronouns: Option<crate::sh_weaver::actor::PronounsList<'a>>, 1952 } 1953 1954 pub mod profile_view_basic_state { ··· 2288 /// Free-form profile description text. 2289 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2290 #[serde(borrow)] 2291 - pub description: Option<jacquard_common::CowStr<'a>>, 2292 #[serde(borrow)] 2293 pub did: jacquard_common::types::string::Did<'a>, 2294 #[serde(borrow)] 2295 pub handle: jacquard_common::types::string::Handle<'a>, 2296 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2297 #[serde(borrow)] 2298 - pub links: Option<Vec<jacquard_common::types::string::Uri<'a>>>, 2299 /// Free-form location text. 2300 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2301 #[serde(borrow)] 2302 - pub location: Option<jacquard_common::CowStr<'a>>, 2303 /// Any ATURI, it is up to appviews to validate these fields. 2304 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2305 #[serde(borrow)] 2306 - pub pinned_repositories: Option<Vec<jacquard_common::types::string::AtUri<'a>>>, 2307 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2308 #[serde(borrow)] 2309 - pub stats: Option<Vec<jacquard_common::CowStr<'a>>>, 2310 } 2311 2312 pub mod tangled_profile_view_state {
··· 25 pub did: jacquard_common::types::string::Did<'a>, 26 /// signed bytes of the corresponding notebook record in the author's repo 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 + pub signature: std::option::Option<bytes::Bytes>, 29 } 30 31 pub mod author_state { ··· 1256 pub struct ProfileView<'a> { 1257 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1258 #[serde(borrow)] 1259 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 1260 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1261 #[serde(borrow)] 1262 + pub banner: std::option::Option<jacquard_common::types::string::Uri<'a>>, 1263 /// Include link to this account on Bluesky. 1264 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1265 + pub bluesky: std::option::Option<bool>, 1266 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1267 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 1268 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1269 #[serde(borrow)] 1270 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 1271 #[serde(borrow)] 1272 pub did: jacquard_common::types::string::Did<'a>, 1273 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1274 #[serde(borrow)] 1275 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 1276 #[serde(borrow)] 1277 pub handle: jacquard_common::types::string::Handle<'a>, 1278 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1279 + pub indexed_at: std::option::Option<jacquard_common::types::string::Datetime>, 1280 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1281 #[serde(borrow)] 1282 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 1283 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1284 #[serde(borrow)] 1285 + pub links: std::option::Option<Vec<jacquard_common::types::string::Uri<'a>>>, 1286 /// Free-form location text. 1287 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1288 #[serde(borrow)] 1289 + pub location: std::option::Option<jacquard_common::CowStr<'a>>, 1290 /// Notebooks or other records pinned for display. 1291 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1292 #[serde(borrow)] 1293 + pub pinned: std::option::Option<crate::sh_weaver::actor::PinnedList<'a>>, 1294 /// Pronouns to use in user-generated content. 1295 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1296 #[serde(borrow)] 1297 + pub pronouns: std::option::Option<crate::sh_weaver::actor::PronounsList<'a>>, 1298 /// Include link to this account on stream.place. 1299 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1300 + pub streamplace: std::option::Option<bool>, 1301 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1302 + pub subscribed_count: std::option::Option<i64>, 1303 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1304 + pub subscriber_count: std::option::Option<i64>, 1305 /// Include link to this account on Tangled. 1306 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1307 + pub tangled: std::option::Option<bool>, 1308 } 1309 1310 pub mod profile_view_state { ··· 1930 pub struct ProfileViewBasic<'a> { 1931 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1932 #[serde(borrow)] 1933 + pub avatar: std::option::Option<jacquard_common::types::string::Uri<'a>>, 1934 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1935 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 1936 #[serde(borrow)] 1937 pub did: jacquard_common::types::string::Did<'a>, 1938 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1939 #[serde(borrow)] 1940 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 1941 #[serde(borrow)] 1942 pub handle: jacquard_common::types::string::Handle<'a>, 1943 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1944 + pub indexed_at: std::option::Option<jacquard_common::types::string::Datetime>, 1945 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1946 #[serde(borrow)] 1947 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 1948 /// Pronouns to use in user-generated content. 1949 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1950 #[serde(borrow)] 1951 + pub pronouns: std::option::Option<crate::sh_weaver::actor::PronounsList<'a>>, 1952 } 1953 1954 pub mod profile_view_basic_state { ··· 2288 /// Free-form profile description text. 2289 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2290 #[serde(borrow)] 2291 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 2292 #[serde(borrow)] 2293 pub did: jacquard_common::types::string::Did<'a>, 2294 #[serde(borrow)] 2295 pub handle: jacquard_common::types::string::Handle<'a>, 2296 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2297 #[serde(borrow)] 2298 + pub links: std::option::Option<Vec<jacquard_common::types::string::Uri<'a>>>, 2299 /// Free-form location text. 2300 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2301 #[serde(borrow)] 2302 + pub location: std::option::Option<jacquard_common::CowStr<'a>>, 2303 /// Any ATURI, it is up to appviews to validate these fields. 2304 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2305 #[serde(borrow)] 2306 + pub pinned_repositories: std::option::Option< 2307 + Vec<jacquard_common::types::string::AtUri<'a>>, 2308 + >, 2309 #[serde(skip_serializing_if = "std::option::Option::is_none")] 2310 #[serde(borrow)] 2311 + pub stats: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 2312 } 2313 2314 pub mod tangled_profile_view_state {
+13 -13
crates/weaver-api/src/sh_weaver/actor/profile.rs
··· 21 /// Small image to be displayed next to posts from account. AKA, 'profile picture' 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 - pub avatar: Option<jacquard_common::types::blob::BlobRef<'a>>, 25 /// Larger horizontal image to display behind profile view. 26 #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 #[serde(borrow)] 28 - pub banner: Option<jacquard_common::types::blob::BlobRef<'a>>, 29 /// Include link to this account on Bluesky. 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 - pub bluesky: Option<bool>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 - pub created_at: Option<jacquard_common::types::string::Datetime>, 34 /// Free-form profile description text. 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 - pub description: Option<jacquard_common::CowStr<'a>>, 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 - pub display_name: Option<jacquard_common::CowStr<'a>>, 41 /// Self-label values, specific to the application, on the overall account. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 - pub labels: Option<crate::com_atproto::label::SelfLabels<'a>>, 45 #[serde(skip_serializing_if = "std::option::Option::is_none")] 46 #[serde(borrow)] 47 - pub links: Option<Vec<jacquard_common::types::string::Uri<'a>>>, 48 /// Free-form location text. 49 #[serde(skip_serializing_if = "std::option::Option::is_none")] 50 #[serde(borrow)] 51 - pub location: Option<jacquard_common::CowStr<'a>>, 52 /// Notebooks or other records pinned for display. 53 #[serde(skip_serializing_if = "std::option::Option::is_none")] 54 #[serde(borrow)] 55 - pub pinned: Option<crate::sh_weaver::actor::PinnedList<'a>>, 56 /// Pronouns to use in user-generated content. 57 #[serde(skip_serializing_if = "std::option::Option::is_none")] 58 #[serde(borrow)] 59 - pub pronouns: Option<crate::sh_weaver::actor::PronounsList<'a>>, 60 /// Include link to this account on stream.place. 61 #[serde(skip_serializing_if = "std::option::Option::is_none")] 62 - pub streamplace: Option<bool>, 63 /// Include link to this account on Tangled. 64 #[serde(skip_serializing_if = "std::option::Option::is_none")] 65 - pub tangled: Option<bool>, 66 } 67 68 pub mod profile_state {
··· 21 /// Small image to be displayed next to posts from account. AKA, 'profile picture' 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 + pub avatar: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 25 /// Larger horizontal image to display behind profile view. 26 #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 #[serde(borrow)] 28 + pub banner: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 29 /// Include link to this account on Bluesky. 30 #[serde(skip_serializing_if = "std::option::Option::is_none")] 31 + pub bluesky: std::option::Option<bool>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 34 /// Free-form profile description text. 35 #[serde(skip_serializing_if = "std::option::Option::is_none")] 36 #[serde(borrow)] 37 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 38 #[serde(skip_serializing_if = "std::option::Option::is_none")] 39 #[serde(borrow)] 40 + pub display_name: std::option::Option<jacquard_common::CowStr<'a>>, 41 /// Self-label values, specific to the application, on the overall account. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 + pub labels: std::option::Option<crate::com_atproto::label::SelfLabels<'a>>, 45 #[serde(skip_serializing_if = "std::option::Option::is_none")] 46 #[serde(borrow)] 47 + pub links: std::option::Option<Vec<jacquard_common::types::string::Uri<'a>>>, 48 /// Free-form location text. 49 #[serde(skip_serializing_if = "std::option::Option::is_none")] 50 #[serde(borrow)] 51 + pub location: std::option::Option<jacquard_common::CowStr<'a>>, 52 /// Notebooks or other records pinned for display. 53 #[serde(skip_serializing_if = "std::option::Option::is_none")] 54 #[serde(borrow)] 55 + pub pinned: std::option::Option<crate::sh_weaver::actor::PinnedList<'a>>, 56 /// Pronouns to use in user-generated content. 57 #[serde(skip_serializing_if = "std::option::Option::is_none")] 58 #[serde(borrow)] 59 + pub pronouns: std::option::Option<crate::sh_weaver::actor::PronounsList<'a>>, 60 /// Include link to this account on stream.place. 61 #[serde(skip_serializing_if = "std::option::Option::is_none")] 62 + pub streamplace: std::option::Option<bool>, 63 /// Include link to this account on Tangled. 64 #[serde(skip_serializing_if = "std::option::Option::is_none")] 65 + pub tangled: std::option::Option<bool>, 66 } 67 68 pub mod profile_state {
+1 -1
crates/weaver-api/src/sh_weaver/edit/cursor.rs
··· 746 pub id: crate::sh_weaver::edit::cursor::Id<'a>, 747 #[serde(skip_serializing_if = "std::option::Option::is_none")] 748 #[serde(borrow)] 749 - pub side: Option<crate::sh_weaver::edit::cursor::CursorSide<'a>>, 750 } 751 752 pub mod cursor_state {
··· 746 pub id: crate::sh_weaver::edit::cursor::Id<'a>, 747 #[serde(skip_serializing_if = "std::option::Option::is_none")] 748 #[serde(borrow)] 749 + pub side: std::option::Option<crate::sh_weaver::edit::cursor::CursorSide<'a>>, 750 } 751 752 pub mod cursor_state {
+2 -2
crates/weaver-api/src/sh_weaver/embed/external.rs
··· 21 pub description: jacquard_common::CowStr<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 - pub thumb: Option<jacquard_common::types::blob::BlobRef<'a>>, 25 #[serde(borrow)] 26 pub title: jacquard_common::CowStr<'a>, 27 #[serde(borrow)] ··· 800 pub description: jacquard_common::CowStr<'a>, 801 #[serde(skip_serializing_if = "std::option::Option::is_none")] 802 #[serde(borrow)] 803 - pub thumb: Option<jacquard_common::types::string::Uri<'a>>, 804 #[serde(borrow)] 805 pub title: jacquard_common::CowStr<'a>, 806 #[serde(borrow)]
··· 21 pub description: jacquard_common::CowStr<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 + pub thumb: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 25 #[serde(borrow)] 26 pub title: jacquard_common::CowStr<'a>, 27 #[serde(borrow)] ··· 800 pub description: jacquard_common::CowStr<'a>, 801 #[serde(skip_serializing_if = "std::option::Option::is_none")] 802 #[serde(borrow)] 803 + pub thumb: std::option::Option<jacquard_common::types::string::Uri<'a>>, 804 #[serde(borrow)] 805 pub title: jacquard_common::CowStr<'a>, 806 #[serde(borrow)]
+5 -5
crates/weaver-api/src/sh_weaver/embed/images.rs
··· 23 /// Blurhash string for the image, used for low-resolution placeholders. This must be a valid Blurhash string. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub blurhash: Option<jacquard_common::CowStr<'a>>, 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub dimensions: Option<ImageDimensions<'a>>, 30 #[serde(borrow)] 31 pub image: jacquard_common::types::blob::BlobRef<'a>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 - pub name: Option<jacquard_common::CowStr<'a>>, 35 } 36 37 pub mod image_state { ··· 880 pub alt: jacquard_common::CowStr<'a>, 881 #[serde(skip_serializing_if = "std::option::Option::is_none")] 882 #[serde(borrow)] 883 - pub dimensions: Option<ViewImageDimensions<'a>>, 884 /// Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. 885 #[serde(borrow)] 886 pub fullsize: jacquard_common::types::string::Uri<'a>, 887 #[serde(skip_serializing_if = "std::option::Option::is_none")] 888 #[serde(borrow)] 889 - pub name: Option<jacquard_common::CowStr<'a>>, 890 /// Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. 891 #[serde(borrow)] 892 pub thumb: jacquard_common::types::string::Uri<'a>,
··· 23 /// Blurhash string for the image, used for low-resolution placeholders. This must be a valid Blurhash string. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub blurhash: std::option::Option<jacquard_common::CowStr<'a>>, 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub dimensions: std::option::Option<ImageDimensions<'a>>, 30 #[serde(borrow)] 31 pub image: jacquard_common::types::blob::BlobRef<'a>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 + pub name: std::option::Option<jacquard_common::CowStr<'a>>, 35 } 36 37 pub mod image_state { ··· 880 pub alt: jacquard_common::CowStr<'a>, 881 #[serde(skip_serializing_if = "std::option::Option::is_none")] 882 #[serde(borrow)] 883 + pub dimensions: std::option::Option<ViewImageDimensions<'a>>, 884 /// Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. 885 #[serde(borrow)] 886 pub fullsize: jacquard_common::types::string::Uri<'a>, 887 #[serde(skip_serializing_if = "std::option::Option::is_none")] 888 #[serde(borrow)] 889 + pub name: std::option::Option<jacquard_common::CowStr<'a>>, 890 /// Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. 891 #[serde(borrow)] 892 pub thumb: jacquard_common::types::string::Uri<'a>,
+1 -1
crates/weaver-api/src/sh_weaver/embed/record_with_media.rs
··· 310 pub media: ViewMedia<'a>, 311 #[serde(skip_serializing_if = "std::option::Option::is_none")] 312 #[serde(borrow)] 313 - pub records: Option<crate::sh_weaver::embed::records::View<'a>>, 314 } 315 316 pub mod view_state {
··· 310 pub media: ViewMedia<'a>, 311 #[serde(skip_serializing_if = "std::option::Option::is_none")] 312 #[serde(borrow)] 313 + pub records: std::option::Option<crate::sh_weaver::embed::records::View<'a>>, 314 } 315 316 pub mod view_state {
+10 -8
crates/weaver-api/src/sh_weaver/embed/records.rs
··· 638 pub struct RecordEmbed<'a> { 639 #[serde(skip_serializing_if = "std::option::Option::is_none")] 640 #[serde(borrow)] 641 - pub name: Option<jacquard_common::CowStr<'a>>, 642 #[serde(borrow)] 643 pub record: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 644 } ··· 810 pub struct RecordEmbedView<'a> { 811 #[serde(skip_serializing_if = "std::option::Option::is_none")] 812 #[serde(borrow)] 813 - pub name: Option<jacquard_common::CowStr<'a>>, 814 #[serde(borrow)] 815 pub record_view: RecordEmbedViewRecordView<'a>, 816 } ··· 1737 pub cid: jacquard_common::types::string::Cid<'a>, 1738 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1739 #[serde(borrow)] 1740 - pub embeds: Option<Vec<crate::sh_weaver::embed::records::RecordEmbedView<'a>>>, 1741 pub indexed_at: jacquard_common::types::string::Datetime, 1742 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1743 #[serde(borrow)] 1744 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 1745 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1746 - pub like_count: Option<i64>, 1747 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1748 - pub quote_count: Option<i64>, 1749 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1750 - pub reply_count: Option<i64>, 1751 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1752 - pub repost_count: Option<i64>, 1753 #[serde(borrow)] 1754 pub uri: jacquard_common::types::string::AtUri<'a>, 1755 /// The record data itself.
··· 638 pub struct RecordEmbed<'a> { 639 #[serde(skip_serializing_if = "std::option::Option::is_none")] 640 #[serde(borrow)] 641 + pub name: std::option::Option<jacquard_common::CowStr<'a>>, 642 #[serde(borrow)] 643 pub record: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 644 } ··· 810 pub struct RecordEmbedView<'a> { 811 #[serde(skip_serializing_if = "std::option::Option::is_none")] 812 #[serde(borrow)] 813 + pub name: std::option::Option<jacquard_common::CowStr<'a>>, 814 #[serde(borrow)] 815 pub record_view: RecordEmbedViewRecordView<'a>, 816 } ··· 1737 pub cid: jacquard_common::types::string::Cid<'a>, 1738 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1739 #[serde(borrow)] 1740 + pub embeds: std::option::Option< 1741 + Vec<crate::sh_weaver::embed::records::RecordEmbedView<'a>>, 1742 + >, 1743 pub indexed_at: jacquard_common::types::string::Datetime, 1744 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1745 #[serde(borrow)] 1746 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 1747 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1748 + pub like_count: std::option::Option<i64>, 1749 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1750 + pub quote_count: std::option::Option<i64>, 1751 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1752 + pub reply_count: std::option::Option<i64>, 1753 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1754 + pub repost_count: std::option::Option<i64>, 1755 #[serde(borrow)] 1756 pub uri: jacquard_common::types::string::AtUri<'a>, 1757 /// The record data itself.
+8 -8
crates/weaver-api/src/sh_weaver/embed/video.rs
··· 615 /// Alt text description of the video, for accessibility. 616 #[serde(skip_serializing_if = "std::option::Option::is_none")] 617 #[serde(borrow)] 618 - pub alt: Option<jacquard_common::CowStr<'a>>, 619 #[serde(skip_serializing_if = "std::option::Option::is_none")] 620 #[serde(borrow)] 621 - pub captions: Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 622 #[serde(skip_serializing_if = "std::option::Option::is_none")] 623 #[serde(borrow)] 624 - pub dimensions: Option<VideoDimensions<'a>>, 625 #[serde(skip_serializing_if = "std::option::Option::is_none")] 626 #[serde(borrow)] 627 - pub name: Option<jacquard_common::CowStr<'a>>, 628 /// The mp4 video file. May be up to 100mb, formerly limited to 50mb. 629 #[serde(borrow)] 630 pub video: jacquard_common::types::blob::BlobRef<'a>, ··· 914 pub struct View<'a> { 915 #[serde(skip_serializing_if = "std::option::Option::is_none")] 916 #[serde(borrow)] 917 - pub alt: Option<jacquard_common::CowStr<'a>>, 918 #[serde(borrow)] 919 pub cid: jacquard_common::types::string::Cid<'a>, 920 #[serde(skip_serializing_if = "std::option::Option::is_none")] 921 #[serde(borrow)] 922 - pub dimensions: Option<ViewDimensions<'a>>, 923 #[serde(skip_serializing_if = "std::option::Option::is_none")] 924 #[serde(borrow)] 925 - pub name: Option<jacquard_common::CowStr<'a>>, 926 #[serde(borrow)] 927 pub playlist: jacquard_common::types::string::Uri<'a>, 928 #[serde(skip_serializing_if = "std::option::Option::is_none")] 929 #[serde(borrow)] 930 - pub thumbnail: Option<jacquard_common::types::string::Uri<'a>>, 931 } 932 933 pub mod view_state {
··· 615 /// Alt text description of the video, for accessibility. 616 #[serde(skip_serializing_if = "std::option::Option::is_none")] 617 #[serde(borrow)] 618 + pub alt: std::option::Option<jacquard_common::CowStr<'a>>, 619 #[serde(skip_serializing_if = "std::option::Option::is_none")] 620 #[serde(borrow)] 621 + pub captions: std::option::Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 622 #[serde(skip_serializing_if = "std::option::Option::is_none")] 623 #[serde(borrow)] 624 + pub dimensions: std::option::Option<VideoDimensions<'a>>, 625 #[serde(skip_serializing_if = "std::option::Option::is_none")] 626 #[serde(borrow)] 627 + pub name: std::option::Option<jacquard_common::CowStr<'a>>, 628 /// The mp4 video file. May be up to 100mb, formerly limited to 50mb. 629 #[serde(borrow)] 630 pub video: jacquard_common::types::blob::BlobRef<'a>, ··· 914 pub struct View<'a> { 915 #[serde(skip_serializing_if = "std::option::Option::is_none")] 916 #[serde(borrow)] 917 + pub alt: std::option::Option<jacquard_common::CowStr<'a>>, 918 #[serde(borrow)] 919 pub cid: jacquard_common::types::string::Cid<'a>, 920 #[serde(skip_serializing_if = "std::option::Option::is_none")] 921 #[serde(borrow)] 922 + pub dimensions: std::option::Option<ViewDimensions<'a>>, 923 #[serde(skip_serializing_if = "std::option::Option::is_none")] 924 #[serde(borrow)] 925 + pub name: std::option::Option<jacquard_common::CowStr<'a>>, 926 #[serde(borrow)] 927 pub playlist: jacquard_common::types::string::Uri<'a>, 928 #[serde(skip_serializing_if = "std::option::Option::is_none")] 929 #[serde(borrow)] 930 + pub thumbnail: std::option::Option<jacquard_common::types::string::Uri<'a>>, 931 } 932 933 pub mod view_state {
+9 -9
crates/weaver-api/src/sh_weaver/notebook.rs
··· 30 pub record: crate::sh_weaver::actor::ProfileDataView<'a>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 - pub uri: Option<jacquard_common::types::string::AtUri<'a>>, 34 } 35 36 pub mod author_list_view_state { ··· 874 pub index: i64, 875 #[serde(skip_serializing_if = "std::option::Option::is_none")] 876 #[serde(borrow)] 877 - pub next: Option<crate::sh_weaver::notebook::BookEntryRef<'a>>, 878 #[serde(skip_serializing_if = "std::option::Option::is_none")] 879 #[serde(borrow)] 880 - pub prev: Option<crate::sh_weaver::notebook::BookEntryRef<'a>>, 881 } 882 883 pub mod book_entry_view_state { ··· 1139 pub record: jacquard_common::types::value::Data<'a>, 1140 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1141 #[serde(borrow)] 1142 - pub rendered_view: Option<crate::sh_weaver::notebook::RenderedView<'a>>, 1143 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1144 #[serde(borrow)] 1145 - pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 1146 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1147 #[serde(borrow)] 1148 - pub title: Option<crate::sh_weaver::notebook::Title<'a>>, 1149 #[serde(borrow)] 1150 pub uri: jacquard_common::types::string::AtUri<'a>, 1151 } ··· 1511 pub record: jacquard_common::types::value::Data<'a>, 1512 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1513 #[serde(borrow)] 1514 - pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 1515 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1516 #[serde(borrow)] 1517 - pub title: Option<crate::sh_weaver::notebook::Title<'a>>, 1518 #[serde(borrow)] 1519 pub uri: jacquard_common::types::string::AtUri<'a>, 1520 } ··· 1854 pub struct RenderedView<'a> { 1855 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1856 #[serde(borrow)] 1857 - pub css: Option<jacquard_common::types::blob::BlobRef<'a>>, 1858 #[serde(borrow)] 1859 pub html: jacquard_common::types::blob::BlobRef<'a>, 1860 }
··· 30 pub record: crate::sh_weaver::actor::ProfileDataView<'a>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 + pub uri: std::option::Option<jacquard_common::types::string::AtUri<'a>>, 34 } 35 36 pub mod author_list_view_state { ··· 874 pub index: i64, 875 #[serde(skip_serializing_if = "std::option::Option::is_none")] 876 #[serde(borrow)] 877 + pub next: std::option::Option<crate::sh_weaver::notebook::BookEntryRef<'a>>, 878 #[serde(skip_serializing_if = "std::option::Option::is_none")] 879 #[serde(borrow)] 880 + pub prev: std::option::Option<crate::sh_weaver::notebook::BookEntryRef<'a>>, 881 } 882 883 pub mod book_entry_view_state { ··· 1139 pub record: jacquard_common::types::value::Data<'a>, 1140 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1141 #[serde(borrow)] 1142 + pub rendered_view: std::option::Option<crate::sh_weaver::notebook::RenderedView<'a>>, 1143 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1144 #[serde(borrow)] 1145 + pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 1146 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1147 #[serde(borrow)] 1148 + pub title: std::option::Option<crate::sh_weaver::notebook::Title<'a>>, 1149 #[serde(borrow)] 1150 pub uri: jacquard_common::types::string::AtUri<'a>, 1151 } ··· 1511 pub record: jacquard_common::types::value::Data<'a>, 1512 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1513 #[serde(borrow)] 1514 + pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 1515 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1516 #[serde(borrow)] 1517 + pub title: std::option::Option<crate::sh_weaver::notebook::Title<'a>>, 1518 #[serde(borrow)] 1519 pub uri: jacquard_common::types::string::AtUri<'a>, 1520 } ··· 1854 pub struct RenderedView<'a> { 1855 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1856 #[serde(borrow)] 1857 + pub css: std::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 1858 #[serde(borrow)] 1859 pub html: jacquard_common::types::blob::BlobRef<'a>, 1860 }
+3 -3
crates/weaver-api/src/sh_weaver/notebook/authors.rs
··· 19 #[serde(rename_all = "camelCase")] 20 pub struct AuthorListItem<'a> { 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 - pub index: Option<i64>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub profile: Option<AuthorListItemProfile<'a>>, 26 } 27 28 pub mod author_list_item_state { ··· 313 #[serde(borrow)] 314 pub author_list: Vec<crate::sh_weaver::notebook::authors::AuthorListItem<'a>>, 315 #[serde(skip_serializing_if = "std::option::Option::is_none")] 316 - pub created_at: Option<jacquard_common::types::string::Datetime>, 317 } 318 319 pub mod authors_state {
··· 19 #[serde(rename_all = "camelCase")] 20 pub struct AuthorListItem<'a> { 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 + pub index: std::option::Option<i64>, 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub profile: std::option::Option<AuthorListItemProfile<'a>>, 26 } 27 28 pub mod author_list_item_state { ··· 313 #[serde(borrow)] 314 pub author_list: Vec<crate::sh_weaver::notebook::authors::AuthorListItem<'a>>, 315 #[serde(skip_serializing_if = "std::option::Option::is_none")] 316 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 317 } 318 319 pub mod authors_state {
+5 -5
crates/weaver-api/src/sh_weaver/notebook/book.rs
··· 22 pub authors: Vec<crate::sh_weaver::actor::Author<'a>>, 23 /// Client-declared timestamp when this was originally created. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 - pub created_at: Option<jacquard_common::types::string::Datetime>, 26 #[serde(borrow)] 27 pub entry_list: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 - pub path: Option<crate::sh_weaver::notebook::Path<'a>>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 - pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 - pub theme: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 #[serde(borrow)] 39 - pub title: Option<crate::sh_weaver::notebook::Title<'a>>, 40 } 41 42 pub mod book_state {
··· 22 pub authors: Vec<crate::sh_weaver::actor::Author<'a>>, 23 /// Client-declared timestamp when this was originally created. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 26 #[serde(borrow)] 27 pub entry_list: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 + pub path: std::option::Option<crate::sh_weaver::notebook::Path<'a>>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 + pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub theme: std::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 #[serde(borrow)] 39 + pub title: std::option::Option<crate::sh_weaver::notebook::Title<'a>>, 40 } 41 42 pub mod book_state {
+3 -3
crates/weaver-api/src/sh_weaver/notebook/chapter.rs
··· 22 pub authors: Vec<crate::sh_weaver::actor::Author<'a>>, 23 /// Client-declared timestamp when this was originally created. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 - pub created_at: Option<jacquard_common::types::string::Datetime>, 26 #[serde(borrow)] 27 pub entry_list: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 28 /// The notebook this chapter belongs to. ··· 30 pub notebook: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 - pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 - pub title: Option<crate::sh_weaver::notebook::Title<'a>>, 37 } 38 39 pub mod chapter_state {
··· 22 pub authors: Vec<crate::sh_weaver::actor::Author<'a>>, 23 /// Client-declared timestamp when this was originally created. 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 26 #[serde(borrow)] 27 pub entry_list: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 28 /// The notebook this chapter belongs to. ··· 30 pub notebook: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 + pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub title: std::option::Option<crate::sh_weaver::notebook::Title<'a>>, 37 } 38 39 pub mod chapter_state {
+2 -2
crates/weaver-api/src/sh_weaver/notebook/entry.rs
··· 26 /// The set of images and records, if any, embedded in the notebook entry. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub embeds: Option<EntryEmbeds<'a>>, 30 #[serde(borrow)] 31 pub path: crate::sh_weaver::notebook::Path<'a>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 - pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 35 #[serde(borrow)] 36 pub title: crate::sh_weaver::notebook::Title<'a>, 37 }
··· 26 /// The set of images and records, if any, embedded in the notebook entry. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub embeds: std::option::Option<EntryEmbeds<'a>>, 30 #[serde(borrow)] 31 pub path: crate::sh_weaver::notebook::Path<'a>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 + pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 35 #[serde(borrow)] 36 pub title: crate::sh_weaver::notebook::Title<'a>, 37 }
+3 -3
crates/weaver-api/src/sh_weaver/notebook/page.rs
··· 20 pub struct Page<'a> { 21 /// Client-declared timestamp when this was originally created. 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 - pub created_at: Option<jacquard_common::types::string::Datetime>, 24 #[serde(borrow)] 25 pub entry_list: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 26 /// The notebook this page belongs to. ··· 28 pub notebook: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 #[serde(borrow)] 31 - pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 - pub title: Option<crate::sh_weaver::notebook::Title<'a>>, 35 } 36 37 pub mod page_state {
··· 20 pub struct Page<'a> { 21 /// Client-declared timestamp when this was originally created. 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 24 #[serde(borrow)] 25 pub entry_list: Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 26 /// The notebook this page belongs to. ··· 28 pub notebook: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 #[serde(borrow)] 31 + pub tags: std::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 32 #[serde(skip_serializing_if = "std::option::Option::is_none")] 33 #[serde(borrow)] 34 + pub title: std::option::Option<crate::sh_weaver::notebook::Title<'a>>, 35 } 36 37 pub mod page_state {
+1 -1
crates/weaver-api/src/sh_weaver/notebook/theme.rs
··· 565 pub dark_scheme: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 566 #[serde(skip_serializing_if = "std::option::Option::is_none")] 567 #[serde(borrow)] 568 - pub default_theme: Option<jacquard_common::CowStr<'a>>, 569 #[serde(borrow)] 570 pub fonts: ThemeFonts<'a>, 571 /// Syntax highlighting theme for light mode
··· 565 pub dark_scheme: crate::com_atproto::repo::strong_ref::StrongRef<'a>, 566 #[serde(skip_serializing_if = "std::option::Option::is_none")] 567 #[serde(borrow)] 568 + pub default_theme: std::option::Option<jacquard_common::CowStr<'a>>, 569 #[serde(borrow)] 570 pub fonts: ThemeFonts<'a>, 571 /// Syntax highlighting theme for light mode
+2 -2
crates/weaver-api/src/tools_ozone/communication.rs
··· 31 pub id: jacquard_common::CowStr<'a>, 32 /// Message language. 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 - pub lang: Option<jacquard_common::types::string::Language>, 35 /// DID of the user who last updated the template. 36 #[serde(borrow)] 37 pub last_updated_by: jacquard_common::types::string::Did<'a>, ··· 41 /// Content of the template, can contain markdown and variable placeholders. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 - pub subject: Option<jacquard_common::CowStr<'a>>, 45 pub updated_at: jacquard_common::types::string::Datetime, 46 } 47
··· 31 pub id: jacquard_common::CowStr<'a>, 32 /// Message language. 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 + pub lang: std::option::Option<jacquard_common::types::string::Language>, 35 /// DID of the user who last updated the template. 36 #[serde(borrow)] 37 pub last_updated_by: jacquard_common::types::string::Did<'a>, ··· 41 /// Content of the template, can contain markdown and variable placeholders. 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 + pub subject: std::option::Option<jacquard_common::CowStr<'a>>, 45 pub updated_at: jacquard_common::types::string::Datetime, 46 } 47
+137 -74
crates/weaver-api/src/tools_ozone/moderation.rs
··· 38 pub active: bool, 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 #[serde(borrow)] 41 - pub comment: Option<jacquard_common::CowStr<'a>>, 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 - pub status: Option<jacquard_common::CowStr<'a>>, 45 pub timestamp: jacquard_common::types::string::Datetime, 46 } 47 ··· 1277 }), 1278 ); 1279 map.insert( 1280 ::jacquard_common::smol_str::SmolStr::new_static("policies"), 1281 ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 1282 description: Some( ··· 2053 r#enum: None, 2054 r#const: None, 2055 known_values: None, 2056 }), 2057 ); 2058 map ··· 4960 /// The IP address used when completing the AA flow. 4961 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4962 #[serde(borrow)] 4963 - pub complete_ip: Option<jacquard_common::CowStr<'a>>, 4964 /// The user agent used when completing the AA flow. 4965 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4966 #[serde(borrow)] 4967 - pub complete_ua: Option<jacquard_common::CowStr<'a>>, 4968 /// The date and time of this write operation. 4969 pub created_at: jacquard_common::types::string::Datetime, 4970 /// The IP address used when initiating the AA flow. 4971 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4972 #[serde(borrow)] 4973 - pub init_ip: Option<jacquard_common::CowStr<'a>>, 4974 /// The user agent used when initiating the AA flow. 4975 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4976 #[serde(borrow)] 4977 - pub init_ua: Option<jacquard_common::CowStr<'a>>, 4978 /// The status of the age assurance process. 4979 #[serde(borrow)] 4980 pub status: jacquard_common::CowStr<'a>, ··· 5312 pub created_at: jacquard_common::types::string::Datetime, 5313 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5314 #[serde(borrow)] 5315 - pub details: Option<BlobViewDetails<'a>>, 5316 #[serde(borrow)] 5317 pub mime_type: jacquard_common::CowStr<'a>, 5318 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5319 #[serde(borrow)] 5320 - pub moderation: Option<crate::tools_ozone::moderation::Moderation<'a>>, 5321 pub size: i64, 5322 } 5323 ··· 5662 pub struct IdentityEvent<'a> { 5663 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5664 #[serde(borrow)] 5665 - pub comment: Option<jacquard_common::CowStr<'a>>, 5666 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5667 #[serde(borrow)] 5668 - pub handle: Option<jacquard_common::types::string::Handle<'a>>, 5669 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5670 #[serde(borrow)] 5671 - pub pds_host: Option<jacquard_common::types::string::Uri<'a>>, 5672 pub timestamp: jacquard_common::types::string::Datetime, 5673 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5674 - pub tombstone: Option<bool>, 5675 } 5676 5677 pub mod identity_event_state { ··· 6180 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6181 #[serde(borrow)] 6182 pub content: std::option::Option<jacquard_common::CowStr<'a>>, 6183 /// Names/Keywords of the policies that necessitated the email. 6184 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6185 #[serde(borrow)] ··· 6278 pub struct ModEventLabel<'a> { 6279 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6280 #[serde(borrow)] 6281 - pub comment: Option<jacquard_common::CowStr<'a>>, 6282 #[serde(borrow)] 6283 pub create_label_vals: Vec<jacquard_common::CowStr<'a>>, 6284 /// Indicates how long the label will remain on the subject. Only applies on labels that are being added. 6285 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6286 - pub duration_in_hours: Option<i64>, 6287 #[serde(borrow)] 6288 pub negate_label_vals: Vec<jacquard_common::CowStr<'a>>, 6289 } ··· 6495 pub struct ModEventMute<'a> { 6496 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6497 #[serde(borrow)] 6498 - pub comment: Option<jacquard_common::CowStr<'a>>, 6499 /// Indicates how long the subject should remain muted. 6500 pub duration_in_hours: i64, 6501 } ··· 6695 pub struct ModEventPriorityScore<'a> { 6696 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6697 #[serde(borrow)] 6698 - pub comment: Option<jacquard_common::CowStr<'a>>, 6699 pub score: i64, 6700 } 6701 ··· 6882 pub struct ModEventReport<'a> { 6883 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6884 #[serde(borrow)] 6885 - pub comment: Option<jacquard_common::CowStr<'a>>, 6886 /// Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject. 6887 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6888 - pub is_reporter_muted: Option<bool>, 6889 #[serde(borrow)] 6890 pub report_type: crate::com_atproto::moderation::ReasonType<'a>, 6891 } ··· 7163 /// Additional comment about added/removed tags. 7164 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7165 #[serde(borrow)] 7166 - pub comment: Option<jacquard_common::CowStr<'a>>, 7167 /// Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. 7168 #[serde(borrow)] 7169 pub remove: Vec<jacquard_common::CowStr<'a>>, ··· 7382 /// When the strike should expire. If not provided, the strike never expires. 7383 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7384 pub strike_expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 7385 } 7386 7387 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ModEventTakedown<'a> { ··· 7504 pub created_by: jacquard_common::types::string::Did<'a>, 7505 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7506 #[serde(borrow)] 7507 - pub creator_handle: Option<jacquard_common::CowStr<'a>>, 7508 #[serde(borrow)] 7509 pub event: ModEventViewEvent<'a>, 7510 pub id: i64, 7511 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7512 #[serde(borrow)] 7513 - pub mod_tool: Option<crate::tools_ozone::moderation::ModTool<'a>>, 7514 #[serde(borrow)] 7515 pub subject: ModEventViewSubject<'a>, 7516 #[serde(borrow)] 7517 pub subject_blob_cids: Vec<jacquard_common::CowStr<'a>>, 7518 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7519 #[serde(borrow)] 7520 - pub subject_handle: Option<jacquard_common::CowStr<'a>>, 7521 } 7522 7523 pub mod mod_event_view_state { ··· 8033 pub id: i64, 8034 #[serde(skip_serializing_if = "std::option::Option::is_none")] 8035 #[serde(borrow)] 8036 - pub mod_tool: Option<crate::tools_ozone::moderation::ModTool<'a>>, 8037 #[serde(borrow)] 8038 pub subject: ModEventViewDetailSubject<'a>, 8039 #[serde(borrow)] ··· 8610 pub struct RecordEvent<'a> { 8611 #[serde(skip_serializing_if = "std::option::Option::is_none")] 8612 #[serde(borrow)] 8613 - pub cid: Option<jacquard_common::types::string::Cid<'a>>, 8614 #[serde(skip_serializing_if = "std::option::Option::is_none")] 8615 #[serde(borrow)] 8616 - pub comment: Option<jacquard_common::CowStr<'a>>, 8617 #[serde(borrow)] 8618 pub op: jacquard_common::CowStr<'a>, 8619 pub timestamp: jacquard_common::types::string::Datetime, ··· 9265 pub indexed_at: jacquard_common::types::string::Datetime, 9266 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9267 #[serde(borrow)] 9268 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 9269 #[serde(borrow)] 9270 pub moderation: crate::tools_ozone::moderation::ModerationDetail<'a>, 9271 #[serde(borrow)] ··· 9868 #[serde(rename_all = "camelCase")] 9869 pub struct RepoView<'a> { 9870 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9871 - pub deactivated_at: Option<jacquard_common::types::string::Datetime>, 9872 #[serde(borrow)] 9873 pub did: jacquard_common::types::string::Did<'a>, 9874 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9875 #[serde(borrow)] 9876 - pub email: Option<jacquard_common::CowStr<'a>>, 9877 #[serde(borrow)] 9878 pub handle: jacquard_common::types::string::Handle<'a>, 9879 pub indexed_at: jacquard_common::types::string::Datetime, 9880 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9881 #[serde(borrow)] 9882 - pub invite_note: Option<jacquard_common::CowStr<'a>>, 9883 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9884 #[serde(borrow)] 9885 - pub invited_by: Option<crate::com_atproto::server::InviteCode<'a>>, 9886 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9887 - pub invites_disabled: Option<bool>, 9888 #[serde(borrow)] 9889 pub moderation: crate::tools_ozone::moderation::Moderation<'a>, 9890 #[serde(borrow)] 9891 pub related_records: Vec<jacquard_common::types::value::Data<'a>>, 9892 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9893 #[serde(borrow)] 9894 - pub threat_signatures: Option<Vec<crate::com_atproto::admin::ThreatSignature<'a>>>, 9895 } 9896 9897 pub mod repo_view_state { ··· 10316 #[serde(rename_all = "camelCase")] 10317 pub struct RepoViewDetail<'a> { 10318 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10319 - pub deactivated_at: Option<jacquard_common::types::string::Datetime>, 10320 #[serde(borrow)] 10321 pub did: jacquard_common::types::string::Did<'a>, 10322 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10323 #[serde(borrow)] 10324 - pub email: Option<jacquard_common::CowStr<'a>>, 10325 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10326 - pub email_confirmed_at: Option<jacquard_common::types::string::Datetime>, 10327 #[serde(borrow)] 10328 pub handle: jacquard_common::types::string::Handle<'a>, 10329 pub indexed_at: jacquard_common::types::string::Datetime, 10330 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10331 #[serde(borrow)] 10332 - pub invite_note: Option<jacquard_common::CowStr<'a>>, 10333 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10334 #[serde(borrow)] 10335 - pub invited_by: Option<crate::com_atproto::server::InviteCode<'a>>, 10336 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10337 #[serde(borrow)] 10338 - pub invites: Option<Vec<crate::com_atproto::server::InviteCode<'a>>>, 10339 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10340 - pub invites_disabled: Option<bool>, 10341 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10342 #[serde(borrow)] 10343 - pub labels: Option<Vec<crate::com_atproto::label::Label<'a>>>, 10344 #[serde(borrow)] 10345 pub moderation: crate::tools_ozone::moderation::ModerationDetail<'a>, 10346 #[serde(borrow)] 10347 pub related_records: Vec<jacquard_common::types::value::Data<'a>>, 10348 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10349 #[serde(borrow)] 10350 - pub threat_signatures: Option<Vec<crate::com_atproto::admin::ThreatSignature<'a>>>, 10351 } 10352 10353 pub mod repo_view_detail_state { ··· 11644 /// Serialized event object that will be propagated to the event when performed 11645 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11646 #[serde(borrow)] 11647 - pub event_data: Option<jacquard_common::types::value::Data<'a>>, 11648 /// Earliest time to execute the action (for randomized scheduling) 11649 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11650 - pub execute_after: Option<jacquard_common::types::string::Datetime>, 11651 /// Exact time to execute the action 11652 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11653 - pub execute_at: Option<jacquard_common::types::string::Datetime>, 11654 /// Latest time to execute the action (for randomized scheduling) 11655 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11656 - pub execute_until: Option<jacquard_common::types::string::Datetime>, 11657 /// ID of the moderation event created when action was successfully executed 11658 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11659 - pub execution_event_id: Option<i64>, 11660 /// Auto-incrementing row ID 11661 pub id: i64, 11662 /// When the action was last attempted to be executed 11663 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11664 - pub last_executed_at: Option<jacquard_common::types::string::Datetime>, 11665 /// Reason for the last execution failure 11666 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11667 #[serde(borrow)] 11668 - pub last_failure_reason: Option<jacquard_common::CowStr<'a>>, 11669 /// Whether execution time should be randomized within the specified range 11670 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11671 - pub randomize_execution: Option<bool>, 11672 /// Current status of the scheduled action 11673 #[serde(borrow)] 11674 pub status: jacquard_common::CowStr<'a>, 11675 /// When the scheduled action was last updated 11676 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11677 - pub updated_at: Option<jacquard_common::types::string::Datetime>, 11678 } 11679 11680 pub mod scheduled_action_view_state { ··· 12297 /// Statistics related to the account subject 12298 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12299 #[serde(borrow)] 12300 - pub account_stats: Option<crate::tools_ozone::moderation::AccountStats<'a>>, 12301 /// Strike information for the account (account-level only) 12302 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12303 #[serde(borrow)] 12304 - pub account_strike: Option<crate::tools_ozone::moderation::AccountStrike<'a>>, 12305 /// Current age assurance state of the subject. 12306 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12307 #[serde(borrow)] 12308 - pub age_assurance_state: Option<jacquard_common::CowStr<'a>>, 12309 /// Whether or not the last successful update to age assurance was made by the user or admin. 12310 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12311 #[serde(borrow)] 12312 - pub age_assurance_updated_by: Option<jacquard_common::CowStr<'a>>, 12313 /// True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. 12314 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12315 - pub appealed: Option<bool>, 12316 /// Sticky comment on the subject. 12317 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12318 #[serde(borrow)] 12319 - pub comment: Option<jacquard_common::CowStr<'a>>, 12320 /// Timestamp referencing the first moderation status impacting event was emitted on the subject 12321 pub created_at: jacquard_common::types::string::Datetime, 12322 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12323 #[serde(borrow)] 12324 - pub hosting: Option<SubjectStatusViewHosting<'a>>, 12325 pub id: i64, 12326 /// Timestamp referencing when the author of the subject appealed a moderation action 12327 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12328 - pub last_appealed_at: Option<jacquard_common::types::string::Datetime>, 12329 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12330 - pub last_reported_at: Option<jacquard_common::types::string::Datetime>, 12331 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12332 - pub last_reviewed_at: Option<jacquard_common::types::string::Datetime>, 12333 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12334 #[serde(borrow)] 12335 - pub last_reviewed_by: Option<jacquard_common::types::string::Did<'a>>, 12336 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12337 - pub mute_reporting_until: Option<jacquard_common::types::string::Datetime>, 12338 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12339 - pub mute_until: Option<jacquard_common::types::string::Datetime>, 12340 /// Numeric value representing the level of priority. Higher score means higher priority. 12341 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12342 - pub priority_score: Option<i64>, 12343 /// Statistics related to the record subjects authored by the subject's account 12344 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12345 #[serde(borrow)] 12346 - pub records_stats: Option<crate::tools_ozone::moderation::RecordsStats<'a>>, 12347 #[serde(borrow)] 12348 pub review_state: crate::tools_ozone::moderation::SubjectReviewState<'a>, 12349 #[serde(borrow)] 12350 pub subject: SubjectStatusViewSubject<'a>, 12351 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12352 #[serde(borrow)] 12353 - pub subject_blob_cids: Option<Vec<jacquard_common::types::string::Cid<'a>>>, 12354 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12355 #[serde(borrow)] 12356 - pub subject_repo_handle: Option<jacquard_common::CowStr<'a>>, 12357 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12358 - pub suspend_until: Option<jacquard_common::types::string::Datetime>, 12359 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12360 #[serde(borrow)] 12361 - pub tags: Option<Vec<jacquard_common::CowStr<'a>>>, 12362 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12363 - pub takendown: Option<bool>, 12364 /// Timestamp referencing when the last update was made to the moderation status of the subject 12365 pub updated_at: jacquard_common::types::string::Datetime, 12366 } ··· 13158 pub struct SubjectView<'a> { 13159 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13160 #[serde(borrow)] 13161 - pub profile: Option<jacquard_common::types::value::Data<'a>>, 13162 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13163 #[serde(borrow)] 13164 - pub record: Option<crate::tools_ozone::moderation::RecordViewDetail<'a>>, 13165 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13166 #[serde(borrow)] 13167 - pub repo: Option<crate::tools_ozone::moderation::RepoViewDetail<'a>>, 13168 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13169 #[serde(borrow)] 13170 - pub status: Option<crate::tools_ozone::moderation::SubjectStatusView<'a>>, 13171 #[serde(borrow)] 13172 pub subject: jacquard_common::CowStr<'a>, 13173 #[serde(borrow)]
··· 38 pub active: bool, 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 #[serde(borrow)] 41 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 #[serde(borrow)] 44 + pub status: std::option::Option<jacquard_common::CowStr<'a>>, 45 pub timestamp: jacquard_common::types::string::Datetime, 46 } 47 ··· 1277 }), 1278 ); 1279 map.insert( 1280 + ::jacquard_common::smol_str::SmolStr::new_static( 1281 + "isDelivered", 1282 + ), 1283 + ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 1284 + description: None, 1285 + default: None, 1286 + r#const: None, 1287 + }), 1288 + ); 1289 + map.insert( 1290 ::jacquard_common::smol_str::SmolStr::new_static("policies"), 1291 ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 1292 description: Some( ··· 2063 r#enum: None, 2064 r#const: None, 2065 known_values: None, 2066 + }), 2067 + ); 2068 + map.insert( 2069 + ::jacquard_common::smol_str::SmolStr::new_static( 2070 + "targetServices", 2071 + ), 2072 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 2073 + description: Some( 2074 + ::jacquard_common::CowStr::new_static( 2075 + "List of services where the takedown should be applied. If empty or not provided, takedown is applied on all configured services.", 2076 + ), 2077 + ), 2078 + items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString { 2079 + description: None, 2080 + format: None, 2081 + default: None, 2082 + min_length: None, 2083 + max_length: None, 2084 + min_graphemes: None, 2085 + max_graphemes: None, 2086 + r#enum: None, 2087 + r#const: None, 2088 + known_values: None, 2089 + }), 2090 + min_length: None, 2091 + max_length: None, 2092 }), 2093 ); 2094 map ··· 4996 /// The IP address used when completing the AA flow. 4997 #[serde(skip_serializing_if = "std::option::Option::is_none")] 4998 #[serde(borrow)] 4999 + pub complete_ip: std::option::Option<jacquard_common::CowStr<'a>>, 5000 /// The user agent used when completing the AA flow. 5001 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5002 #[serde(borrow)] 5003 + pub complete_ua: std::option::Option<jacquard_common::CowStr<'a>>, 5004 /// The date and time of this write operation. 5005 pub created_at: jacquard_common::types::string::Datetime, 5006 /// The IP address used when initiating the AA flow. 5007 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5008 #[serde(borrow)] 5009 + pub init_ip: std::option::Option<jacquard_common::CowStr<'a>>, 5010 /// The user agent used when initiating the AA flow. 5011 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5012 #[serde(borrow)] 5013 + pub init_ua: std::option::Option<jacquard_common::CowStr<'a>>, 5014 /// The status of the age assurance process. 5015 #[serde(borrow)] 5016 pub status: jacquard_common::CowStr<'a>, ··· 5348 pub created_at: jacquard_common::types::string::Datetime, 5349 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5350 #[serde(borrow)] 5351 + pub details: std::option::Option<BlobViewDetails<'a>>, 5352 #[serde(borrow)] 5353 pub mime_type: jacquard_common::CowStr<'a>, 5354 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5355 #[serde(borrow)] 5356 + pub moderation: std::option::Option<crate::tools_ozone::moderation::Moderation<'a>>, 5357 pub size: i64, 5358 } 5359 ··· 5698 pub struct IdentityEvent<'a> { 5699 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5700 #[serde(borrow)] 5701 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 5702 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5703 #[serde(borrow)] 5704 + pub handle: std::option::Option<jacquard_common::types::string::Handle<'a>>, 5705 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5706 #[serde(borrow)] 5707 + pub pds_host: std::option::Option<jacquard_common::types::string::Uri<'a>>, 5708 pub timestamp: jacquard_common::types::string::Datetime, 5709 #[serde(skip_serializing_if = "std::option::Option::is_none")] 5710 + pub tombstone: std::option::Option<bool>, 5711 } 5712 5713 pub mod identity_event_state { ··· 6216 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6217 #[serde(borrow)] 6218 pub content: std::option::Option<jacquard_common::CowStr<'a>>, 6219 + /// Indicates whether the email was successfully delivered to the user's inbox. 6220 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 6221 + pub is_delivered: std::option::Option<bool>, 6222 /// Names/Keywords of the policies that necessitated the email. 6223 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6224 #[serde(borrow)] ··· 6317 pub struct ModEventLabel<'a> { 6318 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6319 #[serde(borrow)] 6320 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 6321 #[serde(borrow)] 6322 pub create_label_vals: Vec<jacquard_common::CowStr<'a>>, 6323 /// Indicates how long the label will remain on the subject. Only applies on labels that are being added. 6324 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6325 + pub duration_in_hours: std::option::Option<i64>, 6326 #[serde(borrow)] 6327 pub negate_label_vals: Vec<jacquard_common::CowStr<'a>>, 6328 } ··· 6534 pub struct ModEventMute<'a> { 6535 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6536 #[serde(borrow)] 6537 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 6538 /// Indicates how long the subject should remain muted. 6539 pub duration_in_hours: i64, 6540 } ··· 6734 pub struct ModEventPriorityScore<'a> { 6735 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6736 #[serde(borrow)] 6737 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 6738 pub score: i64, 6739 } 6740 ··· 6921 pub struct ModEventReport<'a> { 6922 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6923 #[serde(borrow)] 6924 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 6925 /// Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject. 6926 #[serde(skip_serializing_if = "std::option::Option::is_none")] 6927 + pub is_reporter_muted: std::option::Option<bool>, 6928 #[serde(borrow)] 6929 pub report_type: crate::com_atproto::moderation::ReasonType<'a>, 6930 } ··· 7202 /// Additional comment about added/removed tags. 7203 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7204 #[serde(borrow)] 7205 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 7206 /// Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. 7207 #[serde(borrow)] 7208 pub remove: Vec<jacquard_common::CowStr<'a>>, ··· 7421 /// When the strike should expire. If not provided, the strike never expires. 7422 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7423 pub strike_expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 7424 + /// List of services where the takedown should be applied. If empty or not provided, takedown is applied on all configured services. 7425 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 7426 + #[serde(borrow)] 7427 + pub target_services: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 7428 } 7429 7430 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ModEventTakedown<'a> { ··· 7547 pub created_by: jacquard_common::types::string::Did<'a>, 7548 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7549 #[serde(borrow)] 7550 + pub creator_handle: std::option::Option<jacquard_common::CowStr<'a>>, 7551 #[serde(borrow)] 7552 pub event: ModEventViewEvent<'a>, 7553 pub id: i64, 7554 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7555 #[serde(borrow)] 7556 + pub mod_tool: std::option::Option<crate::tools_ozone::moderation::ModTool<'a>>, 7557 #[serde(borrow)] 7558 pub subject: ModEventViewSubject<'a>, 7559 #[serde(borrow)] 7560 pub subject_blob_cids: Vec<jacquard_common::CowStr<'a>>, 7561 #[serde(skip_serializing_if = "std::option::Option::is_none")] 7562 #[serde(borrow)] 7563 + pub subject_handle: std::option::Option<jacquard_common::CowStr<'a>>, 7564 } 7565 7566 pub mod mod_event_view_state { ··· 8076 pub id: i64, 8077 #[serde(skip_serializing_if = "std::option::Option::is_none")] 8078 #[serde(borrow)] 8079 + pub mod_tool: std::option::Option<crate::tools_ozone::moderation::ModTool<'a>>, 8080 #[serde(borrow)] 8081 pub subject: ModEventViewDetailSubject<'a>, 8082 #[serde(borrow)] ··· 8653 pub struct RecordEvent<'a> { 8654 #[serde(skip_serializing_if = "std::option::Option::is_none")] 8655 #[serde(borrow)] 8656 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 8657 #[serde(skip_serializing_if = "std::option::Option::is_none")] 8658 #[serde(borrow)] 8659 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 8660 #[serde(borrow)] 8661 pub op: jacquard_common::CowStr<'a>, 8662 pub timestamp: jacquard_common::types::string::Datetime, ··· 9308 pub indexed_at: jacquard_common::types::string::Datetime, 9309 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9310 #[serde(borrow)] 9311 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 9312 #[serde(borrow)] 9313 pub moderation: crate::tools_ozone::moderation::ModerationDetail<'a>, 9314 #[serde(borrow)] ··· 9911 #[serde(rename_all = "camelCase")] 9912 pub struct RepoView<'a> { 9913 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9914 + pub deactivated_at: std::option::Option<jacquard_common::types::string::Datetime>, 9915 #[serde(borrow)] 9916 pub did: jacquard_common::types::string::Did<'a>, 9917 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9918 #[serde(borrow)] 9919 + pub email: std::option::Option<jacquard_common::CowStr<'a>>, 9920 #[serde(borrow)] 9921 pub handle: jacquard_common::types::string::Handle<'a>, 9922 pub indexed_at: jacquard_common::types::string::Datetime, 9923 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9924 #[serde(borrow)] 9925 + pub invite_note: std::option::Option<jacquard_common::CowStr<'a>>, 9926 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9927 #[serde(borrow)] 9928 + pub invited_by: std::option::Option<crate::com_atproto::server::InviteCode<'a>>, 9929 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9930 + pub invites_disabled: std::option::Option<bool>, 9931 #[serde(borrow)] 9932 pub moderation: crate::tools_ozone::moderation::Moderation<'a>, 9933 #[serde(borrow)] 9934 pub related_records: Vec<jacquard_common::types::value::Data<'a>>, 9935 #[serde(skip_serializing_if = "std::option::Option::is_none")] 9936 #[serde(borrow)] 9937 + pub threat_signatures: std::option::Option< 9938 + Vec<crate::com_atproto::admin::ThreatSignature<'a>>, 9939 + >, 9940 } 9941 9942 pub mod repo_view_state { ··· 10361 #[serde(rename_all = "camelCase")] 10362 pub struct RepoViewDetail<'a> { 10363 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10364 + pub deactivated_at: std::option::Option<jacquard_common::types::string::Datetime>, 10365 #[serde(borrow)] 10366 pub did: jacquard_common::types::string::Did<'a>, 10367 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10368 #[serde(borrow)] 10369 + pub email: std::option::Option<jacquard_common::CowStr<'a>>, 10370 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10371 + pub email_confirmed_at: std::option::Option< 10372 + jacquard_common::types::string::Datetime, 10373 + >, 10374 #[serde(borrow)] 10375 pub handle: jacquard_common::types::string::Handle<'a>, 10376 pub indexed_at: jacquard_common::types::string::Datetime, 10377 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10378 #[serde(borrow)] 10379 + pub invite_note: std::option::Option<jacquard_common::CowStr<'a>>, 10380 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10381 #[serde(borrow)] 10382 + pub invited_by: std::option::Option<crate::com_atproto::server::InviteCode<'a>>, 10383 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10384 #[serde(borrow)] 10385 + pub invites: std::option::Option<Vec<crate::com_atproto::server::InviteCode<'a>>>, 10386 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10387 + pub invites_disabled: std::option::Option<bool>, 10388 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10389 #[serde(borrow)] 10390 + pub labels: std::option::Option<Vec<crate::com_atproto::label::Label<'a>>>, 10391 #[serde(borrow)] 10392 pub moderation: crate::tools_ozone::moderation::ModerationDetail<'a>, 10393 #[serde(borrow)] 10394 pub related_records: Vec<jacquard_common::types::value::Data<'a>>, 10395 #[serde(skip_serializing_if = "std::option::Option::is_none")] 10396 #[serde(borrow)] 10397 + pub threat_signatures: std::option::Option< 10398 + Vec<crate::com_atproto::admin::ThreatSignature<'a>>, 10399 + >, 10400 } 10401 10402 pub mod repo_view_detail_state { ··· 11693 /// Serialized event object that will be propagated to the event when performed 11694 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11695 #[serde(borrow)] 11696 + pub event_data: std::option::Option<jacquard_common::types::value::Data<'a>>, 11697 /// Earliest time to execute the action (for randomized scheduling) 11698 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11699 + pub execute_after: std::option::Option<jacquard_common::types::string::Datetime>, 11700 /// Exact time to execute the action 11701 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11702 + pub execute_at: std::option::Option<jacquard_common::types::string::Datetime>, 11703 /// Latest time to execute the action (for randomized scheduling) 11704 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11705 + pub execute_until: std::option::Option<jacquard_common::types::string::Datetime>, 11706 /// ID of the moderation event created when action was successfully executed 11707 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11708 + pub execution_event_id: std::option::Option<i64>, 11709 /// Auto-incrementing row ID 11710 pub id: i64, 11711 /// When the action was last attempted to be executed 11712 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11713 + pub last_executed_at: std::option::Option<jacquard_common::types::string::Datetime>, 11714 /// Reason for the last execution failure 11715 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11716 #[serde(borrow)] 11717 + pub last_failure_reason: std::option::Option<jacquard_common::CowStr<'a>>, 11718 /// Whether execution time should be randomized within the specified range 11719 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11720 + pub randomize_execution: std::option::Option<bool>, 11721 /// Current status of the scheduled action 11722 #[serde(borrow)] 11723 pub status: jacquard_common::CowStr<'a>, 11724 /// When the scheduled action was last updated 11725 #[serde(skip_serializing_if = "std::option::Option::is_none")] 11726 + pub updated_at: std::option::Option<jacquard_common::types::string::Datetime>, 11727 } 11728 11729 pub mod scheduled_action_view_state { ··· 12346 /// Statistics related to the account subject 12347 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12348 #[serde(borrow)] 12349 + pub account_stats: std::option::Option< 12350 + crate::tools_ozone::moderation::AccountStats<'a>, 12351 + >, 12352 /// Strike information for the account (account-level only) 12353 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12354 #[serde(borrow)] 12355 + pub account_strike: std::option::Option< 12356 + crate::tools_ozone::moderation::AccountStrike<'a>, 12357 + >, 12358 /// Current age assurance state of the subject. 12359 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12360 #[serde(borrow)] 12361 + pub age_assurance_state: std::option::Option<jacquard_common::CowStr<'a>>, 12362 /// Whether or not the last successful update to age assurance was made by the user or admin. 12363 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12364 #[serde(borrow)] 12365 + pub age_assurance_updated_by: std::option::Option<jacquard_common::CowStr<'a>>, 12366 /// True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. 12367 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12368 + pub appealed: std::option::Option<bool>, 12369 /// Sticky comment on the subject. 12370 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12371 #[serde(borrow)] 12372 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 12373 /// Timestamp referencing the first moderation status impacting event was emitted on the subject 12374 pub created_at: jacquard_common::types::string::Datetime, 12375 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12376 #[serde(borrow)] 12377 + pub hosting: std::option::Option<SubjectStatusViewHosting<'a>>, 12378 pub id: i64, 12379 /// Timestamp referencing when the author of the subject appealed a moderation action 12380 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12381 + pub last_appealed_at: std::option::Option<jacquard_common::types::string::Datetime>, 12382 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12383 + pub last_reported_at: std::option::Option<jacquard_common::types::string::Datetime>, 12384 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12385 + pub last_reviewed_at: std::option::Option<jacquard_common::types::string::Datetime>, 12386 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12387 #[serde(borrow)] 12388 + pub last_reviewed_by: std::option::Option<jacquard_common::types::string::Did<'a>>, 12389 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12390 + pub mute_reporting_until: std::option::Option< 12391 + jacquard_common::types::string::Datetime, 12392 + >, 12393 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12394 + pub mute_until: std::option::Option<jacquard_common::types::string::Datetime>, 12395 /// Numeric value representing the level of priority. Higher score means higher priority. 12396 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12397 + pub priority_score: std::option::Option<i64>, 12398 /// Statistics related to the record subjects authored by the subject's account 12399 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12400 #[serde(borrow)] 12401 + pub records_stats: std::option::Option< 12402 + crate::tools_ozone::moderation::RecordsStats<'a>, 12403 + >, 12404 #[serde(borrow)] 12405 pub review_state: crate::tools_ozone::moderation::SubjectReviewState<'a>, 12406 #[serde(borrow)] 12407 pub subject: SubjectStatusViewSubject<'a>, 12408 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12409 #[serde(borrow)] 12410 + pub subject_blob_cids: std::option::Option< 12411 + Vec<jacquard_common::types::string::Cid<'a>>, 12412 + >, 12413 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12414 #[serde(borrow)] 12415 + pub subject_repo_handle: std::option::Option<jacquard_common::CowStr<'a>>, 12416 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12417 + pub suspend_until: std::option::Option<jacquard_common::types::string::Datetime>, 12418 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12419 #[serde(borrow)] 12420 + pub tags: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 12421 #[serde(skip_serializing_if = "std::option::Option::is_none")] 12422 + pub takendown: std::option::Option<bool>, 12423 /// Timestamp referencing when the last update was made to the moderation status of the subject 12424 pub updated_at: jacquard_common::types::string::Datetime, 12425 } ··· 13217 pub struct SubjectView<'a> { 13218 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13219 #[serde(borrow)] 13220 + pub profile: std::option::Option<jacquard_common::types::value::Data<'a>>, 13221 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13222 #[serde(borrow)] 13223 + pub record: std::option::Option< 13224 + crate::tools_ozone::moderation::RecordViewDetail<'a>, 13225 + >, 13226 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13227 #[serde(borrow)] 13228 + pub repo: std::option::Option<crate::tools_ozone::moderation::RepoViewDetail<'a>>, 13229 #[serde(skip_serializing_if = "std::option::Option::is_none")] 13230 #[serde(borrow)] 13231 + pub status: std::option::Option< 13232 + crate::tools_ozone::moderation::SubjectStatusView<'a>, 13233 + >, 13234 #[serde(borrow)] 13235 pub subject: jacquard_common::CowStr<'a>, 13236 #[serde(borrow)]
+2 -2
crates/weaver-api/src/tools_ozone/moderation/cancel_scheduled_actions.rs
··· 440 pub error: jacquard_common::CowStr<'a>, 441 #[serde(skip_serializing_if = "std::option::Option::is_none")] 442 #[serde(borrow)] 443 - pub error_code: Option<jacquard_common::CowStr<'a>>, 444 } 445 446 pub mod failed_cancellation_state { ··· 637 /// Optional comment describing the reason for cancellation 638 #[serde(skip_serializing_if = "std::option::Option::is_none")] 639 #[serde(borrow)] 640 - pub comment: Option<jacquard_common::CowStr<'a>>, 641 /// Array of DID subjects to cancel scheduled actions for 642 #[serde(borrow)] 643 pub subjects: Vec<jacquard_common::types::string::Did<'a>>,
··· 440 pub error: jacquard_common::CowStr<'a>, 441 #[serde(skip_serializing_if = "std::option::Option::is_none")] 442 #[serde(borrow)] 443 + pub error_code: std::option::Option<jacquard_common::CowStr<'a>>, 444 } 445 446 pub mod failed_cancellation_state { ··· 637 /// Optional comment describing the reason for cancellation 638 #[serde(skip_serializing_if = "std::option::Option::is_none")] 639 #[serde(borrow)] 640 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 641 /// Array of DID subjects to cancel scheduled actions for 642 #[serde(borrow)] 643 pub subjects: Vec<jacquard_common::types::string::Did<'a>>,
+5 -3
crates/weaver-api/src/tools_ozone/moderation/emit_event.rs
··· 24 /// An optional external ID for the event, used to deduplicate events from external systems. Fails when an event of same type with the same external ID exists for the same subject. 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 - pub external_id: Option<jacquard_common::CowStr<'a>>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 - pub mod_tool: Option<crate::tools_ozone::moderation::ModTool<'a>>, 31 #[serde(borrow)] 32 pub subject: EmitEventSubject<'a>, 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 #[serde(borrow)] 35 - pub subject_blob_cids: Option<Vec<jacquard_common::types::string::Cid<'a>>>, 36 } 37 38 pub mod emit_event_state {
··· 24 /// An optional external ID for the event, used to deduplicate events from external systems. Fails when an event of same type with the same external ID exists for the same subject. 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 + pub external_id: std::option::Option<jacquard_common::CowStr<'a>>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 + pub mod_tool: std::option::Option<crate::tools_ozone::moderation::ModTool<'a>>, 31 #[serde(borrow)] 32 pub subject: EmitEventSubject<'a>, 33 #[serde(skip_serializing_if = "std::option::Option::is_none")] 34 #[serde(borrow)] 35 + pub subject_blob_cids: std::option::Option< 36 + Vec<jacquard_common::types::string::Cid<'a>>, 37 + >, 38 } 39 40 pub mod emit_event_state {
+5 -5
crates/weaver-api/src/tools_ozone/moderation/list_scheduled_actions.rs
··· 20 /// Cursor for pagination 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 - pub cursor: Option<jacquard_common::CowStr<'a>>, 24 /// Filter actions scheduled to execute before this time 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 - pub ends_before: Option<jacquard_common::types::string::Datetime>, 27 /// Maximum number of results to return 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 - pub limit: Option<i64>, 30 /// Filter actions scheduled to execute after this time 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 - pub starts_after: Option<jacquard_common::types::string::Datetime>, 33 /// Filter actions by status 34 #[serde(borrow)] 35 pub statuses: Vec<jacquard_common::CowStr<'a>>, 36 /// Filter actions for specific DID subjects 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 #[serde(borrow)] 39 - pub subjects: Option<Vec<jacquard_common::types::string::Did<'a>>>, 40 } 41 42 pub mod list_scheduled_actions_state {
··· 20 /// Cursor for pagination 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 + pub cursor: std::option::Option<jacquard_common::CowStr<'a>>, 24 /// Filter actions scheduled to execute before this time 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 + pub ends_before: std::option::Option<jacquard_common::types::string::Datetime>, 27 /// Maximum number of results to return 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 + pub limit: std::option::Option<i64>, 30 /// Filter actions scheduled to execute after this time 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 + pub starts_after: std::option::Option<jacquard_common::types::string::Datetime>, 33 /// Filter actions by status 34 #[serde(borrow)] 35 pub statuses: Vec<jacquard_common::CowStr<'a>>, 36 /// Filter actions for specific DID subjects 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 #[serde(borrow)] 39 + pub subjects: std::option::Option<Vec<jacquard_common::types::string::Did<'a>>>, 40 } 41 42 pub mod list_scheduled_actions_state {
+119 -2
crates/weaver-api/src/tools_ozone/moderation/schedule_action.rs
··· 21 pub error: jacquard_common::CowStr<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 - pub error_code: Option<jacquard_common::CowStr<'a>>, 25 #[serde(borrow)] 26 pub subject: jacquard_common::types::string::Did<'a>, 27 } ··· 574 }), 575 ); 576 map.insert( 577 ::jacquard_common::smol_str::SmolStr::new_static("policies"), 578 ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 579 description: Some( ··· 597 max_length: Some(5usize), 598 }), 599 ); 600 map 601 }, 602 }), ··· 642 /// This will be propagated to the moderation event when it is applied 643 #[serde(skip_serializing_if = "std::option::Option::is_none")] 644 #[serde(borrow)] 645 - pub mod_tool: Option<crate::tools_ozone::moderation::ModTool<'a>>, 646 #[serde(borrow)] 647 pub scheduling: crate::tools_ozone::moderation::schedule_action::SchedulingConfig< 648 'a, ··· 1198 /// Indicates how long the takedown should be in effect before automatically expiring. 1199 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1200 pub duration_in_hours: std::option::Option<i64>, 1201 /// Names/Keywords of the policies that drove the decision. 1202 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1203 #[serde(borrow)] 1204 pub policies: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 1205 } 1206 1207 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Takedown<'a> {
··· 21 pub error: jacquard_common::CowStr<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 #[serde(borrow)] 24 + pub error_code: std::option::Option<jacquard_common::CowStr<'a>>, 25 #[serde(borrow)] 26 pub subject: jacquard_common::types::string::Did<'a>, 27 } ··· 574 }), 575 ); 576 map.insert( 577 + ::jacquard_common::smol_str::SmolStr::new_static( 578 + "emailContent", 579 + ), 580 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 581 + description: Some( 582 + ::jacquard_common::CowStr::new_static( 583 + "Email content to be sent to the user upon takedown.", 584 + ), 585 + ), 586 + format: None, 587 + default: None, 588 + min_length: None, 589 + max_length: None, 590 + min_graphemes: None, 591 + max_graphemes: None, 592 + r#enum: None, 593 + r#const: None, 594 + known_values: None, 595 + }), 596 + ); 597 + map.insert( 598 + ::jacquard_common::smol_str::SmolStr::new_static( 599 + "emailSubject", 600 + ), 601 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 602 + description: Some( 603 + ::jacquard_common::CowStr::new_static( 604 + "Subject of the email to be sent to the user upon takedown.", 605 + ), 606 + ), 607 + format: None, 608 + default: None, 609 + min_length: None, 610 + max_length: None, 611 + min_graphemes: None, 612 + max_graphemes: None, 613 + r#enum: None, 614 + r#const: None, 615 + known_values: None, 616 + }), 617 + ); 618 + map.insert( 619 ::jacquard_common::smol_str::SmolStr::new_static("policies"), 620 ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 621 description: Some( ··· 639 max_length: Some(5usize), 640 }), 641 ); 642 + map.insert( 643 + ::jacquard_common::smol_str::SmolStr::new_static( 644 + "severityLevel", 645 + ), 646 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 647 + description: Some( 648 + ::jacquard_common::CowStr::new_static( 649 + "Severity level of the violation (e.g., 'sev-0', 'sev-1', 'sev-2', etc.).", 650 + ), 651 + ), 652 + format: None, 653 + default: None, 654 + min_length: None, 655 + max_length: None, 656 + min_graphemes: None, 657 + max_graphemes: None, 658 + r#enum: None, 659 + r#const: None, 660 + known_values: None, 661 + }), 662 + ); 663 + map.insert( 664 + ::jacquard_common::smol_str::SmolStr::new_static( 665 + "strikeCount", 666 + ), 667 + ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 668 + description: None, 669 + default: None, 670 + minimum: None, 671 + maximum: None, 672 + r#enum: None, 673 + r#const: None, 674 + }), 675 + ); 676 + map.insert( 677 + ::jacquard_common::smol_str::SmolStr::new_static( 678 + "strikeExpiresAt", 679 + ), 680 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 681 + description: Some( 682 + ::jacquard_common::CowStr::new_static( 683 + "When the strike should expire. If not provided, the strike never expires.", 684 + ), 685 + ), 686 + format: Some( 687 + ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 688 + ), 689 + default: None, 690 + min_length: None, 691 + max_length: None, 692 + min_graphemes: None, 693 + max_graphemes: None, 694 + r#enum: None, 695 + r#const: None, 696 + known_values: None, 697 + }), 698 + ); 699 map 700 }, 701 }), ··· 741 /// This will be propagated to the moderation event when it is applied 742 #[serde(skip_serializing_if = "std::option::Option::is_none")] 743 #[serde(borrow)] 744 + pub mod_tool: std::option::Option<crate::tools_ozone::moderation::ModTool<'a>>, 745 #[serde(borrow)] 746 pub scheduling: crate::tools_ozone::moderation::schedule_action::SchedulingConfig< 747 'a, ··· 1297 /// Indicates how long the takedown should be in effect before automatically expiring. 1298 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1299 pub duration_in_hours: std::option::Option<i64>, 1300 + /// Email content to be sent to the user upon takedown. 1301 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1302 + #[serde(borrow)] 1303 + pub email_content: std::option::Option<jacquard_common::CowStr<'a>>, 1304 + /// Subject of the email to be sent to the user upon takedown. 1305 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1306 + #[serde(borrow)] 1307 + pub email_subject: std::option::Option<jacquard_common::CowStr<'a>>, 1308 /// Names/Keywords of the policies that drove the decision. 1309 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1310 #[serde(borrow)] 1311 pub policies: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 1312 + /// Severity level of the violation (e.g., 'sev-0', 'sev-1', 'sev-2', etc.). 1313 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1314 + #[serde(borrow)] 1315 + pub severity_level: std::option::Option<jacquard_common::CowStr<'a>>, 1316 + /// Number of strikes to assign to the user when takedown is applied. 1317 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1318 + pub strike_count: std::option::Option<i64>, 1319 + /// When the strike should expire. If not provided, the strike never expires. 1320 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1321 + pub strike_expires_at: std::option::Option<jacquard_common::types::string::Datetime>, 1322 } 1323 1324 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Takedown<'a> {
+2 -2
crates/weaver-api/src/tools_ozone/safelink.rs
··· 110 /// Optional comment about the decision 111 #[serde(skip_serializing_if = "std::option::Option::is_none")] 112 #[serde(borrow)] 113 - pub comment: Option<jacquard_common::CowStr<'a>>, 114 pub created_at: jacquard_common::types::string::Datetime, 115 /// DID of the user who created this rule 116 #[serde(borrow)] ··· 1206 /// Optional comment about the decision 1207 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1208 #[serde(borrow)] 1209 - pub comment: Option<jacquard_common::CowStr<'a>>, 1210 /// Timestamp when the rule was created 1211 pub created_at: jacquard_common::types::string::Datetime, 1212 /// DID of the user added the rule.
··· 110 /// Optional comment about the decision 111 #[serde(skip_serializing_if = "std::option::Option::is_none")] 112 #[serde(borrow)] 113 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 114 pub created_at: jacquard_common::types::string::Datetime, 115 /// DID of the user who created this rule 116 #[serde(borrow)] ··· 1206 /// Optional comment about the decision 1207 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1208 #[serde(borrow)] 1209 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 1210 /// Timestamp when the rule was created 1211 pub created_at: jacquard_common::types::string::Datetime, 1212 /// DID of the user added the rule.
+2 -2
crates/weaver-api/src/tools_ozone/safelink/add_rule.rs
··· 22 /// Optional comment about the decision 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub comment: Option<jacquard_common::CowStr<'a>>, 26 /// Author DID. Only respected when using admin auth 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub created_by: Option<jacquard_common::types::string::Did<'a>>, 30 #[serde(borrow)] 31 pub pattern: crate::tools_ozone::safelink::PatternType<'a>, 32 #[serde(borrow)]
··· 22 /// Optional comment about the decision 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 26 /// Author DID. Only respected when using admin auth 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub created_by: std::option::Option<jacquard_common::types::string::Did<'a>>, 30 #[serde(borrow)] 31 pub pattern: crate::tools_ozone::safelink::PatternType<'a>, 32 #[serde(borrow)]
+2 -2
crates/weaver-api/src/tools_ozone/safelink/remove_rule.rs
··· 20 /// Optional comment about why the rule is being removed 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 - pub comment: Option<jacquard_common::CowStr<'a>>, 24 /// Optional DID of the user. Only respected when using admin auth. 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 - pub created_by: Option<jacquard_common::types::string::Did<'a>>, 28 #[serde(borrow)] 29 pub pattern: crate::tools_ozone::safelink::PatternType<'a>, 30 /// The URL or domain to remove the rule for
··· 20 /// Optional comment about why the rule is being removed 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 24 /// Optional DID of the user. Only respected when using admin auth. 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 + pub created_by: std::option::Option<jacquard_common::types::string::Did<'a>>, 28 #[serde(borrow)] 29 pub pattern: crate::tools_ozone::safelink::PatternType<'a>, 30 /// The URL or domain to remove the rule for
+2 -2
crates/weaver-api/src/tools_ozone/safelink/update_rule.rs
··· 22 /// Optional comment about the update 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 - pub comment: Option<jacquard_common::CowStr<'a>>, 26 /// Optional DID to credit as the creator. Only respected for admin_token authentication. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 - pub created_by: Option<jacquard_common::types::string::Did<'a>>, 30 #[serde(borrow)] 31 pub pattern: crate::tools_ozone::safelink::PatternType<'a>, 32 #[serde(borrow)]
··· 22 /// Optional comment about the update 23 #[serde(skip_serializing_if = "std::option::Option::is_none")] 24 #[serde(borrow)] 25 + pub comment: std::option::Option<jacquard_common::CowStr<'a>>, 26 /// Optional DID to credit as the creator. Only respected for admin_token authentication. 27 #[serde(skip_serializing_if = "std::option::Option::is_none")] 28 #[serde(borrow)] 29 + pub created_by: std::option::Option<jacquard_common::types::string::Did<'a>>, 30 #[serde(borrow)] 31 pub pattern: crate::tools_ozone::safelink::PatternType<'a>, 32 #[serde(borrow)]
+1 -1
crates/weaver-api/src/tools_ozone/set.rs
··· 283 pub created_at: jacquard_common::types::string::Datetime, 284 #[serde(skip_serializing_if = "std::option::Option::is_none")] 285 #[serde(borrow)] 286 - pub description: Option<jacquard_common::CowStr<'a>>, 287 #[serde(borrow)] 288 pub name: jacquard_common::CowStr<'a>, 289 pub set_size: i64,
··· 283 pub created_at: jacquard_common::types::string::Datetime, 284 #[serde(skip_serializing_if = "std::option::Option::is_none")] 285 #[serde(borrow)] 286 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 287 #[serde(borrow)] 288 pub name: jacquard_common::CowStr<'a>, 289 pub set_size: i64,
+4 -4
crates/weaver-api/src/tools_ozone/setting.rs
··· 22 #[serde(rename_all = "camelCase")] 23 pub struct DefsOption<'a> { 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 - pub created_at: Option<jacquard_common::types::string::Datetime>, 26 #[serde(borrow)] 27 pub created_by: jacquard_common::types::string::Did<'a>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 - pub description: Option<jacquard_common::CowStr<'a>>, 31 #[serde(borrow)] 32 pub did: jacquard_common::types::string::Did<'a>, 33 #[serde(borrow)] ··· 36 pub last_updated_by: jacquard_common::types::string::Did<'a>, 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 #[serde(borrow)] 39 - pub manager_role: Option<jacquard_common::CowStr<'a>>, 40 #[serde(borrow)] 41 pub scope: jacquard_common::CowStr<'a>, 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 - pub updated_at: Option<jacquard_common::types::string::Datetime>, 44 #[serde(borrow)] 45 pub value: jacquard_common::types::value::Data<'a>, 46 }
··· 22 #[serde(rename_all = "camelCase")] 23 pub struct DefsOption<'a> { 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 26 #[serde(borrow)] 27 pub created_by: jacquard_common::types::string::Did<'a>, 28 #[serde(skip_serializing_if = "std::option::Option::is_none")] 29 #[serde(borrow)] 30 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 31 #[serde(borrow)] 32 pub did: jacquard_common::types::string::Did<'a>, 33 #[serde(borrow)] ··· 36 pub last_updated_by: jacquard_common::types::string::Did<'a>, 37 #[serde(skip_serializing_if = "std::option::Option::is_none")] 38 #[serde(borrow)] 39 + pub manager_role: std::option::Option<jacquard_common::CowStr<'a>>, 40 #[serde(borrow)] 41 pub scope: jacquard_common::CowStr<'a>, 42 #[serde(skip_serializing_if = "std::option::Option::is_none")] 43 + pub updated_at: std::option::Option<jacquard_common::types::string::Datetime>, 44 #[serde(borrow)] 45 pub value: jacquard_common::types::value::Data<'a>, 46 }
+2 -2
crates/weaver-api/src/tools_ozone/setting/upsert_option.rs
··· 19 pub struct UpsertOption<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 - pub description: Option<jacquard_common::CowStr<'a>>, 23 #[serde(borrow)] 24 pub key: jacquard_common::types::string::Nsid<'a>, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 - pub manager_role: Option<jacquard_common::CowStr<'a>>, 28 #[serde(borrow)] 29 pub scope: jacquard_common::CowStr<'a>, 30 #[serde(borrow)]
··· 19 pub struct UpsertOption<'a> { 20 #[serde(skip_serializing_if = "std::option::Option::is_none")] 21 #[serde(borrow)] 22 + pub description: std::option::Option<jacquard_common::CowStr<'a>>, 23 #[serde(borrow)] 24 pub key: jacquard_common::types::string::Nsid<'a>, 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 #[serde(borrow)] 27 + pub manager_role: std::option::Option<jacquard_common::CowStr<'a>>, 28 #[serde(borrow)] 29 pub scope: jacquard_common::CowStr<'a>, 30 #[serde(borrow)]
+5 -5
crates/weaver-api/src/tools_ozone/team.rs
··· 23 #[serde(rename_all = "camelCase")] 24 pub struct Member<'a> { 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 - pub created_at: Option<jacquard_common::types::string::Datetime>, 27 #[serde(borrow)] 28 pub did: jacquard_common::types::string::Did<'a>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 - pub disabled: Option<bool>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 - pub last_updated_by: Option<jacquard_common::CowStr<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 - pub profile: Option<crate::app_bsky::actor::ProfileViewDetailed<'a>>, 37 #[serde(borrow)] 38 pub role: jacquard_common::CowStr<'a>, 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 - pub updated_at: Option<jacquard_common::types::string::Datetime>, 41 } 42 43 pub mod member_state {
··· 23 #[serde(rename_all = "camelCase")] 24 pub struct Member<'a> { 25 #[serde(skip_serializing_if = "std::option::Option::is_none")] 26 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 27 #[serde(borrow)] 28 pub did: jacquard_common::types::string::Did<'a>, 29 #[serde(skip_serializing_if = "std::option::Option::is_none")] 30 + pub disabled: std::option::Option<bool>, 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 #[serde(borrow)] 33 + pub last_updated_by: std::option::Option<jacquard_common::CowStr<'a>>, 34 #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 #[serde(borrow)] 36 + pub profile: std::option::Option<crate::app_bsky::actor::ProfileViewDetailed<'a>>, 37 #[serde(borrow)] 38 pub role: jacquard_common::CowStr<'a>, 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 + pub updated_at: std::option::Option<jacquard_common::types::string::Datetime>, 41 } 42 43 pub mod member_state {
+2 -2
crates/weaver-api/src/tools_ozone/team/update_member.rs
··· 20 #[serde(borrow)] 21 pub did: jacquard_common::types::string::Did<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 - pub disabled: Option<bool>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 - pub role: Option<jacquard_common::CowStr<'a>>, 27 } 28 29 pub mod update_member_state {
··· 20 #[serde(borrow)] 21 pub did: jacquard_common::types::string::Did<'a>, 22 #[serde(skip_serializing_if = "std::option::Option::is_none")] 23 + pub disabled: std::option::Option<bool>, 24 #[serde(skip_serializing_if = "std::option::Option::is_none")] 25 #[serde(borrow)] 26 + pub role: std::option::Option<jacquard_common::CowStr<'a>>, 27 } 28 29 pub mod update_member_state {
+7 -7
crates/weaver-api/src/tools_ozone/verification.rs
··· 35 pub issuer: jacquard_common::types::string::Did<'a>, 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 #[serde(borrow)] 38 - pub issuer_profile: Option<jacquard_common::types::value::Data<'a>>, 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 #[serde(borrow)] 41 - pub issuer_repo: Option<VerificationViewIssuerRepo<'a>>, 42 /// Describes the reason for revocation, also indicating that the verification is no longer valid. 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 #[serde(borrow)] 45 - pub revoke_reason: Option<jacquard_common::CowStr<'a>>, 46 /// Timestamp when the verification was revoked. 47 #[serde(skip_serializing_if = "std::option::Option::is_none")] 48 - pub revoked_at: Option<jacquard_common::types::string::Datetime>, 49 /// The user who revoked this verification. 50 #[serde(skip_serializing_if = "std::option::Option::is_none")] 51 #[serde(borrow)] 52 - pub revoked_by: Option<jacquard_common::types::string::Did<'a>>, 53 /// The subject of the verification. 54 #[serde(borrow)] 55 pub subject: jacquard_common::types::string::Did<'a>, 56 #[serde(skip_serializing_if = "std::option::Option::is_none")] 57 #[serde(borrow)] 58 - pub subject_profile: Option<jacquard_common::types::value::Data<'a>>, 59 #[serde(skip_serializing_if = "std::option::Option::is_none")] 60 #[serde(borrow)] 61 - pub subject_repo: Option<VerificationViewSubjectRepo<'a>>, 62 /// The AT-URI of the verification record. 63 #[serde(borrow)] 64 pub uri: jacquard_common::types::string::AtUri<'a>,
··· 35 pub issuer: jacquard_common::types::string::Did<'a>, 36 #[serde(skip_serializing_if = "std::option::Option::is_none")] 37 #[serde(borrow)] 38 + pub issuer_profile: std::option::Option<jacquard_common::types::value::Data<'a>>, 39 #[serde(skip_serializing_if = "std::option::Option::is_none")] 40 #[serde(borrow)] 41 + pub issuer_repo: std::option::Option<VerificationViewIssuerRepo<'a>>, 42 /// Describes the reason for revocation, also indicating that the verification is no longer valid. 43 #[serde(skip_serializing_if = "std::option::Option::is_none")] 44 #[serde(borrow)] 45 + pub revoke_reason: std::option::Option<jacquard_common::CowStr<'a>>, 46 /// Timestamp when the verification was revoked. 47 #[serde(skip_serializing_if = "std::option::Option::is_none")] 48 + pub revoked_at: std::option::Option<jacquard_common::types::string::Datetime>, 49 /// The user who revoked this verification. 50 #[serde(skip_serializing_if = "std::option::Option::is_none")] 51 #[serde(borrow)] 52 + pub revoked_by: std::option::Option<jacquard_common::types::string::Did<'a>>, 53 /// The subject of the verification. 54 #[serde(borrow)] 55 pub subject: jacquard_common::types::string::Did<'a>, 56 #[serde(skip_serializing_if = "std::option::Option::is_none")] 57 #[serde(borrow)] 58 + pub subject_profile: std::option::Option<jacquard_common::types::value::Data<'a>>, 59 #[serde(skip_serializing_if = "std::option::Option::is_none")] 60 #[serde(borrow)] 61 + pub subject_repo: std::option::Option<VerificationViewSubjectRepo<'a>>, 62 /// The AT-URI of the verification record. 63 #[serde(borrow)] 64 pub uri: jacquard_common::types::string::AtUri<'a>,
+1 -1
crates/weaver-api/src/tools_ozone/verification/grant_verifications.rs
··· 618 pub struct VerificationInput<'a> { 619 /// Timestamp for verification record. Defaults to current time when not specified. 620 #[serde(skip_serializing_if = "std::option::Option::is_none")] 621 - pub created_at: Option<jacquard_common::types::string::Datetime>, 622 /// Display name of the subject the verification applies to at the moment of verifying. 623 #[serde(borrow)] 624 pub display_name: jacquard_common::CowStr<'a>,
··· 618 pub struct VerificationInput<'a> { 619 /// Timestamp for verification record. Defaults to current time when not specified. 620 #[serde(skip_serializing_if = "std::option::Option::is_none")] 621 + pub created_at: std::option::Option<jacquard_common::types::string::Datetime>, 622 /// Display name of the subject the verification applies to at the moment of verifying. 623 #[serde(borrow)] 624 pub display_name: jacquard_common::CowStr<'a>,
+1 -1
crates/weaver-api/src/tools_ozone/verification/revoke_verifications.rs
··· 20 /// Reason for revoking the verification. This is optional and can be omitted if not needed. 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 - pub revoke_reason: Option<jacquard_common::CowStr<'a>>, 24 /// Array of verification record uris to revoke 25 #[serde(borrow)] 26 pub uris: Vec<jacquard_common::types::string::AtUri<'a>>,
··· 20 /// Reason for revoking the verification. This is optional and can be omitted if not needed. 21 #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 #[serde(borrow)] 23 + pub revoke_reason: std::option::Option<jacquard_common::CowStr<'a>>, 24 /// Array of verification record uris to revoke 25 #[serde(borrow)] 26 pub uris: Vec<jacquard_common::types::string::AtUri<'a>>,
+10
crates/weaver-app/.env-dev
···
··· 1 + WEAVER_APP_ENV="dev" 2 + WEAVER_APP_HOST="http://localhost" 3 + WEAVER_APP_DOMAIN="" 4 + WEAVER_PORT=8080 5 + WEAVER_APP_SCOPES="atproto transition:generic" 6 + WEAVER_CLIENT_NAME="Weaver" 7 + 8 + WEAVER_LOGO_URI="" 9 + WEAVER_TOS_URI="" 10 + WEAVER_PRIVACY_POLICY_URI=""
-10
crates/weaver-app/.env-prod
··· 1 - WEAVER_APP_ENV="prod" 2 - WEAVER_APP_HOST="https://alpha.weaver.sh" 3 - WEAVER_APP_DOMAIN="https://alpha.weaver.sh" 4 - WEAVER_PORT=8080 5 - WEAVER_APP_SCOPES="atproto transition:generic" 6 - WEAVER_CLIENT_NAME="Weaver" 7 - 8 - WEAVER_LOGO_URI="" 9 - WEAVER_TOS_URI="" 10 - WEAVER_PRIVACY_POLICY_URI=""
···
+2 -1
crates/weaver-app/Cargo.toml
··· 24 weaver-common = { path = "../weaver-common" } 25 jacquard = { workspace = true}#, features = ["streaming"] } 26 jacquard-lexicon = { workspace = true } 27 jacquard-axum = { workspace = true, optional = true } 28 weaver-api = { path = "../weaver-api"}#, features = ["streaming"] } 29 markdown-weaver = { workspace = true } ··· 41 http = "1.3" 42 reqwest = { version = "0.12", default-features = false, features = ["json"] } 43 44 - dioxus-free-icons = { version = "0.10.0" } 45 # diesel = { version = "2.3", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "chrono", "serde_json"] } 46 # diesel_migrations = { version = "2.3", features = ["sqlite"] } 47 tokio = { version = "1.28", features = ["sync"] } 48 serde_html_form = "0.2.8" 49 tracing.workspace = true 50 51 [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] 52 webbrowser = "1.0.6"
··· 24 weaver-common = { path = "../weaver-common" } 25 jacquard = { workspace = true}#, features = ["streaming"] } 26 jacquard-lexicon = { workspace = true } 27 + jacquard-identity = { workspace = true } 28 jacquard-axum = { workspace = true, optional = true } 29 weaver-api = { path = "../weaver-api"}#, features = ["streaming"] } 30 markdown-weaver = { workspace = true } ··· 42 http = "1.3" 43 reqwest = { version = "0.12", default-features = false, features = ["json"] } 44 45 # diesel = { version = "2.3", features = ["sqlite", "returning_clauses_for_sqlite_3_35", "chrono", "serde_json"] } 46 # diesel_migrations = { version = "2.3", features = ["sqlite"] } 47 tokio = { version = "1.28", features = ["sync"] } 48 serde_html_form = "0.2.8" 49 tracing.workspace = true 50 + serde_ipld_dagcbor = { version = "0.6" } 51 52 [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] 53 webbrowser = "1.0.6"
+3 -4
crates/weaver-app/src/auth/mod.rs
··· 5 mod state; 6 pub use state::AuthState; 7 8 #[cfg(all(feature = "fullstack-server", feature = "server"))] 9 use dioxus::prelude::*; 10 #[cfg(all(feature = "fullstack-server", feature = "server"))] ··· 25 } 26 27 #[cfg(not(target_arch = "wasm32"))] 28 - pub async fn restore_session() -> Result<(), String> { 29 Ok(()) 30 } 31 32 #[cfg(target_arch = "wasm32")] 33 - pub async fn restore_session() -> Result<(), CapturedError> { 34 - use crate::fetch::CachedFetcher; 35 use dioxus::prelude::*; 36 use gloo_storage::{LocalStorage, Storage}; 37 use jacquard::types::string::Did; ··· 59 let (did_str, session_id) = 60 found_session.ok_or(CapturedError::from_display("No saved session found"))?; 61 let did = Did::new_owned(did_str)?; 62 - let fetcher = use_context::<CachedFetcher>(); 63 64 let session = fetcher 65 .client
··· 5 mod state; 6 pub use state::AuthState; 7 8 + use crate::fetch::Fetcher; 9 #[cfg(all(feature = "fullstack-server", feature = "server"))] 10 use dioxus::prelude::*; 11 #[cfg(all(feature = "fullstack-server", feature = "server"))] ··· 26 } 27 28 #[cfg(not(target_arch = "wasm32"))] 29 + pub async fn restore_session(_fetcher: Fetcher) -> Result<(), String> { 30 Ok(()) 31 } 32 33 #[cfg(target_arch = "wasm32")] 34 + pub async fn restore_session(fetcher: Fetcher) -> Result<(), CapturedError> { 35 use dioxus::prelude::*; 36 use gloo_storage::{LocalStorage, Storage}; 37 use jacquard::types::string::Did; ··· 59 let (did_str, session_id) = 60 found_session.ok_or(CapturedError::from_display("No saved session found"))?; 61 let did = Did::new_owned(did_str)?; 62 63 let session = fetcher 64 .client
+31 -7
crates/weaver-app/src/blobcache.rs
··· 42 } 43 AtIdentifier::Handle(handle) => self.client.pds_for_handle(&handle).await?, 44 }; 45 - let blob = self 46 - .client 47 - .xrpc(pds_url) 48 - .send(&GetBlob::new().cid(cid.clone()).did(repo_did).build()) 49 - .await? 50 - .buffer() 51 - .clone(); 52 53 self.cache.insert(cid.clone(), blob); 54 if let Some(name) = name {
··· 42 } 43 AtIdentifier::Handle(handle) => self.client.pds_for_handle(&handle).await?, 44 }; 45 + // let blob = if let Ok(blob_stream) = self 46 + // .client 47 + // .xrpc(pds_url) 48 + // .send( 49 + // &GetBlob::new() 50 + // .cid(cid.clone()) 51 + // .did(repo_did.clone()) 52 + // .build(), 53 + // ) 54 + // .await 55 + // { 56 + // blob_stream.buffer().clone() 57 + // } else { 58 + // reqwest::get(format!( 59 + // "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg", 60 + // repo_did, cid 61 + // )) 62 + // .await? 63 + // .bytes() 64 + // .await? 65 + // .clone() 66 + // }; 67 + // 68 + let blob = reqwest::get(format!( 69 + "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg", 70 + repo_did, cid 71 + )) 72 + .await? 73 + .bytes() 74 + .await? 75 + .clone(); 76 77 self.cache.insert(cid.clone(), blob); 78 if let Some(name) = name {
+2 -2
crates/weaver-app/src/components/css.rs
··· 35 use weaver_renderer::css::{generate_base_css, generate_syntax_css}; 36 use weaver_renderer::theme::{default_resolved_theme, resolve_theme}; 37 38 - let fetcher = use_context::<fetch::CachedFetcher>(); 39 40 let css_content = use_resource(move || { 41 let ident = ident.clone(); ··· 87 } 88 89 #[cfg(feature = "fullstack-server")] 90 - #[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::CachedFetcher>>)] 91 pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<Response> { 92 use dioxus::fullstack::http::header::CONTENT_TYPE; 93 use jacquard::client::AgentSessionExt;
··· 35 use weaver_renderer::css::{generate_base_css, generate_syntax_css}; 36 use weaver_renderer::theme::{default_resolved_theme, resolve_theme}; 37 38 + let fetcher = use_context::<fetch::Fetcher>(); 39 40 let css_content = use_resource(move || { 41 let ident = ident.clone(); ··· 87 } 88 89 #[cfg(feature = "fullstack-server")] 90 + #[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::Fetcher>>)] 91 pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<Response> { 92 use dioxus::fullstack::http::header::CONTENT_TYPE; 93 use jacquard::client::AgentSessionExt;
+58 -66
crates/weaver-app/src/components/entry.rs
··· 4 use crate::blobcache::BlobCache; 5 use crate::{ 6 components::avatar::{Avatar, AvatarImage}, 7 - data::use_handle, 8 }; 9 10 use crate::Route; ··· 22 }; 23 #[allow(unused_imports)] 24 use std::sync::Arc; 25 - use weaver_api::sh_weaver::notebook::{BookEntryView, entry}; 26 27 #[component] 28 pub fn Entry( ··· 31 title: ReadSignal<SmolStr>, 32 ) -> Element { 33 // Use feature-gated hook for SSR support 34 - let entry = crate::data::use_entry_data(ident(), book_title(), title())?; 35 36 // Handle blob caching when entry data is available 37 - use_effect(move || { 38 - if let Some((_book_entry_view, entry_record)) = &*entry.read() { 39 - if let Some(embeds) = &entry_record.embeds { 40 - if let Some(images) = &embeds.images { 41 - // Register blob mappings with service worker (client-side only) 42 - #[cfg(all( 43 - target_family = "wasm", 44 - target_os = "unknown", 45 - not(feature = "fullstack-server") 46 - ))] 47 - { 48 - let images = images.clone(); 49 - spawn(async move { 50 - let fetcher = use_context::<fetch::CachedFetcher>(); 51 - let _ = crate::service_worker::register_entry_blobs( 52 - &ident(), 53 - book_title().as_str(), 54 - &images, 55 - &fetcher, 56 - ) 57 - .await; 58 - }); 59 - } 60 - #[cfg(feature = "fullstack-server")] 61 - { 62 - let ident = ident(); 63 - let images = images.clone(); 64 - spawn(async move { 65 - for image in &images.images { 66 - use crate::data::cache_blob; 67 - 68 - let cid = image.image.blob().cid(); 69 - cache_blob( 70 - ident.to_smolstr(), 71 - cid.to_smolstr(), 72 - image.name.as_ref().map(|n| n.to_smolstr()), 73 ) 74 - .await 75 - .ok(); 76 - } 77 - }); 78 } 79 } 80 } 81 - } 82 - }); 83 - 84 - match &*entry.read_unchecked() { 85 - Some((book_entry_view, entry_record)) => { 86 - rsx! { EntryPage { 87 - book_entry_view: book_entry_view.clone(), 88 - entry_record: entry_record.clone(), 89 - ident: use_handle(ident())?(), 90 - book_title: book_title() 91 - } } 92 } 93 - _ => rsx! { p { "Loading..." } }, 94 } 95 } 96 97 /// Full entry page with metadata, content, and navigation 98 #[component] 99 - fn EntryPage( 100 book_entry_view: BookEntryView<'static>, 101 entry_record: entry::Entry<'static>, 102 ident: AtIdentifier<'static>, ··· 166 author_count: usize, 167 ) -> Element { 168 use crate::Route; 169 - use jacquard::{IntoStatic, from_data}; 170 use weaver_api::sh_weaver::notebook::entry::Entry; 171 172 let entry_view = &entry.entry; ··· 176 .map(|t| t.as_ref()) 177 .unwrap_or("Untitled"); 178 179 - let ident = use_handle(entry_view.uri.authority().clone().into_static())?; 180 - 181 // Format date 182 let formatted_date = entry_view 183 .indexed_at ··· 204 rsx! { 205 div { class: "entry-card", 206 Link { 207 - to: Route::Entry { 208 - ident: ident(), 209 book_title: book_title.clone(), 210 title: title.to_string().into() 211 }, ··· 236 } 237 } 238 span { class: "author-name", "{display_name}" } 239 - span { class: "meta-label", "@{ident}" } 240 } 241 } 242 ProfileDataViewInner::ProfileViewDetailed(profile) => { ··· 248 } 249 } 250 span { class: "author-name", "{display_name}" } 251 - span { class: "meta-label", "@{ident}" } 252 } 253 } 254 ProfileDataViewInner::TangledProfileView(profile) => { ··· 291 /// Metadata header showing title, authors, date, tags 292 #[component] 293 fn EntryMetadata( 294 - entry_view: weaver_api::sh_weaver::notebook::EntryView<'static>, 295 ident: AtIdentifier<'static>, 296 created_at: Datetime, 297 ) -> Element { ··· 402 #[component] 403 fn NavButton( 404 direction: &'static str, 405 - entry: weaver_api::sh_weaver::notebook::EntryView<'static>, 406 ident: AtIdentifier<'static>, 407 book_title: SmolStr, 408 ) -> Element { ··· 415 416 rsx! { 417 Link { 418 - to: Route::Entry { 419 ident: ident.clone(), 420 book_title: book_title.clone(), 421 title: entry_title.to_string().into()
··· 4 use crate::blobcache::BlobCache; 5 use crate::{ 6 components::avatar::{Avatar, AvatarImage}, 7 + data::{NotebookHandle, use_handle}, 8 }; 9 10 use crate::Route; ··· 22 }; 23 #[allow(unused_imports)] 24 use std::sync::Arc; 25 + use weaver_api::sh_weaver::notebook::{BookEntryView, EntryView, entry}; 26 + 27 + #[component] 28 + pub fn EntryPage( 29 + ident: ReadSignal<AtIdentifier<'static>>, 30 + book_title: ReadSignal<SmolStr>, 31 + title: ReadSignal<SmolStr>, 32 + ) -> Element { 33 + rsx! { 34 + {std::iter::once(rsx! {Entry {ident, book_title, title}})} 35 + } 36 + } 37 38 #[component] 39 pub fn Entry( ··· 42 title: ReadSignal<SmolStr>, 43 ) -> Element { 44 // Use feature-gated hook for SSR support 45 + let entry = crate::data::use_entry_data(ident(), book_title(), title()); 46 + let handle = use_context::<NotebookHandle>(); 47 + let fetcher = use_context::<crate::fetch::Fetcher>(); 48 49 // Handle blob caching when entry data is available 50 + if let Ok(entry) = entry { 51 + match &*entry.read() { 52 + Some((book_entry_view, entry_record)) => { 53 + if let Some(embeds) = &entry_record.embeds { 54 + if let Some(images) = &embeds.images { 55 + // Register blob mappings with service worker (client-side only) 56 + #[cfg(all( 57 + target_family = "wasm", 58 + target_os = "unknown", 59 + not(feature = "fullstack-server") 60 + ))] 61 + { 62 + let fetcher = fetcher.clone(); 63 + let images = images.clone(); 64 + spawn(async move { 65 + let _ = crate::service_worker::register_entry_blobs( 66 + &ident(), 67 + book_title().as_str(), 68 + &images, 69 + &fetcher, 70 ) 71 + .await; 72 + }); 73 + } 74 } 75 } 76 + rsx! { EntryPageView { 77 + book_entry_view: book_entry_view.clone(), 78 + entry_record: entry_record.clone(), 79 + ident: handle.as_ref().unwrap().clone(), 80 + book_title: book_title() 81 + } } 82 } 83 + _ => rsx! { p { "Loading..." } }, 84 } 85 + } else { 86 + rsx! { p { "Loading..." } } 87 } 88 } 89 90 /// Full entry page with metadata, content, and navigation 91 #[component] 92 + fn EntryPageView( 93 book_entry_view: BookEntryView<'static>, 94 entry_record: entry::Entry<'static>, 95 ident: AtIdentifier<'static>, ··· 159 author_count: usize, 160 ) -> Element { 161 use crate::Route; 162 + use jacquard::from_data; 163 use weaver_api::sh_weaver::notebook::entry::Entry; 164 165 let entry_view = &entry.entry; ··· 169 .map(|t| t.as_ref()) 170 .unwrap_or("Untitled"); 171 172 + let ident = use_context::<NotebookHandle>(); 173 // Format date 174 let formatted_date = entry_view 175 .indexed_at ··· 196 rsx! { 197 div { class: "entry-card", 198 Link { 199 + to: Route::EntryPage { 200 + ident: ident.as_ref().unwrap().clone(), 201 book_title: book_title.clone(), 202 title: title.to_string().into() 203 }, ··· 228 } 229 } 230 span { class: "author-name", "{display_name}" } 231 + span { class: "meta-label", "@{ident.as_ref().unwrap()}" } 232 } 233 } 234 ProfileDataViewInner::ProfileViewDetailed(profile) => { ··· 240 } 241 } 242 span { class: "author-name", "{display_name}" } 243 + span { class: "meta-label", "@{ident.as_ref().unwrap()}" } 244 } 245 } 246 ProfileDataViewInner::TangledProfileView(profile) => { ··· 283 /// Metadata header showing title, authors, date, tags 284 #[component] 285 fn EntryMetadata( 286 + entry_view: EntryView<'static>, 287 ident: AtIdentifier<'static>, 288 created_at: Datetime, 289 ) -> Element { ··· 394 #[component] 395 fn NavButton( 396 direction: &'static str, 397 + entry: EntryView<'static>, 398 ident: AtIdentifier<'static>, 399 book_title: SmolStr, 400 ) -> Element { ··· 407 408 rsx! { 409 Link { 410 + to: Route::EntryPage { 411 ident: ident.clone(), 412 book_title: book_title.clone(), 413 title: entry_title.to_string().into()
+38 -36
crates/weaver-app/src/components/identity.rs
··· 8 9 #[component] 10 pub fn Repository(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 11 rsx! { 12 - // We can create elements inside the rsx macro with the element name followed by a block of attributes and children. 13 div { 14 Outlet::<Route> {} 15 } 16 } 17 } 18 19 #[component] 20 pub fn RepositoryIndex(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 21 use crate::components::ProfileDisplay; 22 - 23 - // Fetch notebooks for this specific DID with SSR support 24 - let notebooks = data::use_notebooks_for_did(ident()).ok(); 25 - 26 rsx! { 27 document::Stylesheet { href: NOTEBOOK_CARD_CSS } 28 ··· 35 // Main content area 36 main { class: "repository-main", 37 div { class: "notebooks-list", 38 - if let Some(notebooks_memo) = &notebooks { 39 - match &*notebooks_memo.read_unchecked() { 40 - Some(notebook_list) => rsx! { 41 - for notebook in notebook_list.iter() { 42 - { 43 - let view = &notebook.0; 44 - let entries = &notebook.1; 45 - rsx! { 46 - div { 47 - key: "{view.cid}", 48 - NotebookCard { 49 - notebook: view.clone(), 50 - entry_refs: entries.clone() 51 - } 52 - } 53 - } 54 - } 55 - } 56 - }, 57 - None => rsx! { 58 - div { "Loading notebooks..." } 59 - } 60 - } 61 - } else { 62 - div { "Loading notebooks..." } 63 - } 64 } 65 } 66 } ··· 74 ) -> Element { 75 use jacquard::IntoStatic; 76 77 - let fetcher = use_context::<fetch::CachedFetcher>(); 78 79 let title = notebook 80 .title ··· 109 div { class: "notebook-card-container", 110 111 Link { 112 - to: Route::Entry { 113 ident: ident.clone(), 114 book_title: title.to_string().into(), 115 title: "".into() // Will redirect to first entry ··· 192 193 rsx! { 194 Link { 195 - to: Route::Entry { 196 ident: ident.clone(), 197 book_title: book_title.clone(), 198 title: entry_title.to_string().into() ··· 236 237 rsx! { 238 Link { 239 - to: Route::Entry { 240 ident: ident.clone(), 241 book_title: book_title.clone(), 242 title: entry_title.to_string().into() ··· 289 290 rsx! { 291 Link { 292 - to: Route::Entry { 293 ident: ident.clone(), 294 book_title: book_title.clone(), 295 title: entry_title.to_string().into()
··· 8 9 #[component] 10 pub fn Repository(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 11 + // Fetch notebooks for this specific DID with SSR support 12 + let notebooks = data::use_notebooks_for_did(ident())?; 13 + use_context_provider(|| notebooks); 14 rsx! { 15 div { 16 Outlet::<Route> {} 17 } 18 } 19 } 20 21 + pub fn use_repo_notebook_context() 22 + -> Option<Resource<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>> { 23 + try_use_context::<Resource<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>>() 24 + } 25 + 26 #[component] 27 pub fn RepositoryIndex(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 28 use crate::components::ProfileDisplay; 29 + let notebooks = use_repo_notebook_context(); 30 rsx! { 31 document::Stylesheet { href: NOTEBOOK_CARD_CSS } 32 ··· 39 // Main content area 40 main { class: "repository-main", 41 div { class: "notebooks-list", 42 + if let Some(notebooks) = notebooks { 43 + match &*notebooks.read() { 44 + Some(Some(notebook_list)) => rsx! { 45 + for notebook in notebook_list.iter() { 46 + { 47 + let view = &notebook.0; 48 + let entries = &notebook.1; 49 + rsx! { 50 + div { 51 + key: "{view.cid}", 52 + NotebookCard { 53 + notebook: view.clone(), 54 + entry_refs: entries.clone() 55 + } 56 + } 57 + } 58 + } 59 + } 60 + }, 61 + _ => rsx! { 62 + div { "Loading notebooks..." } 63 + } 64 + } 65 + } 66 } 67 } 68 } ··· 76 ) -> Element { 77 use jacquard::IntoStatic; 78 79 + let fetcher = use_context::<fetch::Fetcher>(); 80 81 let title = notebook 82 .title ··· 111 div { class: "notebook-card-container", 112 113 Link { 114 + to: Route::EntryPage { 115 ident: ident.clone(), 116 book_title: title.to_string().into(), 117 title: "".into() // Will redirect to first entry ··· 194 195 rsx! { 196 Link { 197 + to: Route::EntryPage { 198 ident: ident.clone(), 199 book_title: book_title.clone(), 200 title: entry_title.to_string().into() ··· 238 239 rsx! { 240 Link { 241 + to: Route::EntryPage { 242 ident: ident.clone(), 243 book_title: book_title.clone(), 244 title: entry_title.to_string().into() ··· 291 292 rsx! { 293 Link { 294 + to: Route::EntryPage { 295 ident: ident.clone(), 296 book_title: book_title.clone(), 297 title: entry_title.to_string().into()
+70 -43
crates/weaver-app/src/components/login.rs
··· 4 use jacquard::oauth::session::ClientData; 5 use jacquard::{oauth::types::AuthorizeOptions, smol_str::SmolStr}; 6 7 - use crate::CONFIG; 8 use crate::{ 9 components::{ 10 button::{Button, ButtonVariant}, 11 dialog::{DialogContent, DialogRoot, DialogTitle}, 12 input::Input, 13 }, 14 - fetch::CachedFetcher, 15 }; 16 17 #[component] 18 pub fn LoginModal(open: Signal<bool>) -> Element { 19 let mut handle_input = use_signal(|| String::new()); 20 - let mut error = use_signal(|| Option::<String>::None); 21 - let mut is_loading = use_signal(|| false); 22 - 23 - let mut handle_submit = move || { 24 - let handle = handle_input.read().clone(); 25 - if handle.is_empty() { 26 - error.set(Some("Please enter a handle".to_string())); 27 - return; 28 - } 29 - 30 - is_loading.set(true); 31 - error.set(None); 32 - 33 - let fetcher = use_context::<CachedFetcher>(); 34 - 35 - #[cfg(target_arch = "wasm32")] 36 - { 37 - use crate::Route; 38 - use gloo_storage::Storage; 39 - let full_route = use_route::<Route>(); 40 - gloo_storage::LocalStorage::set("cached_route", format!("{}", full_route)).ok(); 41 - } 42 43 - use_effect(move || { 44 - let handle = handle.clone(); 45 - let fetcher = fetcher.clone(); 46 - spawn(async move { 47 - match start_oauth_flow(handle, fetcher).await { 48 - Ok(_) => { 49 - open.set(false); 50 - } 51 - Err(e) => { 52 - error!("Authentication failed: {}", e); 53 - error.set(Some(format!("Authentication failed: {}", e))); 54 - is_loading.set(false); 55 - } 56 - } 57 - }); 58 - }); 59 }; 60 61 rsx! { ··· 76 oninput: move |e: FormEvent| handle_input.set(e.value()), 77 onkeypress: move |k: KeyboardEvent| { 78 if k.key() == Key::Enter { 79 - handle_submit(); 80 } 81 }, 82 placeholder: "Enter your handle", ··· 98 r#type: "submit", 99 disabled: is_loading(), 100 onclick: move |_| { 101 - handle_submit(); 102 }, 103 if is_loading() { "Authenticating..." } else { "Sign In" } 104 } ··· 108 } 109 } 110 111 - async fn start_oauth_flow(handle: String, fetcher: CachedFetcher) -> Result<(), SmolStr> { 112 info!("Starting OAuth flow for handle: {}", handle); 113 114 let client_data = ClientData {
··· 4 use jacquard::oauth::session::ClientData; 5 use jacquard::{oauth::types::AuthorizeOptions, smol_str::SmolStr}; 6 7 + use crate::{CONFIG, Route}; 8 use crate::{ 9 components::{ 10 button::{Button, ButtonVariant}, 11 dialog::{DialogContent, DialogRoot, DialogTitle}, 12 input::Input, 13 }, 14 + fetch::Fetcher, 15 }; 16 17 + fn handle_submit( 18 + full_route: Route, 19 + fetcher: Fetcher, 20 + mut error: Signal<Option<String>>, 21 + mut is_loading: Signal<bool>, 22 + handle_input: Signal<String>, 23 + mut open: Signal<bool>, 24 + ) { 25 + let handle = handle_input.read().clone(); 26 + if handle.is_empty() { 27 + error.set(Some("Please enter a handle".to_string())); 28 + return; 29 + } 30 + 31 + is_loading.set(true); 32 + error.set(None); 33 + 34 + #[cfg(target_arch = "wasm32")] 35 + { 36 + use gloo_storage::Storage; 37 + gloo_storage::LocalStorage::set("cached_route", format!("{}", full_route)).ok(); 38 + spawn(async move { 39 + match start_oauth_flow(handle, fetcher).await { 40 + Ok(_) => { 41 + open.set(false); 42 + } 43 + Err(e) => { 44 + error!("Authentication failed: {}", e); 45 + error.set(Some(format!("Authentication failed: {}", e))); 46 + is_loading.set(false); 47 + } 48 + } 49 + }); 50 + } 51 + } 52 + 53 #[component] 54 pub fn LoginModal(open: Signal<bool>) -> Element { 55 let mut handle_input = use_signal(|| String::new()); 56 + let error = use_signal(|| Option::<String>::None); 57 + let is_loading = use_signal(|| false); 58 + let full_route = use_route::<Route>(); 59 + let fetcher = use_context::<Fetcher>(); 60 + let submit_route = full_route.clone(); 61 + let submit_fetcher = fetcher.clone(); 62 + let submit_closure1 = move || { 63 + let submit_route = submit_route.clone(); 64 + let submit_fetcher = submit_fetcher.clone(); 65 + handle_submit( 66 + submit_route, 67 + submit_fetcher, 68 + error, 69 + is_loading, 70 + handle_input, 71 + open, 72 + ); 73 + }; 74 75 + let submit_closure2 = move || { 76 + let submit_route = full_route.clone(); 77 + let submit_fetcher = fetcher.clone(); 78 + handle_submit( 79 + submit_route, 80 + submit_fetcher, 81 + error, 82 + is_loading, 83 + handle_input, 84 + open, 85 + ); 86 }; 87 88 rsx! { ··· 103 oninput: move |e: FormEvent| handle_input.set(e.value()), 104 onkeypress: move |k: KeyboardEvent| { 105 if k.key() == Key::Enter { 106 + submit_closure1(); 107 } 108 }, 109 placeholder: "Enter your handle", ··· 125 r#type: "submit", 126 disabled: is_loading(), 127 onclick: move |_| { 128 + submit_closure2(); 129 }, 130 if is_loading() { "Authenticating..." } else { "Sign In" } 131 } ··· 135 } 136 } 137 138 + async fn start_oauth_flow(handle: String, fetcher: Fetcher) -> Result<(), SmolStr> { 139 info!("Starting OAuth flow for handle: {}", handle); 140 141 let client_data = ClientData {
+7 -4
crates/weaver-app/src/components/mod.rs
··· 7 8 mod entry; 9 #[allow(unused_imports)] 10 - pub use entry::{Entry, EntryCard, EntryMarkdown}; 11 12 pub mod identity; 13 #[allow(unused_imports)] ··· 21 pub use notebook_cover::NotebookCover; 22 23 pub mod login; 24 25 use dioxus::prelude::*; 26 ··· 121 .with_hash_suffix(false) 122 .into_asset_options() 123 ); 124 - pub mod input; 125 - pub mod dialog; 126 - pub mod button; 127 pub mod accordion;
··· 7 8 mod entry; 9 #[allow(unused_imports)] 10 + pub use entry::{Entry, EntryCard, EntryMarkdown, EntryPage}; 11 12 pub mod identity; 13 #[allow(unused_imports)] ··· 21 pub use notebook_cover::NotebookCover; 22 23 pub mod login; 24 + 25 + pub mod record_editor; 26 + pub mod record_view; 27 28 use dioxus::prelude::*; 29 ··· 124 .with_hash_suffix(false) 125 .into_asset_options() 126 ); 127 pub mod accordion; 128 + pub mod button; 129 + pub mod dialog; 130 + pub mod input;
+7 -7
crates/weaver-app/src/components/notebook_cover.rs
··· 1 #![allow(non_snake_case)] 2 3 - use crate::components::avatar::{Avatar, AvatarImage}; 4 use dioxus::prelude::*; 5 use weaver_api::sh_weaver::notebook::NotebookView; 6 ··· 21 h1 { class: "notebook-cover-title", "{title}" } 22 div { "Error loading notebook details" } 23 } 24 - } 25 } 26 }; 27 ··· 89 90 #[component] 91 fn NotebookAuthor(author: weaver_api::sh_weaver::notebook::AuthorListView<'static>) -> Element { 92 - use crate::data::use_handle; 93 use weaver_api::sh_weaver::actor::ProfileDataViewInner; 94 95 // Author already has profile data hydrated ··· 100 .as_ref() 101 .map(|n| n.as_ref()) 102 .unwrap_or("Unknown"); 103 - let handle = use_handle(p.did.clone().into())?; 104 105 rsx! { 106 div { class: "notebook-author", ··· 111 } 112 div { class: "notebook-author-info", 113 div { class: "notebook-author-name", "{display_name}" } 114 - div { class: "notebook-author-handle", "@{handle()}" } 115 } 116 } 117 } ··· 122 .as_ref() 123 .map(|n| n.as_ref()) 124 .unwrap_or("Unknown"); 125 - let handle = use_handle(p.did.clone().into())?; 126 127 rsx! { 128 div { class: "notebook-author", ··· 133 } 134 div { class: "notebook-author-info", 135 div { class: "notebook-author-name", "{display_name}" } 136 - div { class: "notebook-author-handle", "@{handle()}" } 137 } 138 } 139 }
··· 1 #![allow(non_snake_case)] 2 3 + use crate::{ 4 + components::avatar::{Avatar, AvatarImage}, 5 + data::NotebookHandle, 6 + }; 7 use dioxus::prelude::*; 8 use weaver_api::sh_weaver::notebook::NotebookView; 9 ··· 24 h1 { class: "notebook-cover-title", "{title}" } 25 div { "Error loading notebook details" } 26 } 27 + }; 28 } 29 }; 30 ··· 92 93 #[component] 94 fn NotebookAuthor(author: weaver_api::sh_weaver::notebook::AuthorListView<'static>) -> Element { 95 use weaver_api::sh_weaver::actor::ProfileDataViewInner; 96 97 // Author already has profile data hydrated ··· 102 .as_ref() 103 .map(|n| n.as_ref()) 104 .unwrap_or("Unknown"); 105 106 rsx! { 107 div { class: "notebook-author", ··· 112 } 113 div { class: "notebook-author-info", 114 div { class: "notebook-author-name", "{display_name}" } 115 + div { class: "notebook-author-handle", "@{p.handle}" } 116 } 117 } 118 } ··· 123 .as_ref() 124 .map(|n| n.as_ref()) 125 .unwrap_or("Unknown"); 126 127 rsx! { 128 div { class: "notebook-author", ··· 133 } 134 div { class: "notebook-author-info", 135 div { class: "notebook-author-name", "{display_name}" } 136 + div { class: "notebook-author-handle", "@{p.handle}" } 137 } 138 } 139 }
+65 -55
crates/weaver-app/src/components/profile.rs
··· 1 #![allow(non_snake_case)] 2 3 - use crate::{ 4 - components::{ 5 - avatar::{Avatar, AvatarImage}, 6 - BskyIcon, TangledIcon, 7 - }, 8 - data::use_handle, 9 }; 10 use dioxus::prelude::*; 11 use jacquard::types::ident::AtIdentifier; ··· 14 const PROFILE_CSS: Asset = asset!("/assets/styling/profile.css"); 15 16 #[component] 17 - pub fn ProfileDisplay(ident: AtIdentifier<'static>) -> Element { 18 // Fetch profile data 19 - let profile = crate::data::use_profile_data(ident.clone())?; 20 21 - match profile().as_ref() { 22 - Some(profile_view) => rsx! { 23 - document::Stylesheet { href: PROFILE_CSS } 24 25 - div { class: "profile-display", 26 - // Banner if present 27 - {match &profile_view.inner { 28 - ProfileDataViewInner::ProfileView(p) => { 29 - if let Some(ref banner) = p.banner { 30 - rsx! { 31 - div { class: "profile-banner", 32 - img { src: "{banner.as_ref()}", alt: "Profile banner" } 33 } 34 } 35 - } else { 36 - rsx! { } 37 } 38 - } 39 - ProfileDataViewInner::ProfileViewDetailed(p) => { 40 - if let Some(ref banner) = p.banner { 41 - rsx! { 42 - div { class: "profile-banner", 43 - img { src: "{banner.as_ref()}", alt: "Profile banner" } 44 } 45 } 46 - } else { 47 - rsx! { } 48 } 49 - } 50 - _ => rsx! { } 51 - }} 52 53 - div { class: "profile-content", 54 - // Avatar and identity 55 - ProfileIdentity { profile_view: profile_view.clone(), ident: ident.clone() } 56 - div { 57 - class: "profile-extras", 58 - // Stats 59 - ProfileStats { ident: ident.clone() } 60 61 - // Links 62 - ProfileLinks { profile_view: profile_view.clone(), ident: ident.clone() } 63 - } 64 65 66 } 67 } 68 - }, 69 - None => rsx! { 70 div { class: "profile-display profile-loading", 71 "Loading profile..." 72 } ··· 76 77 #[component] 78 fn ProfileIdentity( 79 - profile_view: weaver_api::sh_weaver::actor::ProfileDataView<'static>, 80 - ident: AtIdentifier<'static>, 81 ) -> Element { 82 - match &profile_view.inner { 83 ProfileDataViewInner::ProfileView(profile) => { 84 let display_name = profile 85 .display_name ··· 121 span { class: "profile-pronouns", " ({pronouns})" } 122 } 123 } 124 - div { class: "profile-handle", "@{use_handle(ident.clone())?}" } 125 126 if let Some(ref location) = profile.location { 127 div { class: "profile-location", "{location}" } ··· 155 156 div { class: "profile-name-section", 157 h1 { class: "profile-display-name", "{display_name}" } 158 - div { class: "profile-handle", "@{use_handle(ident.clone())?}" } 159 } 160 } 161 ··· 192 } 193 194 #[component] 195 - fn ProfileStats(ident: AtIdentifier<'static>) -> Element { 196 // Fetch notebook count 197 - let notebooks = crate::data::use_notebooks_for_did(ident.clone())?; 198 199 - let notebook_count = notebooks().as_ref().map(|n| n.len()).unwrap_or(0); 200 201 rsx! { 202 div { class: "profile-stats", ··· 210 211 #[component] 212 fn ProfileLinks( 213 - profile_view: weaver_api::sh_weaver::actor::ProfileDataView<'static>, 214 - ident: AtIdentifier<'static>, 215 ) -> Element { 216 - match &profile_view.inner { 217 ProfileDataViewInner::ProfileView(profile) => { 218 rsx! { 219 div { class: "profile-links",
··· 1 #![allow(non_snake_case)] 2 3 + use crate::components::{ 4 + BskyIcon, TangledIcon, 5 + avatar::{Avatar, AvatarImage}, 6 + identity::use_repo_notebook_context, 7 }; 8 use dioxus::prelude::*; 9 use jacquard::types::ident::AtIdentifier; ··· 12 const PROFILE_CSS: Asset = asset!("/assets/styling/profile.css"); 13 14 #[component] 15 + pub fn ProfileDisplay(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 16 // Fetch profile data 17 + let profile = crate::data::use_profile_data(ident()); 18 19 + match &*profile?.read() { 20 + Some(profile_view) => { 21 + let profile_view = use_signal(|| profile_view.clone()); 22 + rsx! { 23 + document::Stylesheet { href: PROFILE_CSS } 24 25 + div { class: "profile-display", 26 + // Banner if present 27 + {match &profile_view.read().inner { 28 + ProfileDataViewInner::ProfileView(p) => { 29 + if let Some(ref banner) = p.banner { 30 + rsx! { 31 + div { class: "profile-banner", 32 + img { src: "{banner.as_ref()}", alt: "Profile banner" } 33 + } 34 } 35 + } else { 36 + rsx! { } 37 } 38 } 39 + ProfileDataViewInner::ProfileViewDetailed(p) => { 40 + if let Some(ref banner) = p.banner { 41 + rsx! { 42 + div { class: "profile-banner", 43 + img { src: "{banner.as_ref()}", alt: "Profile banner" } 44 + } 45 } 46 + } else { 47 + rsx! { } 48 } 49 } 50 + _ => rsx! { } 51 + }} 52 53 + div { class: "profile-content", 54 + // Avatar and identity 55 + ProfileIdentity { profile_view, ident } 56 + div { 57 + class: "profile-extras", 58 + // Stats 59 + ProfileStats { ident } 60 61 + // Links 62 + ProfileLinks { profile_view, ident } 63 + } 64 65 66 + } 67 } 68 } 69 + } 70 + _ => rsx! { 71 div { class: "profile-display profile-loading", 72 "Loading profile..." 73 } ··· 77 78 #[component] 79 fn ProfileIdentity( 80 + profile_view: ReadSignal<weaver_api::sh_weaver::actor::ProfileDataView<'static>>, 81 + ident: ReadSignal<AtIdentifier<'static>>, 82 ) -> Element { 83 + match &profile_view.read().inner { 84 ProfileDataViewInner::ProfileView(profile) => { 85 let display_name = profile 86 .display_name ··· 122 span { class: "profile-pronouns", " ({pronouns})" } 123 } 124 } 125 + div { class: "profile-handle", "@{profile.handle}" } 126 127 if let Some(ref location) = profile.location { 128 div { class: "profile-location", "{location}" } ··· 156 157 div { class: "profile-name-section", 158 h1 { class: "profile-display-name", "{display_name}" } 159 + div { class: "profile-handle", "@{profile.handle}" } 160 } 161 } 162 ··· 193 } 194 195 #[component] 196 + fn ProfileStats(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 197 // Fetch notebook count 198 + let notebooks = use_repo_notebook_context(); 199 200 + let notebook_count = if let Some(notebooks) = notebooks { 201 + if let Some(Some(notebooks)) = &*notebooks.read() { 202 + notebooks.len() 203 + } else { 204 + 0 205 + } 206 + } else { 207 + 0 208 + }; 209 210 rsx! { 211 div { class: "profile-stats", ··· 219 220 #[component] 221 fn ProfileLinks( 222 + profile_view: ReadSignal<weaver_api::sh_weaver::actor::ProfileDataView<'static>>, 223 + 224 + ident: ReadSignal<AtIdentifier<'static>>, 225 ) -> Element { 226 + match &profile_view.read().inner { 227 ProfileDataViewInner::ProfileView(profile) => { 228 rsx! { 229 div { class: "profile-links",
+1663
crates/weaver-app/src/components/record_editor.rs
···
··· 1 + use crate::Route; 2 + use crate::components::accordion::{Accordion, AccordionContent, AccordionItem, AccordionTrigger}; 3 + use crate::components::dialog::{DialogContent, DialogDescription, DialogRoot, DialogTitle}; 4 + use crate::components::record_view::{PathLabel, SchemaView, ViewMode}; 5 + use crate::fetch::Fetcher; 6 + use crate::record_utils::{create_array_item_default, infer_data_from_text, try_parse_as_type}; 7 + use dioxus::prelude::{FormData, *}; 8 + use http::StatusCode; 9 + use humansize::format_size; 10 + use jacquard::api::com_atproto::repo::get_record::GetRecordOutput; 11 + use jacquard::bytes::Bytes; 12 + use jacquard::client::AgentError; 13 + use jacquard::{atproto, prelude::*}; 14 + use jacquard::{ 15 + client::AgentSessionExt, 16 + common::{Data, IntoStatic}, 17 + types::{aturi::AtUri, ident::AtIdentifier, string::Nsid}, 18 + }; 19 + use jacquard_lexicon::lexicon::LexiconDoc; 20 + use jacquard_lexicon::validation::ValidationResult; 21 + use mime_sniffer::MimeTypeSniffer; 22 + use weaver_api::com_atproto::repo::{ 23 + create_record::CreateRecord, delete_record::DeleteRecord, put_record::PutRecord, 24 + }; 25 + // ============================================================================ 26 + // Pretty Editor: Component Hierarchy 27 + // ============================================================================ 28 + 29 + /// Main dispatcher - routes to specific field editors based on Data type 30 + #[component] 31 + fn EditableDataView( 32 + root: Signal<Data<'static>>, 33 + path: String, 34 + did: String, 35 + #[props(default)] remove_button: Option<Element>, 36 + ) -> Element { 37 + let path_for_memo = path.clone(); 38 + let root_read = root.read(); 39 + 40 + match root_read 41 + .get_at_path(&path_for_memo) 42 + .map(|d| d.clone().into_static()) 43 + { 44 + Some(Data::Object(_)) => { 45 + rsx! { EditableObjectField { root, path: path.clone(), did, remove_button } } 46 + } 47 + Some(Data::Array(_)) => rsx! { EditableArrayField { root, path: path.clone(), did } }, 48 + Some(Data::String(_)) => { 49 + rsx! { EditableStringField { root, path: path.clone(), remove_button } } 50 + } 51 + Some(Data::Integer(_)) => { 52 + rsx! { EditableIntegerField { root, path: path.clone(), remove_button } } 53 + } 54 + Some(Data::Boolean(_)) => { 55 + rsx! { EditableBooleanField { root, path: path.clone(), remove_button } } 56 + } 57 + Some(Data::Null) => rsx! { EditableNullField { root, path: path.clone(), remove_button } }, 58 + Some(Data::Blob(_)) => { 59 + rsx! { EditableBlobField { root, path: path.clone(), did, remove_button } } 60 + } 61 + Some(Data::Bytes(_)) => { 62 + rsx! { EditableBytesField { root, path: path.clone(), remove_button } } 63 + } 64 + Some(Data::CidLink(_)) => { 65 + rsx! { EditableCidLinkField { root, path: path.clone(), remove_button } } 66 + } 67 + 68 + None => rsx! { div { class: "field-error", "❌ Path not found: {path}" } }, 69 + } 70 + } 71 + 72 + // ============================================================================ 73 + // Primitive Field Editors 74 + // ============================================================================ 75 + 76 + /// String field with type preservation 77 + #[component] 78 + fn EditableStringField( 79 + root: Signal<Data<'static>>, 80 + path: String, 81 + #[props(default)] remove_button: Option<Element>, 82 + ) -> Element { 83 + use jacquard::types::LexiconStringType; 84 + 85 + let path_for_text = path.clone(); 86 + let path_for_type = path.clone(); 87 + 88 + // Get current string value 89 + let current_text = use_memo(move || { 90 + root.read() 91 + .get_at_path(&path_for_text) 92 + .and_then(|d| d.as_str()) 93 + .map(|s| s.to_string()) 94 + .unwrap_or_default() 95 + }); 96 + 97 + // Get string type (Copy, cheap to store) 98 + let string_type = use_memo(move || { 99 + root.read() 100 + .get_at_path(&path_for_type) 101 + .and_then(|d| match d { 102 + Data::String(s) => Some(s.string_type()), 103 + _ => None, 104 + }) 105 + .unwrap_or(LexiconStringType::String) 106 + }); 107 + 108 + // Local state for invalid input 109 + let mut input_text = use_signal(|| current_text()); 110 + let mut parse_error = use_signal(|| None::<String>); 111 + 112 + // Sync input when current changes 113 + use_effect(move || { 114 + input_text.set(current_text()); 115 + }); 116 + 117 + let path_for_mutation = path.clone(); 118 + let handle_input = move |evt: Event<FormData>| { 119 + let new_text = evt.value(); 120 + input_text.set(new_text.clone()); 121 + 122 + match try_parse_as_type(&new_text, string_type()) { 123 + Ok(new_atproto_str) => { 124 + parse_error.set(None); 125 + let mut new_data = root.read().clone(); 126 + new_data.set_at_path(&path_for_mutation, Data::String(new_atproto_str)); 127 + root.set(new_data); 128 + } 129 + Err(e) => { 130 + parse_error.set(Some(e)); 131 + } 132 + } 133 + }; 134 + 135 + let type_label = format!("{:?}", string_type()).to_lowercase(); 136 + let is_plain_string = string_type() == LexiconStringType::String; 137 + 138 + // Dynamic width based on content length 139 + let input_width = use_memo(move || { 140 + let len = input_text().len(); 141 + let min_width = match string_type() { 142 + LexiconStringType::Cid => 60, 143 + LexiconStringType::Nsid => 40, 144 + LexiconStringType::Did => 50, 145 + LexiconStringType::AtUri => 50, 146 + _ => 20, 147 + }; 148 + format!("{}ch", len.max(min_width)) 149 + }); 150 + 151 + rsx! { 152 + div { class: "record-field", 153 + div { class: "field-header", 154 + PathLabel { path: path.clone() } 155 + if type_label != "string" { 156 + span { class: "string-type-tag", " [{type_label}]" } 157 + } 158 + {remove_button} 159 + } 160 + if is_plain_string { 161 + textarea { 162 + value: "{input_text}", 163 + oninput: handle_input, 164 + class: if parse_error().is_some() { "invalid" } else { "" }, 165 + rows: "1", 166 + } 167 + } else { 168 + input { 169 + r#type: "text", 170 + value: "{input_text}", 171 + style: "width: {input_width}", 172 + oninput: handle_input, 173 + class: if parse_error().is_some() { "invalid" } else { "" }, 174 + } 175 + } 176 + if let Some(err) = parse_error() { 177 + span { class: "field-error", " ❌ {err}" } 178 + } 179 + } 180 + } 181 + } 182 + 183 + /// Integer field with validation 184 + #[component] 185 + fn EditableIntegerField( 186 + root: Signal<Data<'static>>, 187 + path: String, 188 + #[props(default)] remove_button: Option<Element>, 189 + ) -> Element { 190 + let path_for_memo = path.clone(); 191 + let current_value = use_memo(move || { 192 + root.read() 193 + .get_at_path(&path_for_memo) 194 + .and_then(|d| d.as_integer()) 195 + .unwrap_or(0) 196 + }); 197 + 198 + let mut input_text = use_signal(|| current_value().to_string()); 199 + let mut parse_error = use_signal(|| None::<String>); 200 + 201 + use_effect(move || { 202 + input_text.set(current_value().to_string()); 203 + }); 204 + 205 + let path_for_mutation = path.clone(); 206 + 207 + rsx! { 208 + div { class: "record-field", 209 + div { class: "field-header", 210 + PathLabel { path: path.clone() } 211 + {remove_button} 212 + } 213 + input { 214 + r#type: "number", 215 + value: "{input_text}", 216 + oninput: move |evt| { 217 + let text = evt.value(); 218 + input_text.set(text.clone()); 219 + 220 + match text.parse::<i64>() { 221 + Ok(num) => { 222 + parse_error.set(None); 223 + let mut data_edit = root.write_unchecked(); 224 + data_edit.set_at_path(&path_for_mutation, Data::Integer(num)); 225 + } 226 + Err(_) => { 227 + parse_error.set(Some("Must be a valid integer".to_string())); 228 + } 229 + } 230 + } 231 + } 232 + if let Some(err) = parse_error() { 233 + span { class: "field-error", " ❌ {err}" } 234 + } 235 + } 236 + } 237 + } 238 + 239 + /// Boolean field (toggle button) 240 + #[component] 241 + fn EditableBooleanField( 242 + root: Signal<Data<'static>>, 243 + path: String, 244 + #[props(default)] remove_button: Option<Element>, 245 + ) -> Element { 246 + let path_for_memo = path.clone(); 247 + let current_value = use_memo(move || { 248 + root.read() 249 + .get_at_path(&path_for_memo) 250 + .and_then(|d| d.as_boolean()) 251 + .unwrap_or(false) 252 + }); 253 + 254 + let path_for_mutation = path.clone(); 255 + rsx! { 256 + div { class: "record-field", 257 + div { class: "field-header", 258 + PathLabel { path: path.clone() } 259 + {remove_button} 260 + } 261 + button { 262 + class: if current_value() { "boolean-toggle boolean-toggle-true" } else { "boolean-toggle boolean-toggle-false" }, 263 + onclick: move |_| { 264 + root.with_mut(|data| { 265 + if let Some(target) = data.get_at_path_mut(path_for_mutation.as_str()) { 266 + if let Some(bool_val) = target.as_boolean() { 267 + *target = Data::Boolean(!bool_val); 268 + } 269 + } 270 + }); 271 + }, 272 + "{current_value()}" 273 + } 274 + } 275 + } 276 + } 277 + 278 + /// Null field with type inference 279 + #[component] 280 + fn EditableNullField( 281 + root: Signal<Data<'static>>, 282 + path: String, 283 + #[props(default)] remove_button: Option<Element>, 284 + ) -> Element { 285 + let mut input_text = use_signal(|| String::new()); 286 + let mut parse_error = use_signal(|| None::<String>); 287 + 288 + let path_for_mutation = path.clone(); 289 + rsx! { 290 + div { class: "record-field", 291 + div { class: "field-header", 292 + PathLabel { path: path.clone() } 293 + span { class: "field-value muted", "null" } 294 + {remove_button} 295 + } 296 + input { 297 + r#type: "text", 298 + placeholder: "Enter value (or {{}}, [], true, 123)...", 299 + value: "{input_text}", 300 + oninput: move |evt| { 301 + input_text.set(evt.value()); 302 + }, 303 + onkeydown: move |evt| { 304 + use dioxus::prelude::keyboard_types::Key; 305 + if evt.key() == Key::Enter { 306 + let text = input_text(); 307 + match infer_data_from_text(&text) { 308 + Ok(new_value) => { 309 + root.with_mut(|data| { 310 + if let Some(target) = data.get_at_path_mut(path_for_mutation.as_str()) { 311 + *target = new_value; 312 + } 313 + }); 314 + input_text.set(String::new()); 315 + parse_error.set(None); 316 + } 317 + Err(e) => { 318 + parse_error.set(Some(e)); 319 + } 320 + } 321 + } 322 + } 323 + } 324 + if let Some(err) = parse_error() { 325 + span { class: "field-error", " ❌ {err}" } 326 + } 327 + } 328 + } 329 + } 330 + 331 + /// Blob field - shows CID, size (editable), mime type (read-only), file upload 332 + #[component] 333 + fn EditableBlobField( 334 + root: Signal<Data<'static>>, 335 + path: String, 336 + did: String, 337 + #[props(default)] remove_button: Option<Element>, 338 + ) -> Element { 339 + let path_for_memo = path.clone(); 340 + let blob_data = use_memo(move || { 341 + root.read() 342 + .get_at_path(&path_for_memo) 343 + .and_then(|d| match d { 344 + Data::Blob(blob) => Some(( 345 + blob.r#ref.to_string(), 346 + blob.size, 347 + blob.mime_type.as_str().to_string(), 348 + )), 349 + _ => None, 350 + }) 351 + }); 352 + 353 + let mut cid_input = use_signal(|| String::new()); 354 + let mut size_input = use_signal(|| String::new()); 355 + let mut cid_error = use_signal(|| None::<String>); 356 + let mut size_error = use_signal(|| None::<String>); 357 + let mut uploading = use_signal(|| false); 358 + let mut upload_error = use_signal(|| None::<String>); 359 + let mut preview_data_url = use_signal(|| None::<String>); 360 + 361 + // Sync inputs when blob data changes 362 + use_effect(move || { 363 + if let Some((cid, size, _)) = blob_data() { 364 + cid_input.set(cid); 365 + size_input.set(size.to_string()); 366 + } 367 + }); 368 + 369 + let fetcher = use_context::<Fetcher>(); 370 + let path_for_upload = path.clone(); 371 + let handle_file = move |evt: Event<FormData>| { 372 + let fetcher = fetcher.clone(); 373 + let path_upload_clone = path_for_upload.clone(); 374 + spawn(async move { 375 + uploading.set(true); 376 + upload_error.set(None); 377 + 378 + let files = evt.files(); 379 + for file_data in files { 380 + match file_data.read_bytes().await { 381 + Ok(bytes_data) => { 382 + // Convert to jacquard Bytes and sniff MIME type 383 + let bytes = Bytes::from(bytes_data.to_vec()); 384 + let mime_str = bytes 385 + .sniff_mime_type() 386 + .unwrap_or("application/octet-stream"); 387 + let mime_type = jacquard::types::blob::MimeType::new_owned(mime_str); 388 + 389 + // Create data URL for immediate preview if it's an image 390 + if mime_str.starts_with("image/") { 391 + let base64_data = base64::Engine::encode( 392 + &base64::engine::general_purpose::STANDARD, 393 + &bytes, 394 + ); 395 + let data_url = format!("data:{};base64,{}", mime_str, base64_data); 396 + preview_data_url.set(Some(data_url.clone())); 397 + 398 + // Try to decode dimensions and populate aspectRatio field 399 + #[cfg(target_arch = "wasm32")] 400 + { 401 + let path_clone = path_upload_clone.clone(); 402 + spawn(async move { 403 + if let Some((width, height)) = 404 + decode_image_dimensions(&data_url).await 405 + { 406 + populate_aspect_ratio( 407 + root, 408 + &path_clone, 409 + width as i64, 410 + height as i64, 411 + ); 412 + } 413 + }); 414 + } 415 + } 416 + 417 + // Upload blob 418 + let client = fetcher.get_client(); 419 + match client.upload_blob(bytes, mime_type).await { 420 + Ok(new_blob) => { 421 + // Update blob in record 422 + let path_ref = path_upload_clone.clone(); 423 + root.with_mut(|record_data| { 424 + if let Some(Data::Blob(blob)) = 425 + record_data.get_at_path_mut(&path_ref) 426 + { 427 + *blob = new_blob; 428 + } 429 + }); 430 + upload_error.set(None); 431 + } 432 + Err(e) => { 433 + upload_error.set(Some(format!("Upload failed: {:?}", e))); 434 + } 435 + } 436 + } 437 + Err(e) => { 438 + upload_error.set(Some(format!("Failed to read file: {}", e))); 439 + } 440 + } 441 + } 442 + 443 + uploading.set(false); 444 + }); 445 + }; 446 + 447 + let path_for_cid = path.clone(); 448 + let handle_cid_change = move |evt: Event<FormData>| { 449 + let text = evt.value(); 450 + cid_input.set(text.clone()); 451 + 452 + match jacquard::types::cid::CidLink::new_owned(text.as_bytes()) { 453 + Ok(new_cid_link) => { 454 + cid_error.set(None); 455 + root.with_mut(|data| { 456 + if let Some(Data::Blob(blob)) = data.get_at_path_mut(&path_for_cid) { 457 + blob.r#ref = new_cid_link; 458 + } 459 + }); 460 + } 461 + Err(_) => { 462 + cid_error.set(Some("Invalid CID format".to_string())); 463 + } 464 + } 465 + }; 466 + 467 + let path_for_size = path.clone(); 468 + let handle_size_change = move |evt: Event<FormData>| { 469 + let text = evt.value(); 470 + size_input.set(text.clone()); 471 + 472 + match text.parse::<usize>() { 473 + Ok(new_size) => { 474 + size_input.set(format_size(new_size, humansize::BINARY)); 475 + size_error.set(None); 476 + root.with_mut(|data| { 477 + if let Some(Data::Blob(blob)) = data.get_at_path_mut(&path_for_size) { 478 + blob.size = new_size; 479 + } 480 + }); 481 + } 482 + Err(_) => { 483 + size_error.set(Some("Must be a non-negative integer".to_string())); 484 + } 485 + } 486 + }; 487 + 488 + let placeholder_cid = "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 489 + let is_placeholder = blob_data() 490 + .map(|(cid, _, _)| cid == placeholder_cid) 491 + .unwrap_or(true); 492 + let is_image = blob_data() 493 + .map(|(_, _, mime)| mime.starts_with("image/")) 494 + .unwrap_or(false); 495 + 496 + // Use preview data URL if available (fresh upload), otherwise CDN 497 + let image_url = if let Some(data_url) = preview_data_url() { 498 + Some(data_url) 499 + } else if !is_placeholder && is_image { 500 + blob_data().map(|(cid, _, mime)| { 501 + let format = mime.strip_prefix("image/").unwrap_or("jpeg"); 502 + format!( 503 + "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@{}", 504 + did, cid, format 505 + ) 506 + }) 507 + } else { 508 + None 509 + }; 510 + 511 + rsx! { 512 + div { class: "record-field blob-field", 513 + div { class: "field-header", 514 + PathLabel { path: path.clone() } 515 + span { class: "string-type-tag", " [blob]" } 516 + {remove_button} 517 + } 518 + div { class: "blob-fields", 519 + div { class: "blob-field-row blob-field-cid", 520 + label { "CID:" } 521 + input { 522 + r#type: "text", 523 + value: "{cid_input}", 524 + oninput: handle_cid_change, 525 + class: if cid_error().is_some() { "invalid" } else { "" }, 526 + } 527 + if let Some(err) = cid_error() { 528 + span { class: "field-error", " ❌ {err}" } 529 + } 530 + } 531 + div { class: "blob-field-row", 532 + label { "Size:" } 533 + input { 534 + r#type: "number", 535 + value: "{size_input}", 536 + oninput: handle_size_change, 537 + class: if size_error().is_some() { "invalid" } else { "" }, 538 + } 539 + if let Some(err) = size_error() { 540 + span { class: "field-error", " ❌ {err}" } 541 + } 542 + } 543 + div { class: "blob-field-row", 544 + label { "MIME Type:" } 545 + span { class: "readonly", 546 + "{blob_data().map(|(_, _, mime)| mime).unwrap_or_default()}" 547 + } 548 + } 549 + if let Some(url) = image_url { 550 + img { 551 + src: "{url}", 552 + alt: "Blob preview", 553 + class: "blob-image", 554 + } 555 + } 556 + div { class: "blob-upload-section", 557 + input { 558 + r#type: "file", 559 + accept: if is_image { "image/*" } else { "*/*" }, 560 + onchange: handle_file, 561 + disabled: uploading(), 562 + } 563 + if uploading() { 564 + span { class: "upload-status", "Uploading..." } 565 + } 566 + if let Some(err) = upload_error() { 567 + div { class: "field-error", "❌ {err}" } 568 + } 569 + } 570 + } 571 + } 572 + } 573 + } 574 + 575 + /// Decode image dimensions from data URL using browser Image API 576 + #[cfg(target_arch = "wasm32")] 577 + async fn decode_image_dimensions(data_url: &str) -> Option<(u32, u32)> { 578 + use wasm_bindgen::JsCast; 579 + use wasm_bindgen::prelude::*; 580 + use wasm_bindgen_futures::JsFuture; 581 + 582 + let window = web_sys::window()?; 583 + let document = window.document()?; 584 + 585 + let img = document.create_element("img").ok()?; 586 + let img = img.dyn_into::<web_sys::HtmlImageElement>().ok()?; 587 + 588 + img.set_src(data_url); 589 + 590 + // Wait for image to load 591 + let promise = js_sys::Promise::new(&mut |resolve, _reject| { 592 + let onload = Closure::wrap(Box::new(move || { 593 + resolve.call0(&JsValue::NULL).ok(); 594 + }) as Box<dyn FnMut()>); 595 + 596 + img.set_onload(Some(onload.as_ref().unchecked_ref())); 597 + onload.forget(); 598 + }); 599 + 600 + JsFuture::from(promise).await.ok()?; 601 + 602 + Some((img.natural_width(), img.natural_height())) 603 + } 604 + 605 + /// Find and populate aspectRatio field for a blob 606 + #[allow(unused)] 607 + fn populate_aspect_ratio( 608 + mut root: Signal<Data<'static>>, 609 + blob_path: &str, 610 + width: i64, 611 + height: i64, 612 + ) { 613 + // Query for all aspectRatio fields and collect the path we want 614 + let aspect_path_to_update = { 615 + let data = root.read(); 616 + let query_result = data.query("...aspectRatio"); 617 + 618 + query_result.multiple().and_then(|matches| { 619 + // Find aspectRatio that's a sibling of our blob 620 + // e.g. blob at "embed.images[0].image" -> look for "embed.images[0].aspectRatio" 621 + let blob_parent = blob_path.rsplit_once('.').map(|(parent, _)| parent); 622 + matches.iter().find_map(|query_match| { 623 + let aspect_parent = query_match.path.rsplit_once('.').map(|(parent, _)| parent); 624 + 625 + // Check if they share the same parent 626 + if blob_parent == aspect_parent { 627 + Some(query_match.path.clone()) 628 + } else { 629 + None 630 + } 631 + }) 632 + }) 633 + }; 634 + 635 + // Update the aspectRatio if we found a matching field 636 + if let Some(aspect_path) = aspect_path_to_update { 637 + let aspect_obj = atproto! {{ 638 + "width": width, 639 + "height": height 640 + }}; 641 + 642 + root.with_mut(|record_data| { 643 + record_data.set_at_path(&aspect_path, aspect_obj); 644 + }); 645 + } 646 + } 647 + 648 + /// Bytes field with hex/base64 auto-detection 649 + #[component] 650 + fn EditableBytesField( 651 + root: Signal<Data<'static>>, 652 + path: String, 653 + #[props(default)] remove_button: Option<Element>, 654 + ) -> Element { 655 + let path_for_memo = path.clone(); 656 + let current_bytes = use_memo(move || { 657 + root.read() 658 + .get_at_path(&path_for_memo) 659 + .and_then(|d| match d { 660 + Data::Bytes(b) => Some(bytes_to_hex(b)), 661 + _ => None, 662 + }) 663 + }); 664 + 665 + let mut input_text = use_signal(|| String::new()); 666 + let mut parse_error = use_signal(|| None::<String>); 667 + let mut detected_format = use_signal(|| None::<String>); 668 + 669 + // Sync input when bytes change 670 + use_effect(move || { 671 + if let Some(hex) = current_bytes() { 672 + input_text.set(hex); 673 + } 674 + }); 675 + 676 + let path_for_mutation = path.clone(); 677 + let handle_input = move |evt: Event<FormData>| { 678 + let text = evt.value(); 679 + input_text.set(text.clone()); 680 + 681 + match parse_bytes_input(&text) { 682 + Ok((bytes, format)) => { 683 + parse_error.set(None); 684 + detected_format.set(Some(format)); 685 + root.with_mut(|data| { 686 + if let Some(target) = data.get_at_path_mut(&path_for_mutation) { 687 + *target = Data::Bytes(bytes); 688 + } 689 + }); 690 + } 691 + Err(e) => { 692 + parse_error.set(Some(e)); 693 + detected_format.set(None); 694 + } 695 + } 696 + }; 697 + 698 + let byte_count = current_bytes() 699 + .map(|hex| hex.chars().filter(|c| c.is_ascii_hexdigit()).count() / 2) 700 + .unwrap_or(0); 701 + let size_label = if byte_count > 128 { 702 + format_size(byte_count, humansize::BINARY) 703 + } else { 704 + format!("{} bytes", byte_count) 705 + }; 706 + 707 + rsx! { 708 + div { class: "record-field bytes-field", 709 + div { class: "field-header", 710 + PathLabel { path: path.clone() } 711 + span { class: "string-type-tag", " [bytes: {size_label}]" } 712 + if let Some(format) = detected_format() { 713 + span { class: "bytes-format-tag", " ({format})" } 714 + } 715 + {remove_button} 716 + } 717 + textarea { 718 + value: "{input_text}", 719 + placeholder: "Paste hex (1a2b3c...) or base64 (YWJj...)", 720 + oninput: handle_input, 721 + class: if parse_error().is_some() { "invalid" } else { "" }, 722 + rows: "3", 723 + } 724 + if let Some(err) = parse_error() { 725 + span { class: "field-error", " ❌ {err}" } 726 + } 727 + } 728 + } 729 + } 730 + 731 + /// Parse bytes from hex or base64, auto-detecting format 732 + fn parse_bytes_input(text: &str) -> Result<(Bytes, String), String> { 733 + let trimmed = text.trim(); 734 + if trimmed.is_empty() { 735 + return Err("Input is empty".to_string()); 736 + } 737 + 738 + // Remove common whitespace/separators 739 + let cleaned: String = trimmed 740 + .chars() 741 + .filter(|c| !c.is_whitespace() && *c != ':' && *c != '-') 742 + .collect(); 743 + 744 + // Try hex first (more restrictive) 745 + if cleaned.chars().all(|c| c.is_ascii_hexdigit()) { 746 + parse_hex_bytes(&cleaned).map(|b| (b, "hex".to_string())) 747 + } else { 748 + // Try base64 749 + parse_base64_bytes(&cleaned).map(|b| (b, "base64".to_string())) 750 + } 751 + } 752 + 753 + /// Parse hex string to bytes 754 + fn parse_hex_bytes(hex: &str) -> Result<Bytes, String> { 755 + if hex.len() % 2 != 0 { 756 + return Err("Hex string must have even length".to_string()); 757 + } 758 + 759 + let mut bytes = Vec::with_capacity(hex.len() / 2); 760 + for chunk in hex.as_bytes().chunks(2) { 761 + let hex_byte = std::str::from_utf8(chunk).map_err(|e| format!("Invalid UTF-8: {}", e))?; 762 + let byte = 763 + u8::from_str_radix(hex_byte, 16).map_err(|e| format!("Invalid hex digit: {}", e))?; 764 + bytes.push(byte); 765 + } 766 + 767 + Ok(Bytes::from(bytes)) 768 + } 769 + 770 + /// Parse base64 string to bytes 771 + fn parse_base64_bytes(b64: &str) -> Result<Bytes, String> { 772 + use base64::Engine; 773 + let engine = base64::engine::general_purpose::STANDARD; 774 + 775 + engine 776 + .decode(b64) 777 + .map(Bytes::from) 778 + .map_err(|e| format!("Invalid base64: {}", e)) 779 + } 780 + 781 + /// Convert bytes to hex display string (with spacing every 4 chars) 782 + fn bytes_to_hex(bytes: &Bytes) -> String { 783 + bytes 784 + .iter() 785 + .enumerate() 786 + .map(|(i, b)| { 787 + let hex = format!("{:02x}", b); 788 + if i > 0 && i % 2 == 0 { 789 + format!(" {}", hex) 790 + } else { 791 + hex 792 + } 793 + }) 794 + .collect() 795 + } 796 + 797 + /// CidLink field with validation 798 + #[component] 799 + fn EditableCidLinkField( 800 + root: Signal<Data<'static>>, 801 + path: String, 802 + #[props(default)] remove_button: Option<Element>, 803 + ) -> Element { 804 + let path_for_memo = path.clone(); 805 + let current_cid = use_memo(move || { 806 + root.read() 807 + .get_at_path(&path_for_memo) 808 + .map(|d| match d { 809 + Data::CidLink(cid) => cid.to_string(), 810 + _ => String::new(), 811 + }) 812 + .unwrap_or_default() 813 + }); 814 + 815 + let mut input_text = use_signal(|| String::new()); 816 + let mut parse_error = use_signal(|| None::<String>); 817 + 818 + use_effect(move || { 819 + input_text.set(current_cid()); 820 + }); 821 + 822 + let input_width = use_memo(move || { 823 + let len = input_text().len(); 824 + format!("{}ch", len.max(60)) 825 + }); 826 + 827 + let path_for_mutation = path.clone(); 828 + let handle_input = move |evt: Event<FormData>| { 829 + let text = evt.value(); 830 + input_text.set(text.clone()); 831 + 832 + match jacquard::types::cid::Cid::new_owned(text.as_bytes()) { 833 + Ok(new_cid) => { 834 + parse_error.set(None); 835 + root.with_mut(|data| { 836 + if let Some(target) = data.get_at_path_mut(&path_for_mutation) { 837 + *target = Data::CidLink(new_cid); 838 + } 839 + }); 840 + } 841 + Err(_) => { 842 + parse_error.set(Some("Invalid CID format".to_string())); 843 + } 844 + } 845 + }; 846 + 847 + rsx! { 848 + div { class: "record-field cidlink-field", 849 + div { class: "field-header", 850 + PathLabel { path: path.clone() } 851 + span { class: "string-type-tag", " [cid-link]" } 852 + {remove_button} 853 + } 854 + input { 855 + r#type: "text", 856 + value: "{input_text}", 857 + style: "width: {input_width}", 858 + placeholder: "bafyrei...", 859 + oninput: handle_input, 860 + class: if parse_error().is_some() { "invalid" } else { "" }, 861 + } 862 + if let Some(err) = parse_error() { 863 + span { class: "field-error", " ❌ {err}" } 864 + } 865 + } 866 + } 867 + } 868 + 869 + // ============================================================================ 870 + // Field with Remove Button Wrapper 871 + // ============================================================================ 872 + 873 + /// Wraps a field with an optional remove button in the header 874 + #[component] 875 + fn FieldWithRemove( 876 + root: Signal<Data<'static>>, 877 + path: String, 878 + did: String, 879 + is_removable: bool, 880 + parent_path: String, 881 + field_key: String, 882 + ) -> Element { 883 + let remove_button = if is_removable { 884 + Some(rsx! { 885 + button { 886 + class: "field-remove-button", 887 + onclick: move |_| { 888 + let mut new_data = root.read().clone(); 889 + if let Some(Data::Object(obj)) = new_data.get_at_path_mut(parent_path.as_str()) { 890 + obj.0.remove(field_key.as_str()); 891 + } 892 + root.set(new_data); 893 + }, 894 + "Remove" 895 + } 896 + }) 897 + } else { 898 + None 899 + }; 900 + 901 + rsx! { 902 + EditableDataView { 903 + root: root, 904 + path: path.clone(), 905 + did: did.clone(), 906 + remove_button: remove_button, 907 + } 908 + } 909 + } 910 + 911 + // ============================================================================ 912 + // Array Field Editor (enables recursion) 913 + // ============================================================================ 914 + 915 + /// Array field - iterates items and renders child EditableDataView for each 916 + #[component] 917 + fn EditableArrayField(root: Signal<Data<'static>>, path: String, did: String) -> Element { 918 + let path_for_memo = path.clone(); 919 + let array_len = use_memo(move || { 920 + root.read() 921 + .get_at_path(&path_for_memo) 922 + .and_then(|d| d.as_array()) 923 + .map(|arr| arr.0.len()) 924 + .unwrap_or(0) 925 + }); 926 + 927 + let path_for_add = path.clone(); 928 + 929 + rsx! { 930 + div { class: "record-section array-section", 931 + Accordion { 932 + id: "edit-array-{path}", 933 + collapsible: true, 934 + AccordionItem { 935 + default_open: true, 936 + index: 0, 937 + AccordionTrigger { 938 + div { class: "section-header", 939 + div { class: "section-label", 940 + { 941 + let parts: Vec<&str> = path.split('.').collect(); 942 + let final_part = parts.last().unwrap_or(&""); 943 + rsx! { "{final_part}" } 944 + } 945 + } 946 + span { class: "array-length", "[{array_len}]" } 947 + } 948 + } 949 + AccordionContent { 950 + div { class: "section-content", 951 + for idx in 0..array_len() { 952 + { 953 + let item_path = format!("{}[{}]", path, idx); 954 + let path_for_remove = path.clone(); 955 + 956 + rsx! { 957 + div { 958 + class: "array-item", 959 + key: "{item_path}", 960 + 961 + EditableDataView { 962 + root: root, 963 + path: item_path.clone(), 964 + did: did.clone(), 965 + remove_button: rsx! { 966 + button { 967 + class: "field-remove-button", 968 + onclick: move |_| { 969 + root.with_mut(|data| { 970 + if let Some(Data::Array(arr)) = data.get_at_path_mut(&path_for_remove) { 971 + arr.0.remove(idx); 972 + } 973 + }); 974 + }, 975 + "Remove" 976 + } 977 + } 978 + } 979 + } 980 + } 981 + } 982 + } 983 + div { 984 + class: "array-item", 985 + div { 986 + class: "add-field-widget", 987 + button { 988 + onclick: move |_| { 989 + root.with_mut(|data| { 990 + if let Some(Data::Array(arr)) = data.get_at_path_mut(&path_for_add) { 991 + let new_item = create_array_item_default(arr); 992 + arr.0.push(new_item); 993 + } 994 + }); 995 + }, 996 + "+ Add Item" 997 + } 998 + } 999 + } 1000 + } 1001 + } 1002 + } 1003 + } 1004 + } 1005 + } 1006 + } 1007 + 1008 + // ============================================================================ 1009 + // Object Field Editor (enables recursion) 1010 + // ============================================================================ 1011 + 1012 + /// Object field - iterates fields and renders child EditableDataView for each 1013 + #[component] 1014 + fn EditableObjectField( 1015 + root: Signal<Data<'static>>, 1016 + path: String, 1017 + did: String, 1018 + #[props(default)] remove_button: Option<Element>, 1019 + ) -> Element { 1020 + let path_for_memo = path.clone(); 1021 + let field_keys = use_memo(move || { 1022 + root.read() 1023 + .get_at_path(&path_for_memo) 1024 + .and_then(|d| d.as_object()) 1025 + .map(|obj| obj.0.keys().cloned().collect::<Vec<_>>()) 1026 + .unwrap_or_default() 1027 + }); 1028 + 1029 + let is_root = path.is_empty(); 1030 + 1031 + rsx! { 1032 + if !is_root { 1033 + div { class: "record-section object-section", 1034 + Accordion { 1035 + id: "edit-object-{path}", 1036 + collapsible: true, 1037 + AccordionItem { 1038 + default_open: true, 1039 + index: 0, 1040 + AccordionTrigger { 1041 + div { class: "section-header", 1042 + div { class: "section-label", 1043 + { 1044 + let parts: Vec<&str> = path.split('.').collect(); 1045 + let final_part = parts.last().unwrap_or(&""); 1046 + rsx! { "{final_part}" } 1047 + } 1048 + } 1049 + {remove_button} 1050 + } 1051 + } 1052 + AccordionContent { 1053 + div { class: "section-content", 1054 + for key in field_keys() { 1055 + { 1056 + let field_path = if path.is_empty() { 1057 + key.to_string() 1058 + } else { 1059 + format!("{}.{}", path, key) 1060 + }; 1061 + let is_type_field = key == "$type"; 1062 + 1063 + rsx! { 1064 + FieldWithRemove { 1065 + key: "{field_path}", 1066 + root: root, 1067 + path: field_path.clone(), 1068 + did: did.clone(), 1069 + is_removable: !is_type_field, 1070 + parent_path: path.clone(), 1071 + field_key: key.clone(), 1072 + } 1073 + } 1074 + } 1075 + } 1076 + 1077 + AddFieldWidget { root: root, path: path.clone() } 1078 + } 1079 + } 1080 + } 1081 + } 1082 + } 1083 + } else { 1084 + for key in field_keys() { 1085 + { 1086 + let field_path = key.to_string(); 1087 + let is_type_field = key == "$type"; 1088 + 1089 + rsx! { 1090 + FieldWithRemove { 1091 + key: "{field_path}", 1092 + root: root, 1093 + path: field_path.clone(), 1094 + did: did.clone(), 1095 + is_removable: !is_type_field, 1096 + parent_path: path.clone(), 1097 + field_key: key.clone(), 1098 + } 1099 + } 1100 + } 1101 + } 1102 + 1103 + AddFieldWidget { root: root, path: path.clone() } 1104 + } 1105 + } 1106 + } 1107 + 1108 + /// Widget for adding new fields to objects 1109 + #[component] 1110 + fn AddFieldWidget(root: Signal<Data<'static>>, path: String) -> Element { 1111 + let mut field_name = use_signal(|| String::new()); 1112 + let mut field_value = use_signal(|| String::new()); 1113 + let mut error = use_signal(|| None::<String>); 1114 + let mut show_form = use_signal(|| false); 1115 + 1116 + let path_for_enter = path.clone(); 1117 + let path_for_button = path.clone(); 1118 + 1119 + rsx! { 1120 + div { class: "add-field-widget", 1121 + if !show_form() { 1122 + button { 1123 + class: "add-button", 1124 + onclick: move |_| show_form.set(true), 1125 + "+ Add Field" 1126 + } 1127 + } else { 1128 + div { class: "add-field-form", 1129 + input { 1130 + r#type: "text", 1131 + placeholder: "Field name", 1132 + value: "{field_name}", 1133 + oninput: move |evt| field_name.set(evt.value()), 1134 + } 1135 + input { 1136 + r#type: "text", 1137 + placeholder: r#"Value: {{}}, [], true, 123, "text""#, 1138 + value: "{field_value}", 1139 + oninput: move |evt| field_value.set(evt.value()), 1140 + onkeydown: move |evt| { 1141 + use dioxus::prelude::keyboard_types::Key; 1142 + if evt.key() == Key::Enter { 1143 + let name = field_name(); 1144 + let value_text = field_value(); 1145 + 1146 + if name.is_empty() { 1147 + error.set(Some("Field name required".to_string())); 1148 + return; 1149 + } 1150 + 1151 + let new_value = match infer_data_from_text(&value_text) { 1152 + Ok(data) => data, 1153 + Err(e) => { 1154 + error.set(Some(e)); 1155 + return; 1156 + } 1157 + }; 1158 + 1159 + let mut new_data = root.read().clone(); 1160 + if let Some(Data::Object(obj)) = new_data.get_at_path_mut(path_for_enter.as_str()) { 1161 + obj.0.insert(name.into(), new_value); 1162 + } 1163 + root.set(new_data); 1164 + 1165 + // Reset form 1166 + field_name.set(String::new()); 1167 + field_value.set(String::new()); 1168 + show_form.set(false); 1169 + error.set(None); 1170 + } 1171 + } 1172 + } 1173 + button { 1174 + class: "add-field-widget-edit", 1175 + onclick: move |_| { 1176 + let name = field_name(); 1177 + let value_text = field_value(); 1178 + 1179 + if name.is_empty() { 1180 + error.set(Some("Field name required".to_string())); 1181 + return; 1182 + } 1183 + 1184 + let new_value = match infer_data_from_text(&value_text) { 1185 + Ok(data) => data, 1186 + Err(e) => { 1187 + error.set(Some(e)); 1188 + return; 1189 + } 1190 + }; 1191 + 1192 + let mut new_data = root.read().clone(); 1193 + if let Some(Data::Object(obj)) = new_data.get_at_path_mut(path_for_button.as_str()) { 1194 + obj.0.insert(name.into(), new_value); 1195 + } 1196 + root.set(new_data); 1197 + 1198 + // Reset form 1199 + field_name.set(String::new()); 1200 + field_value.set(String::new()); 1201 + show_form.set(false); 1202 + error.set(None); 1203 + }, 1204 + "Add" 1205 + } 1206 + button { 1207 + class: "add-field-widget-edit", 1208 + onclick: move |_| { 1209 + show_form.set(false); 1210 + field_name.set(String::new()); 1211 + field_value.set(String::new()); 1212 + error.set(None); 1213 + }, 1214 + "Cancel" 1215 + } 1216 + if let Some(err) = error() { 1217 + div { class: "field-error", "❌ {err}" } 1218 + } 1219 + } 1220 + } 1221 + } 1222 + } 1223 + } 1224 + 1225 + #[component] 1226 + pub fn EditableRecordContent( 1227 + record_value: Data<'static>, 1228 + uri: ReadSignal<AtUri<'static>>, 1229 + view_mode: Signal<ViewMode>, 1230 + edit_mode: Signal<bool>, 1231 + record_resource: Resource<Result<GetRecordOutput<'static>, AgentError>>, 1232 + schema: ReadSignal<Option<LexiconDoc<'static>>>, 1233 + ) -> Element { 1234 + let mut edit_data = use_signal(use_reactive!(|record_value| record_value.clone())); 1235 + let nsid = use_memo(move || edit_data().type_discriminator().map(|s| s.to_string())); 1236 + let navigator = use_navigator(); 1237 + let fetcher = use_context::<Fetcher>(); 1238 + 1239 + // Validate edit_data whenever it changes and provide via context 1240 + let mut validation_result = use_signal(|| None); 1241 + use_effect(move || { 1242 + let _ = schema(); // Track schema changes 1243 + if let Some(nsid_str) = nsid() { 1244 + let data = edit_data(); 1245 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 1246 + let result = validator.validate_by_nsid(&nsid_str, &data); 1247 + validation_result.set(Some(result)); 1248 + } 1249 + }); 1250 + use_context_provider(|| validation_result); 1251 + 1252 + let update_fetcher = fetcher.clone(); 1253 + let create_fetcher = fetcher.clone(); 1254 + let replace_fetcher = fetcher.clone(); 1255 + let delete_fetcher = fetcher.clone(); 1256 + 1257 + rsx! { 1258 + div { 1259 + class: "tab-bar", 1260 + button { 1261 + class: if view_mode() == ViewMode::Pretty { "tab-button active" } else { "tab-button" }, 1262 + onclick: move |_| view_mode.set(ViewMode::Pretty), 1263 + "View" 1264 + } 1265 + button { 1266 + class: if view_mode() == ViewMode::Json { "tab-button active" } else { "tab-button" }, 1267 + onclick: move |_| view_mode.set(ViewMode::Json), 1268 + "JSON" 1269 + } 1270 + button { 1271 + class: if view_mode() == ViewMode::Schema { "tab-button active" } else { "tab-button" }, 1272 + onclick: move |_| view_mode.set(ViewMode::Schema), 1273 + "Schema" 1274 + } 1275 + ActionButtons { 1276 + on_update: move |_| { 1277 + let fetcher = update_fetcher.clone(); 1278 + let uri = uri(); 1279 + let data = edit_data(); 1280 + spawn(async move { 1281 + if let Some((did, _)) = fetcher.session_info().await { 1282 + if let (Some(collection_str), Some(rkey)) = (uri.collection(), uri.rkey()) { 1283 + let collection = Nsid::new(collection_str.as_str()).ok(); 1284 + if let Some(collection) = collection { 1285 + let request = PutRecord::new() 1286 + .repo(AtIdentifier::Did(did)) 1287 + .collection(collection) 1288 + .rkey(rkey.clone()) 1289 + .record(data.clone()) 1290 + .build(); 1291 + 1292 + match fetcher.send(request).await { 1293 + Ok(output) => { 1294 + if output.status() == StatusCode::OK.as_u16() { 1295 + tracing::info!("Record updated successfully"); 1296 + edit_data.set(data.clone()); 1297 + edit_mode.set(false); 1298 + } else { 1299 + tracing::error!("Unexpected status code: {:?}", output.status()); 1300 + } 1301 + } 1302 + Err(e) => { 1303 + tracing::error!("Failed to update record: {:?}", e); 1304 + } 1305 + } 1306 + } 1307 + } 1308 + } 1309 + }); 1310 + }, 1311 + on_save_new: move |_| { 1312 + let fetcher = create_fetcher.clone(); 1313 + let data = edit_data(); 1314 + let nav = navigator.clone(); 1315 + spawn(async move { 1316 + if let Some((did, _)) = fetcher.session_info().await { 1317 + if let Some(collection_str) = data.type_discriminator() { 1318 + let collection = Nsid::new(collection_str).ok(); 1319 + if let Some(collection) = collection { 1320 + let request = CreateRecord::new() 1321 + .repo(AtIdentifier::Did(did)) 1322 + .collection(collection) 1323 + .record(data.clone()) 1324 + .build(); 1325 + 1326 + match fetcher.send(request).await { 1327 + Ok(response) => { 1328 + if let Ok(output) = response.into_output() { 1329 + tracing::info!("Record created: {}", output.uri); 1330 + let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, output.uri); 1331 + nav.push(link); 1332 + } 1333 + } 1334 + Err(e) => { 1335 + tracing::error!("Failed to create record: {:?}", e); 1336 + } 1337 + } 1338 + } 1339 + } 1340 + } 1341 + }); 1342 + }, 1343 + on_replace: move |_| { 1344 + let fetcher = replace_fetcher.clone(); 1345 + let uri = uri(); 1346 + let data = edit_data(); 1347 + let nav = navigator.clone(); 1348 + spawn(async move { 1349 + if let Some((did, _)) = fetcher.session_info().await { 1350 + if let Some(new_collection_str) = data.type_discriminator() { 1351 + let new_collection = Nsid::new(new_collection_str).ok(); 1352 + if let Some(new_collection) = new_collection { 1353 + // Create new record 1354 + let create_req = CreateRecord::new() 1355 + .repo(AtIdentifier::Did(did.clone())) 1356 + .collection(new_collection) 1357 + .record(data.clone()) 1358 + .build(); 1359 + 1360 + match fetcher.send(create_req).await { 1361 + Ok(response) => { 1362 + if let Ok(create_output) = response.into_output() { 1363 + // Delete old record 1364 + if let (Some(old_collection_str), Some(old_rkey)) = (uri.collection(), uri.rkey()) { 1365 + let old_collection = Nsid::new(old_collection_str.as_str()).ok(); 1366 + if let Some(old_collection) = old_collection { 1367 + let delete_req = DeleteRecord::new() 1368 + .repo(AtIdentifier::Did(did)) 1369 + .collection(old_collection) 1370 + .rkey(old_rkey.clone()) 1371 + .build(); 1372 + 1373 + if let Err(e) = fetcher.send(delete_req).await { 1374 + tracing::warn!("Created new record but failed to delete old: {:?}", e); 1375 + } 1376 + } 1377 + } 1378 + 1379 + tracing::info!("Record replaced: {}", create_output.uri); 1380 + let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, create_output.uri); 1381 + nav.push(link); 1382 + } 1383 + } 1384 + Err(e) => { 1385 + tracing::error!("Failed to replace record: {:?}", e); 1386 + } 1387 + } 1388 + } 1389 + } 1390 + } 1391 + }); 1392 + }, 1393 + on_delete: move |_| { 1394 + let fetcher = delete_fetcher.clone(); 1395 + let uri = uri(); 1396 + let nav = navigator.clone(); 1397 + spawn(async move { 1398 + if let Some((did, _)) = fetcher.session_info().await { 1399 + if let (Some(collection_str), Some(rkey)) = (uri.collection(), uri.rkey()) { 1400 + let collection = Nsid::new(collection_str.as_str()).ok(); 1401 + if let Some(collection) = collection { 1402 + let request = DeleteRecord::new() 1403 + .repo(AtIdentifier::Did(did)) 1404 + .collection(collection) 1405 + .rkey(rkey.clone()) 1406 + .build(); 1407 + 1408 + match fetcher.send(request).await { 1409 + Ok(_) => { 1410 + tracing::info!("Record deleted"); 1411 + nav.push(Route::Home {}); 1412 + } 1413 + Err(e) => { 1414 + tracing::error!("Failed to delete record: {:?}", e); 1415 + } 1416 + } 1417 + } 1418 + } 1419 + } 1420 + }); 1421 + }, 1422 + on_cancel: move |_| { 1423 + edit_data.set(record_value.clone()); 1424 + edit_mode.set(false); 1425 + }, 1426 + } 1427 + } 1428 + div { 1429 + class: "tab-content", 1430 + match view_mode() { 1431 + ViewMode::Pretty => rsx! { 1432 + div { class: "pretty-record", 1433 + EditableDataView { 1434 + root: edit_data, 1435 + path: String::new(), 1436 + did: uri().authority().to_string(), 1437 + } 1438 + } 1439 + }, 1440 + ViewMode::Json => rsx! { 1441 + JsonEditor { data: edit_data, nsid, schema } 1442 + }, 1443 + ViewMode::Schema => rsx! { 1444 + SchemaView { schema } 1445 + }, 1446 + } 1447 + } 1448 + } 1449 + } 1450 + 1451 + #[component] 1452 + pub fn JsonEditor( 1453 + data: Signal<Data<'static>>, 1454 + nsid: ReadSignal<Option<String>>, 1455 + schema: ReadSignal<Option<LexiconDoc<'static>>>, 1456 + ) -> Element { 1457 + let mut json_text = 1458 + use_signal(|| serde_json::to_string_pretty(&*data.read()).unwrap_or_default()); 1459 + 1460 + let height = use_memo(move || { 1461 + let line_count = json_text().lines().count(); 1462 + let min_lines = 10; 1463 + let lines = line_count.max(min_lines); 1464 + // line-height is 1.5, font-size is 0.9rem (approx 14.4px), so each line is ~21.6px 1465 + // Add padding (1rem top + 1rem bottom = 2rem = 32px) 1466 + format!("{}px", lines * 22 + 32) 1467 + }); 1468 + 1469 + let validation = use_resource(move || { 1470 + let text = json_text(); 1471 + let nsid_val = nsid(); 1472 + let _ = schema(); // Track schema changes 1473 + 1474 + async move { 1475 + // Only validate if we have an NSID 1476 + let nsid_str = nsid_val?; 1477 + 1478 + // Parse JSON to Data 1479 + let parsed = match serde_json::from_str::<Data>(&text) { 1480 + Ok(val) => val.into_static(), 1481 + Err(e) => { 1482 + return Some((None, Some(e.to_string()))); 1483 + } 1484 + }; 1485 + 1486 + // Use global validator (schema already registered) 1487 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 1488 + let result = validator.validate_by_nsid(&nsid_str, &parsed); 1489 + 1490 + Some((Some(result), None)) 1491 + } 1492 + }); 1493 + 1494 + rsx! { 1495 + div { class: "json-editor", 1496 + textarea { 1497 + class: "json-textarea", 1498 + style: "height: {height};", 1499 + value: "{json_text}", 1500 + oninput: move |evt| { 1501 + json_text.set(evt.value()); 1502 + // Update data signal on successful parse 1503 + if let Ok(parsed) = serde_json::from_str::<Data>(&evt.value()) { 1504 + data.set(parsed.into_static()); 1505 + } 1506 + }, 1507 + } 1508 + 1509 + ValidationPanel { 1510 + validation: validation, 1511 + } 1512 + } 1513 + } 1514 + } 1515 + 1516 + #[component] 1517 + pub fn ActionButtons( 1518 + on_update: EventHandler<()>, 1519 + on_save_new: EventHandler<()>, 1520 + on_replace: EventHandler<()>, 1521 + on_delete: EventHandler<()>, 1522 + on_cancel: EventHandler<()>, 1523 + ) -> Element { 1524 + let mut show_save_dropdown = use_signal(|| false); 1525 + let mut show_replace_warning = use_signal(|| false); 1526 + let mut show_delete_confirm = use_signal(|| false); 1527 + 1528 + rsx! { 1529 + div { class: "action-buttons-group", 1530 + button { 1531 + class: "tab-button action-button", 1532 + onclick: move |_| on_update.call(()), 1533 + "Update" 1534 + } 1535 + 1536 + div { class: "dropdown-wrapper", 1537 + button { 1538 + class: "tab-button action-button", 1539 + onclick: move |_| show_save_dropdown.toggle(), 1540 + "Save as New ▼" 1541 + } 1542 + if show_save_dropdown() { 1543 + div { class: "dropdown-menu", 1544 + button { 1545 + onclick: move |_| { 1546 + show_save_dropdown.set(false); 1547 + on_save_new.call(()); 1548 + }, 1549 + "Save as New" 1550 + } 1551 + button { 1552 + onclick: move |_| { 1553 + show_save_dropdown.set(false); 1554 + show_replace_warning.set(true); 1555 + }, 1556 + "Replace" 1557 + } 1558 + } 1559 + } 1560 + } 1561 + 1562 + if show_replace_warning() { 1563 + div { class: "inline-warning", 1564 + "⚠️ This will delete the current record and create a new one with a different rkey. " 1565 + button { 1566 + onclick: move |_| { 1567 + show_replace_warning.set(false); 1568 + on_replace.call(()); 1569 + }, 1570 + "Yes" 1571 + } 1572 + button { 1573 + onclick: move |_| show_replace_warning.set(false), 1574 + "No" 1575 + } 1576 + } 1577 + } 1578 + 1579 + button { 1580 + class: "tab-button action-button action-button-danger", 1581 + onclick: move |_| show_delete_confirm.set(true), 1582 + "Delete" 1583 + } 1584 + 1585 + DialogRoot { 1586 + open: Some(show_delete_confirm()), 1587 + on_open_change: move |open: bool| { 1588 + show_delete_confirm.set(open); 1589 + }, 1590 + DialogContent { 1591 + DialogTitle { "Delete Record?" } 1592 + DialogDescription { 1593 + "This action cannot be undone." 1594 + } 1595 + div { class: "dialog-actions", 1596 + button { 1597 + onclick: move |_| { 1598 + show_delete_confirm.set(false); 1599 + on_delete.call(()); 1600 + }, 1601 + "Delete" 1602 + } 1603 + button { 1604 + onclick: move |_| show_delete_confirm.set(false), 1605 + "Cancel" 1606 + } 1607 + } 1608 + } 1609 + } 1610 + 1611 + button { 1612 + class: "tab-button action-button", 1613 + onclick: move |_| on_cancel.call(()), 1614 + "Cancel" 1615 + } 1616 + } 1617 + } 1618 + } 1619 + 1620 + #[component] 1621 + pub fn ValidationPanel( 1622 + validation: Resource<Option<(Option<ValidationResult>, Option<String>)>>, 1623 + ) -> Element { 1624 + rsx! { 1625 + div { class: "validation-panel", 1626 + if let Some(Some((result_opt, parse_error_opt))) = validation.read().as_ref() { 1627 + if let Some(parse_err) = parse_error_opt { 1628 + div { class: "parse-error", 1629 + "❌ Invalid JSON: {parse_err}" 1630 + } 1631 + } 1632 + 1633 + if let Some(result) = result_opt { 1634 + // Structural validity 1635 + if result.is_structurally_valid() { 1636 + div { class: "validation-success", "✓ Structurally valid" } 1637 + } else { 1638 + div { class: "parse-error", "❌ Structurally invalid" } 1639 + } 1640 + 1641 + // Overall validity 1642 + if result.is_valid() { 1643 + div { class: "validation-success", "✓ Fully valid" } 1644 + } else { 1645 + div { class: "validation-warning", "⚠ Has errors" } 1646 + } 1647 + 1648 + // Show errors if any 1649 + if !result.is_valid() { 1650 + div { class: "validation-errors", 1651 + h4 { "Validation Errors:" } 1652 + for error in result.all_errors() { 1653 + div { class: "error", "{error}" } 1654 + } 1655 + } 1656 + } 1657 + } 1658 + } else { 1659 + div { "Validating..." } 1660 + } 1661 + } 1662 + } 1663 + }
+610
crates/weaver-app/src/components/record_view.rs
···
··· 1 + use crate::components::accordion::{Accordion, AccordionContent, AccordionItem, AccordionTrigger}; 2 + use crate::record_utils::{get_errors_at_exact_path, get_expected_string_format, get_hex_rep}; 3 + use dioxus::prelude::*; 4 + use humansize::format_size; 5 + use jacquard::to_data; 6 + use jacquard::types::string::AtprotoStr; 7 + use jacquard::{ 8 + common::{Data, IntoStatic}, 9 + types::{aturi::AtUri, cid::Cid}, 10 + }; 11 + use jacquard_lexicon::lexicon::LexiconDoc; 12 + use jacquard_lexicon::validation::ValidationResult; 13 + use weaver_renderer::{code_pretty::highlight_code, css::generate_default_css}; 14 + 15 + #[derive(Clone, Copy, PartialEq)] 16 + pub enum ViewMode { 17 + Pretty, 18 + Json, 19 + Schema, 20 + } 21 + 22 + /// Layout component for record view - handles header, metadata, and wraps children 23 + #[component] 24 + pub fn RecordViewLayout( 25 + uri: AtUri<'static>, 26 + cid: Option<Cid<'static>>, 27 + schema: ReadSignal<Option<LexiconDoc<'static>>>, 28 + record_value: Data<'static>, 29 + children: Element, 30 + ) -> Element { 31 + // Validate the record if schema is available 32 + let validation_status = use_memo(move || { 33 + let _schema_doc = schema()?; 34 + let nsid_str = record_value.type_discriminator()?; 35 + 36 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 37 + let result = validator.validate_by_nsid(nsid_str, &record_value); 38 + 39 + Some(result.is_valid()) 40 + }); 41 + 42 + rsx! { 43 + div { 44 + class: "record-metadata", 45 + div { class: "metadata-row", 46 + span { class: "metadata-label", "URI" } 47 + span { class: "metadata-value", 48 + HighlightedUri { uri: uri.clone() } 49 + } 50 + } 51 + if let Some(cid) = cid { 52 + div { class: "metadata-row", 53 + span { class: "metadata-label", "CID" } 54 + code { class: "metadata-value", "{cid}" } 55 + } 56 + } 57 + if let Some(is_valid) = validation_status() { 58 + div { class: "metadata-row", 59 + span { class: "metadata-label", "Schema" } 60 + span { 61 + class: if is_valid { "metadata-value schema-valid" } else { "metadata-value schema-invalid" }, 62 + if is_valid { "Valid" } else { "Invalid" } 63 + } 64 + } 65 + } 66 + } 67 + 68 + {children} 69 + 70 + } 71 + } 72 + 73 + #[component] 74 + pub fn SchemaView(schema: ReadSignal<Option<LexiconDoc<'static>>>) -> Element { 75 + if let Some(schema_doc) = schema() { 76 + // Convert LexiconDoc to Data for display 77 + let schema_data = to_data(&schema_doc).ok().map(|d| d.into_static()); 78 + 79 + if let Some(data) = schema_data { 80 + rsx! { 81 + div { 82 + class: "pretty-record", 83 + DataView { data: data.clone(), root_data: data, path: String::new(), did: String::new() } 84 + } 85 + } 86 + } else { 87 + rsx! { 88 + div { class: "schema-error", "Failed to convert schema to displayable format" } 89 + } 90 + } 91 + } else { 92 + rsx! { 93 + div { class: "schema-loading", "Loading schema..." } 94 + } 95 + } 96 + } 97 + 98 + #[component] 99 + pub fn DataView( 100 + data: Data<'static>, 101 + root_data: ReadSignal<Data<'static>>, 102 + path: String, 103 + did: String, 104 + ) -> Element { 105 + // Try to get validation result from context and get errors exactly at this path 106 + let validation_result = try_use_context::<Signal<Option<ValidationResult>>>(); 107 + 108 + let errors = if let Some(vr_signal) = validation_result { 109 + get_errors_at_exact_path(&*vr_signal.read(), &path) 110 + } else { 111 + Vec::new() 112 + }; 113 + 114 + let has_errors = !errors.is_empty(); 115 + 116 + match &data { 117 + Data::Null => rsx! { 118 + div { class: if has_errors { "record-field field-error" } else { "record-field" }, 119 + PathLabel { path: path.clone() } 120 + span { class: "field-value muted", "null" } 121 + if has_errors { 122 + for error in &errors { 123 + div { class: "field-error-message", "{error}" } 124 + } 125 + } 126 + } 127 + }, 128 + Data::Boolean(b) => rsx! { 129 + div { class: if has_errors { "record-field field-error" } else { "record-field" }, 130 + PathLabel { path: path.clone() } 131 + span { class: "field-value", "{b}" } 132 + if has_errors { 133 + for error in &errors { 134 + div { class: "field-error-message", "{error}" } 135 + } 136 + } 137 + } 138 + }, 139 + Data::Integer(i) => rsx! { 140 + div { class: if has_errors { "record-field field-error" } else { "record-field" }, 141 + PathLabel { path: path.clone() } 142 + span { class: "field-value", "{i}" } 143 + if has_errors { 144 + for error in &errors { 145 + div { class: "field-error-message", "{error}" } 146 + } 147 + } 148 + } 149 + }, 150 + Data::String(s) => { 151 + use jacquard::types::string::AtprotoStr; 152 + use jacquard_lexicon::lexicon::LexStringFormat; 153 + 154 + // Get expected format from schema 155 + let expected_format = get_expected_string_format(&*root_data.read(), &path); 156 + 157 + // Get actual type from data 158 + let actual_type_label = match s { 159 + AtprotoStr::Datetime(_) => "datetime", 160 + AtprotoStr::Language(_) => "language", 161 + AtprotoStr::Tid(_) => "tid", 162 + AtprotoStr::Nsid(_) => "nsid", 163 + AtprotoStr::Did(_) => "did", 164 + AtprotoStr::Handle(_) => "handle", 165 + AtprotoStr::AtIdentifier(_) => "at-identifier", 166 + AtprotoStr::AtUri(_) => "at-uri", 167 + AtprotoStr::Uri(_) => "uri", 168 + AtprotoStr::Cid(_) => "cid", 169 + AtprotoStr::RecordKey(_) => "record-key", 170 + AtprotoStr::String(_) => "string", 171 + }; 172 + 173 + // Prefer schema format if available, otherwise use actual type 174 + let type_label = if let Some(fmt) = expected_format { 175 + match fmt { 176 + LexStringFormat::Datetime => "datetime", 177 + LexStringFormat::Uri => "uri", 178 + LexStringFormat::AtUri => "at-uri", 179 + LexStringFormat::Did => "did", 180 + LexStringFormat::Handle => "handle", 181 + LexStringFormat::AtIdentifier => "at-identifier", 182 + LexStringFormat::Nsid => "nsid", 183 + LexStringFormat::Cid => "cid", 184 + LexStringFormat::Language => "language", 185 + LexStringFormat::Tid => "tid", 186 + LexStringFormat::RecordKey => "record-key", 187 + } 188 + } else { 189 + actual_type_label 190 + }; 191 + 192 + rsx! { 193 + div { class: if has_errors { "record-field field-error" } else { "record-field" }, 194 + PathLabel { path: path.clone() } 195 + span { class: "field-value", 196 + 197 + HighlightedString { string_type: s.clone() } 198 + if type_label != "string" { 199 + span { class: "string-type-tag", " [{type_label}]" } 200 + } 201 + } 202 + if has_errors { 203 + for error in &errors { 204 + div { class: "field-error-message", "{error}" } 205 + } 206 + } 207 + } 208 + } 209 + } 210 + Data::Bytes(b) => { 211 + let hex_string = get_hex_rep(&mut b.to_vec()); 212 + let byte_size = if b.len() > 128 { 213 + format_size(b.len(), humansize::BINARY) 214 + } else { 215 + format!("{} bytes", b.len()) 216 + }; 217 + rsx! { 218 + div { class: if has_errors { "record-field field-error" } else { "record-field" }, 219 + PathLabel { path: path.clone() } 220 + pre { class: "field-value bytes", "{hex_string} [{byte_size}]" } 221 + if has_errors { 222 + for error in &errors { 223 + div { class: "field-error-message", "{error}" } 224 + } 225 + } 226 + } 227 + } 228 + } 229 + Data::CidLink(cid) => rsx! { 230 + div { class: if has_errors { "record-field field-error" } else { "record-field" }, 231 + span { class: "field-label", "{path}" } 232 + span { class: "field-value", "{cid}" } 233 + if has_errors { 234 + for error in &errors { 235 + div { class: "field-error-message", "{error}" } 236 + } 237 + } 238 + } 239 + }, 240 + Data::Array(arr) => { 241 + let label = path.split('.').last().unwrap_or(&path); 242 + rsx! { 243 + div { class: "record-section", 244 + Accordion { 245 + id: "array-{path}", 246 + collapsible: true, 247 + AccordionItem { 248 + default_open: true, 249 + index: 0, 250 + AccordionTrigger { 251 + div { class: "section-label", "{label}" span { class: "array-len", "[{arr.len()}]" } } 252 + } 253 + AccordionContent { 254 + if has_errors { 255 + for error in &errors { 256 + div { class: "field-error-message", "{error}" } 257 + } 258 + } 259 + div { class: "section-content", 260 + for (idx, item) in arr.iter().enumerate() { 261 + { 262 + let item_path = format!("{}[{}]", label, idx); 263 + let is_object = matches!(item, Data::Object(_)); 264 + 265 + if is_object { 266 + rsx! { 267 + div { 268 + class: "array-item", 269 + div { class: "record-section", 270 + div { class: "section-label", "{item_path}" } 271 + div { class: "section-content", 272 + DataView { 273 + data: item.clone(), 274 + root_data, 275 + path: item_path.clone(), 276 + did: did.clone() 277 + } 278 + } 279 + } 280 + } 281 + } 282 + } else { 283 + rsx! { 284 + div { 285 + class: "array-item", 286 + DataView { 287 + data: item.clone(), 288 + root_data, 289 + path: item_path, 290 + did: did.clone() 291 + } 292 + } 293 + } 294 + } 295 + } 296 + } 297 + } 298 + } 299 + } 300 + } 301 + } 302 + } 303 + } 304 + Data::Object(obj) => { 305 + let is_root = path.is_empty(); 306 + let is_array_item = path.split('.').last().unwrap_or(&path).contains('['); 307 + 308 + if is_root || is_array_item { 309 + // Root object or array item: just render children (array items already wrapped) 310 + rsx! { 311 + div { class: if !is_root { "record-section" } else {""}, 312 + if has_errors { 313 + for error in &errors { 314 + div { class: "field-error-message", "{error}" } 315 + } 316 + } 317 + for (key, value) in obj.iter() { 318 + { 319 + let new_path = if is_root { 320 + key.to_string() 321 + } else { 322 + format!("{}.{}", path, key) 323 + }; 324 + let did_clone = did.clone(); 325 + rsx! { 326 + DataView { data: value.clone(), root_data, path: new_path, did: did_clone } 327 + } 328 + } 329 + } 330 + } 331 + } 332 + } else { 333 + // Nested object (not array item): wrap in section 334 + let label = path.split('.').last().unwrap_or(&path); 335 + rsx! { 336 + div { class: "record-section", 337 + Accordion { 338 + id: "object-{path}", 339 + collapsible: true, 340 + AccordionItem { 341 + default_open: true, 342 + index: 0, 343 + AccordionTrigger { 344 + div { class: "section-label", "{label}" } 345 + } 346 + AccordionContent { 347 + if has_errors { 348 + for error in &errors { 349 + div { class: "field-error-message", "{error}" } 350 + } 351 + } 352 + div { class: "section-content", 353 + for (key, value) in obj.iter() { 354 + { 355 + let new_path = format!("{}.{}", path, key); 356 + let did_clone = did.clone(); 357 + rsx! { 358 + DataView { data: value.clone(), root_data, path: new_path, did: did_clone } 359 + } 360 + } 361 + } 362 + } 363 + } 364 + } 365 + } 366 + } 367 + } 368 + } 369 + } 370 + Data::Blob(blob) => { 371 + let is_image = blob.mime_type.starts_with("image/"); 372 + let format = blob.mime_type.strip_prefix("image/").unwrap_or("jpeg"); 373 + let image_url = format!( 374 + "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@{}", 375 + did, 376 + blob.cid(), 377 + format 378 + ); 379 + 380 + let blob_size = format_size(blob.size, humansize::BINARY); 381 + rsx! { 382 + div { class: "record-field", 383 + span { class: "field-label", "{path}" } 384 + span { class: "field-value mime", "[mimeType: {blob.mime_type}, size: {blob_size}]" } 385 + if is_image { 386 + img { 387 + src: "{image_url}", 388 + alt: "Blob image", 389 + class: "blob-image", 390 + } 391 + } 392 + } 393 + } 394 + } 395 + } 396 + } 397 + 398 + #[component] 399 + pub fn HighlightedUri(uri: AtUri<'static>) -> Element { 400 + let s = uri.as_str(); 401 + let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, s); 402 + 403 + if let Some(rest) = s.strip_prefix("at://") { 404 + let parts: Vec<&str> = rest.splitn(3, '/').collect(); 405 + return rsx! { 406 + a { 407 + href: link, 408 + class: "uri-link", 409 + span { class: "string-at-uri", 410 + span { class: "aturi-scheme", "at://" } 411 + span { class: "aturi-authority", "{uri.authority()}" } 412 + 413 + if parts.len() > 1 { 414 + span { class: "aturi-slash", "/" } 415 + if let Some(collection) = uri.collection() { 416 + span { class: "aturi-collection", "{collection.as_ref()}" } 417 + } 418 + } 419 + if parts.len() > 2 { 420 + span { class: "aturi-slash", "/" } 421 + if let Some(rkey) = uri.rkey() { 422 + span { class: "aturi-rkey", "{rkey.as_ref()}" } 423 + } 424 + } 425 + } 426 + } 427 + }; 428 + } 429 + 430 + rsx! { a { class: "string-at-uri", href: s } } 431 + } 432 + 433 + #[component] 434 + pub fn HighlightedString(string_type: AtprotoStr<'static>) -> Element { 435 + use jacquard::types::string::AtprotoStr; 436 + 437 + match &string_type { 438 + AtprotoStr::Nsid(nsid) => { 439 + let parts: Vec<&str> = nsid.as_str().split('.').collect(); 440 + rsx! { 441 + span { class: "string-nsid", 442 + for (i, part) in parts.iter().enumerate() { 443 + span { class: "nsid-segment nsid-segment-{i % 3}", "{part}" } 444 + if i < parts.len() - 1 { 445 + span { class: "nsid-dot", "." } 446 + } 447 + } 448 + } 449 + } 450 + } 451 + AtprotoStr::Did(did) => { 452 + let s = did.as_str(); 453 + if let Some(rest) = s.strip_prefix("did:") { 454 + if let Some((method, identifier)) = rest.split_once(':') { 455 + return rsx! { 456 + span { class: "string-did", 457 + span { class: "did-scheme", "did:" } 458 + span { class: "did-method", "{method}" } 459 + span { class: "did-separator", ":" } 460 + span { class: "did-identifier", "{identifier}" } 461 + } 462 + }; 463 + } 464 + } 465 + rsx! { span { class: "string-did", "{s}" } } 466 + } 467 + AtprotoStr::Handle(handle) => { 468 + let parts: Vec<&str> = handle.as_str().split('.').collect(); 469 + rsx! { 470 + span { class: "string-handle", 471 + for (i, part) in parts.iter().enumerate() { 472 + span { class: "handle-segment handle-segment-{i % 2}", "{part}" } 473 + if i < parts.len() - 1 { 474 + span { class: "handle-dot", "." } 475 + } 476 + } 477 + } 478 + } 479 + } 480 + AtprotoStr::AtUri(uri) => { 481 + rsx! { 482 + HighlightedUri { uri: uri.clone().into_static() } 483 + } 484 + } 485 + AtprotoStr::Uri(uri) => { 486 + let s = uri.as_str(); 487 + if let Ok(at_uri) = AtUri::new(s) { 488 + return rsx! { 489 + HighlightedUri { uri: at_uri.into_static() } 490 + }; 491 + } 492 + 493 + // Try to parse scheme 494 + if let Some((scheme, rest)) = s.split_once("://") { 495 + // Split authority and path 496 + let (authority, path) = if let Some(idx) = rest.find('/') { 497 + (&rest[..idx], &rest[idx..]) 498 + } else { 499 + (rest, "") 500 + }; 501 + 502 + return rsx! { 503 + a { 504 + href: "{s}", 505 + target: "_blank", 506 + rel: "noopener noreferrer", 507 + class: "uri-link", 508 + span { class: "string-uri", 509 + span { class: "uri-scheme", "{scheme}" } 510 + span { class: "uri-separator", "://" } 511 + span { class: "uri-authority", "{authority}" } 512 + if !path.is_empty() { 513 + span { class: "uri-path", "{path}" } 514 + } 515 + } 516 + } 517 + }; 518 + } 519 + 520 + rsx! { span { class: "string-uri", "{s}" } } 521 + } 522 + _ => { 523 + let value = string_type.as_str(); 524 + rsx! { "{value}" } 525 + } 526 + } 527 + } 528 + 529 + #[derive(Props, Clone, PartialEq)] 530 + pub struct CodeViewProps { 531 + #[props(default)] 532 + id: Signal<String>, 533 + #[props(default)] 534 + class: Signal<String>, 535 + code: ReadSignal<String>, 536 + lang: Option<String>, 537 + } 538 + 539 + #[component] 540 + pub fn PrettyRecordView( 541 + record: Data<'static>, 542 + uri: AtUri<'static>, 543 + schema: ReadSignal<Option<LexiconDoc<'static>>>, 544 + ) -> Element { 545 + let did = uri.authority().to_string(); 546 + let root_data = use_signal(|| record.clone()); 547 + 548 + // Validate the record and provide via context - only after schema is loaded 549 + let mut validation_result = use_signal(|| None); 550 + use_effect(move || { 551 + // Wait for schema to be loaded 552 + if schema().is_some() { 553 + if let Some(nsid_str) = root_data.read().type_discriminator() { 554 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 555 + let result = validator.validate_by_nsid(nsid_str, &*root_data.read()); 556 + validation_result.set(Some(result)); 557 + } 558 + } 559 + }); 560 + use_context_provider(|| validation_result); 561 + 562 + rsx! { 563 + div { 564 + class: "pretty-record", 565 + DataView { data: record, root_data, path: String::new(), did } 566 + } 567 + } 568 + } 569 + 570 + #[component] 571 + pub fn CodeView(props: CodeViewProps) -> Element { 572 + let code = &*props.code.read(); 573 + 574 + let mut html_buf = String::new(); 575 + highlight_code(props.lang.as_deref(), code, &mut html_buf).unwrap(); 576 + 577 + rsx! { 578 + document::Style { {generate_default_css().unwrap()}} 579 + div { 580 + id: "{&*props.id.read()}", 581 + class: "{&*props.class.read()}", 582 + dangerous_inner_html: "{html_buf}" 583 + } 584 + } 585 + } 586 + 587 + #[component] 588 + pub fn PathLabel(path: String) -> Element { 589 + if path.is_empty() { 590 + return rsx! {}; 591 + } 592 + 593 + // Find the last separator 594 + let last_sep = path.rfind(|c| c == '.'); 595 + 596 + if let Some(idx) = last_sep { 597 + let prefix = &path[..idx + 1]; 598 + let final_segment = &path[idx + 1..]; 599 + rsx! { 600 + span { class: "field-label", 601 + span { class: "path-prefix", "{prefix}" } 602 + span { class: "path-final", "{final_segment}" } 603 + } 604 + } 605 + } else { 606 + rsx! { 607 + span { class: "field-label","{path}" } 608 + } 609 + } 610 + }
+171 -146
crates/weaver-app/src/data.rs
··· 3 //! In fullstack-server mode, hooks use `use_server_future` with inline async closures. 4 //! In client-only mode, hooks use `use_resource` with context-provided fetchers. 5 6 #[cfg(feature = "server")] 7 use crate::blobcache::BlobCache; 8 use dioxus::prelude::*; 9 #[cfg(feature = "fullstack-server")] 10 #[allow(unused_imports)] 11 use dioxus::{CapturedError, fullstack::extract::Extension}; 12 - use jacquard::types::{did::Did, string::Handle}; 13 #[allow(unused_imports)] 14 use jacquard::{ 15 prelude::IdentityResolver, 16 smol_str::SmolStr, 17 types::{cid::Cid, string::AtIdentifier}, 18 }; 19 #[allow(unused_imports)] 20 use std::sync::Arc; 21 use weaver_api::sh_weaver::notebook::{BookEntryView, NotebookView, entry::Entry}; 22 // ============================================================================ 23 // Wrapper Hooks (feature-gated) 24 // ============================================================================ 25 26 /// Fetches entry data with SSR support in fullstack mode. 27 - /// Returns a MappedSignal over the server future resource. 28 #[cfg(feature = "fullstack-server")] 29 pub fn use_entry_data( 30 ident: AtIdentifier<'static>, 31 book_title: SmolStr, 32 title: SmolStr, 33 ) -> Result<Memo<Option<(BookEntryView<'static>, Entry<'static>)>>, RenderError> { 34 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 35 let fetcher = fetcher.clone(); 36 let ident = use_signal(|| ident); 37 let book_title = use_signal(|| book_title); ··· 88 }) 89 }) 90 } 91 - 92 /// Fetches entry data client-side only (no SSR). 93 #[cfg(not(feature = "fullstack-server"))] 94 pub fn use_entry_data( ··· 96 book_title: SmolStr, 97 title: SmolStr, 98 ) -> Result<Memo<Option<(BookEntryView<'static>, Entry<'static>)>>, RenderError> { 99 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 100 let fetcher = fetcher.clone(); 101 let ident = use_signal(|| ident); 102 let book_title = use_signal(|| book_title); 103 let entry_title = use_signal(|| title); 104 - let r = use_resource(move || { 105 let fetcher = fetcher.clone(); 106 async move { 107 fetcher ··· 109 .await 110 .ok() 111 .flatten() 112 - .map(|arc| (arc.0.clone(), arc.1.clone())) 113 } 114 }); 115 - Ok(use_memo(move || { 116 - if let Some(Some((ev, e))) = &*r.read_unchecked() { 117 - Some((ev.clone(), e.clone())) 118 - } else { 119 - None 120 - } 121 - })) 122 } 123 124 pub fn use_handle( 125 ident: AtIdentifier<'static>, 126 - ) -> Result<Memo<AtIdentifier<'static>>, RenderError> { 127 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 128 let fetcher = fetcher.clone(); 129 let ident = use_signal(|| ident); 130 - #[cfg(feature = "fullstack-server")] 131 - let h_str = { 132 - use_server_future(move || { 133 - let client = fetcher.get_client(); 134 - async move { 135 - use jacquard::smol_str::ToSmolStr; 136 - client 137 - .resolve_ident_owned(&ident()) 138 - .await 139 - .map(|doc| doc.handles().first().map(|h| h.to_smolstr())) 140 - .ok() 141 - .flatten() 142 - } 143 - }) 144 }; 145 - #[cfg(not(feature = "fullstack-server"))] 146 - let h_str = { 147 - use_resource(move || { 148 - let client = fetcher.get_client(); 149 - async move { 150 - use jacquard::smol_str::ToSmolStr; 151 - client 152 - .resolve_ident_owned(&ident()) 153 - .await 154 - .map(|doc| doc.handles().first().map(|h| h.to_smolstr())) 155 - .ok() 156 - .flatten() 157 - } 158 - }) 159 - }; 160 - Ok(h_str.map(|h_str| { 161 - use_memo(move || { 162 - if let Some(Some(e)) = &*h_str.read_unchecked() { 163 - use jacquard::IntoStatic; 164 - 165 - AtIdentifier::Handle(Handle::raw(&e).into_static()) 166 - } else { 167 - ident() 168 - } 169 - }) 170 - })?) 171 } 172 173 /// Hook to render markdown client-side only (no SSR). ··· 178 ) -> Result<Resource<Option<String>>, RenderError> { 179 let ident = use_signal(|| ident); 180 let content = use_signal(|| content); 181 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 182 Ok(use_server_future(move || { 183 let client = fetcher.get_client(); 184 async move { ··· 199 ) -> Result<Resource<Option<String>>, RenderError> { 200 let ident = use_signal(|| ident); 201 let content = use_signal(|| content); 202 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 203 Ok(use_resource(move || { 204 let client = fetcher.get_client(); 205 async move { ··· 221 }; 222 223 let ctx = ClientContext::<()>::new(content.clone(), did); 224 - let parser = markdown_weaver::Parser::new(&content.content); 225 let iter = ContextIterator::default(parser); 226 let processor = NotebookProcessor::new(ctx, iter); 227 ··· 236 #[cfg(feature = "fullstack-server")] 237 pub fn use_profile_data( 238 ident: AtIdentifier<'static>, 239 - ) -> Result<Memo<Option<weaver_api::sh_weaver::actor::ProfileDataView<'static>>>, RenderError> { 240 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 241 let ident = use_signal(|| ident); 242 let res = use_server_future(move || { 243 let fetcher = fetcher.clone(); ··· 252 })?; 253 Ok(use_memo(move || { 254 if let Some(Some(value)) = &*res.read_unchecked() { 255 - jacquard::from_json_value::<weaver_api::sh_weaver::actor::ProfileDataView>( 256 - value.clone(), 257 - ) 258 - .ok() 259 } else { 260 None 261 } ··· 266 #[cfg(not(feature = "fullstack-server"))] 267 pub fn use_profile_data( 268 ident: AtIdentifier<'static>, 269 - ) -> Result<Memo<Option<weaver_api::sh_weaver::actor::ProfileDataView<'static>>>, RenderError> { 270 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 271 - let r = use_resource(use_reactive!(|ident| { 272 let fetcher = fetcher.clone(); 273 async move { 274 fetcher 275 - .fetch_profile(&ident) 276 .await 277 .ok() 278 - .map(|arc| (*arc).clone()) 279 } 280 - })); 281 Ok(use_memo(move || { 282 - r.read_unchecked().as_ref().and_then(|v| v.clone()) 283 })) 284 } 285 ··· 287 #[cfg(feature = "fullstack-server")] 288 pub fn use_notebooks_for_did( 289 ident: AtIdentifier<'static>, 290 - ) -> Result< 291 - Memo< 292 - Option< 293 - Vec<( 294 - weaver_api::sh_weaver::notebook::NotebookView<'static>, 295 - Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>, 296 - )>, 297 - >, 298 - >, 299 - RenderError, 300 - > { 301 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 302 let ident = use_signal(|| ident); 303 let res = use_server_future(move || { 304 let fetcher = fetcher.clone(); ··· 321 values 322 .iter() 323 .map(|v| { 324 - jacquard::from_json_value::<( 325 - weaver_api::sh_weaver::notebook::NotebookView, 326 - Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef>, 327 - )>(v.clone()) 328 - .ok() 329 }) 330 .collect::<Option<Vec<_>>>() 331 } else { ··· 338 #[cfg(not(feature = "fullstack-server"))] 339 pub fn use_notebooks_for_did( 340 ident: AtIdentifier<'static>, 341 - ) -> Result< 342 - Memo< 343 - Option< 344 - Vec<( 345 - weaver_api::sh_weaver::notebook::NotebookView<'static>, 346 - Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>, 347 - )>, 348 - >, 349 - >, 350 - RenderError, 351 - > { 352 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 353 - let r = use_resource(use_reactive!(|ident| { 354 let fetcher = fetcher.clone(); 355 async move { 356 fetcher 357 - .fetch_notebooks_for_did(&ident) 358 .await 359 .ok() 360 - .map(|notebooks| notebooks.iter().map(|arc| (*arc).clone()).collect()) 361 } 362 - })); 363 Ok(use_memo(move || { 364 - r.read_unchecked().as_ref().and_then(|v| v.clone()) 365 })) 366 } 367 368 /// Fetches notebooks from UFOS with SSR support in fullstack mode 369 #[cfg(feature = "fullstack-server")] 370 - pub fn use_notebooks_from_ufos() -> Result< 371 - Memo<Option<Vec<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>>, 372 - RenderError, 373 - > { 374 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 375 let res = use_server_future(move || { 376 let fetcher = fetcher.clone(); 377 async move { ··· 393 values 394 .iter() 395 .map(|v| { 396 - jacquard::from_json_value::<( 397 - NotebookView, 398 - Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef>, 399 - )>(v.clone()) 400 - .ok() 401 }) 402 .collect::<Option<Vec<_>>>() 403 } else { ··· 408 409 /// Fetches notebooks from UFOS client-side only (no SSR) 410 #[cfg(not(feature = "fullstack-server"))] 411 - pub fn use_notebooks_from_ufos() -> Result< 412 - Memo<Option<Vec<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>>, 413 - RenderError, 414 - > { 415 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 416 - let r = use_resource(move || { 417 let fetcher = fetcher.clone(); 418 async move { 419 fetcher 420 .fetch_notebooks_from_ufos() 421 .await 422 .ok() 423 - .map(|notebooks| notebooks.iter().map(|arc| (*arc).clone()).collect()) 424 } 425 }); 426 Ok(use_memo(move || { 427 - r.read_unchecked().as_ref().and_then(|v| v.clone()) 428 })) 429 } 430 ··· 433 pub fn use_notebook( 434 ident: AtIdentifier<'static>, 435 book_title: SmolStr, 436 - ) -> Result< 437 - Memo<Option<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>, 438 - RenderError, 439 - > { 440 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 441 let ident = use_signal(|| ident); 442 let book_title = use_signal(|| book_title); 443 let res = use_server_future(move || { ··· 454 })?; 455 Ok(use_memo(move || { 456 if let Some(Some(value)) = &*res.read_unchecked() { 457 - jacquard::from_json_value::<( 458 - NotebookView, 459 - Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef>, 460 - )>(value.clone()) 461 - .ok() 462 } else { 463 None 464 } ··· 470 pub fn use_notebook( 471 ident: AtIdentifier<'static>, 472 book_title: SmolStr, 473 - ) -> Result< 474 - Memo<Option<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>, 475 - RenderError, 476 - > { 477 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 478 - let r = use_resource(use_reactive!(|(ident, book_title)| { 479 let fetcher = fetcher.clone(); 480 async move { 481 fetcher 482 - .get_notebook(ident, book_title) 483 .await 484 .ok() 485 .flatten() 486 - .map(|arc| (*arc).clone()) 487 } 488 - })); 489 Ok(use_memo(move || { 490 - r.read_unchecked().as_ref().and_then(|v| v.clone()) 491 })) 492 } 493 ··· 497 ident: AtIdentifier<'static>, 498 book_title: SmolStr, 499 ) -> Result<Memo<Option<Vec<BookEntryView<'static>>>>, RenderError> { 500 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 501 let ident = use_signal(|| ident); 502 let book_title = use_signal(|| book_title); 503 let res = use_server_future(move || { ··· 535 ident: AtIdentifier<'static>, 536 book_title: SmolStr, 537 ) -> Result<Memo<Option<Vec<BookEntryView<'static>>>>, RenderError> { 538 - let fetcher = use_context::<crate::fetch::CachedFetcher>(); 539 let r = use_resource(use_reactive!(|(ident, book_title)| { 540 let fetcher = fetcher.clone(); 541 async move {
··· 3 //! In fullstack-server mode, hooks use `use_server_future` with inline async closures. 4 //! In client-only mode, hooks use `use_resource` with context-provided fetchers. 5 6 + use crate::auth::AuthState; 7 #[cfg(feature = "server")] 8 use crate::blobcache::BlobCache; 9 use dioxus::prelude::*; 10 #[cfg(feature = "fullstack-server")] 11 #[allow(unused_imports)] 12 use dioxus::{CapturedError, fullstack::extract::Extension}; 13 + use jacquard::{ 14 + IntoStatic, 15 + identity::resolver::IdentityError, 16 + types::{did::Did, string::Handle}, 17 + }; 18 #[allow(unused_imports)] 19 use jacquard::{ 20 prelude::IdentityResolver, 21 smol_str::SmolStr, 22 types::{cid::Cid, string::AtIdentifier}, 23 }; 24 + use std::cell::Ref; 25 #[allow(unused_imports)] 26 use std::sync::Arc; 27 + use weaver_api::com_atproto::repo::strong_ref::StrongRef; 28 + use weaver_api::sh_weaver::actor::ProfileDataView; 29 use weaver_api::sh_weaver::notebook::{BookEntryView, NotebookView, entry::Entry}; 30 // ============================================================================ 31 // Wrapper Hooks (feature-gated) 32 // ============================================================================ 33 34 /// Fetches entry data with SSR support in fullstack mode. 35 #[cfg(feature = "fullstack-server")] 36 pub fn use_entry_data( 37 ident: AtIdentifier<'static>, 38 book_title: SmolStr, 39 title: SmolStr, 40 ) -> Result<Memo<Option<(BookEntryView<'static>, Entry<'static>)>>, RenderError> { 41 + let fetcher = use_context::<crate::fetch::Fetcher>(); 42 let fetcher = fetcher.clone(); 43 let ident = use_signal(|| ident); 44 let book_title = use_signal(|| book_title); ··· 95 }) 96 }) 97 } 98 /// Fetches entry data client-side only (no SSR). 99 #[cfg(not(feature = "fullstack-server"))] 100 pub fn use_entry_data( ··· 102 book_title: SmolStr, 103 title: SmolStr, 104 ) -> Result<Memo<Option<(BookEntryView<'static>, Entry<'static>)>>, RenderError> { 105 + let fetcher = use_context::<crate::fetch::Fetcher>(); 106 let fetcher = fetcher.clone(); 107 let ident = use_signal(|| ident); 108 let book_title = use_signal(|| book_title); 109 let entry_title = use_signal(|| title); 110 + let res = use_resource(move || { 111 let fetcher = fetcher.clone(); 112 async move { 113 fetcher ··· 115 .await 116 .ok() 117 .flatten() 118 + .map(|arc| { 119 + ( 120 + serde_json::to_value(entry.0.clone()).unwrap(), 121 + serde_json::to_value(entry.1.clone()).unwrap(), 122 + ) 123 + }) 124 } 125 }); 126 + res.map(|r| { 127 + use_memo(move || { 128 + if let Some(Some((ev, e))) = &*r.read_unchecked() { 129 + use jacquard::from_json_value; 130 + 131 + let book_entry = from_json_value::<BookEntryView>(ev.clone()).unwrap(); 132 + let entry = from_json_value::<Entry>(e.clone()).unwrap(); 133 + 134 + Some((book_entry, entry)) 135 + } else { 136 + None 137 + } 138 + }) 139 + }) 140 + } 141 + 142 + pub fn get_handle(did: Did<'static>) -> AtIdentifier<'static> { 143 + let ident = AtIdentifier::Did(did); 144 + use_handle(ident.clone()) 145 + .read() 146 + .as_ref() 147 + .unwrap_or(&Ok(ident)) 148 + .as_ref() 149 + .unwrap() 150 + .clone() 151 } 152 153 pub fn use_handle( 154 ident: AtIdentifier<'static>, 155 + ) -> Resource<Result<AtIdentifier<'static>, IdentityError>> { 156 + let fetcher = use_context::<crate::fetch::Fetcher>(); 157 let fetcher = fetcher.clone(); 158 let ident = use_signal(|| ident); 159 + 160 + use_resource(move || { 161 + let client = fetcher.get_client(); 162 + async move { 163 + client 164 + .resolve_ident_owned(&*ident.read()) 165 + .await 166 + .map(|doc| { 167 + doc.handles() 168 + .first() 169 + .map(|h| AtIdentifier::Handle(h.clone()).into_static()) 170 + }) 171 + .map(|h| h.ok_or(IdentityError::invalid_well_known()))? 172 + } 173 + }) 174 + } 175 + 176 + #[derive(Clone, PartialEq, Eq)] 177 + pub struct NotebookHandle(pub Arc<Option<AtIdentifier<'static>>>); 178 + 179 + impl NotebookHandle { 180 + pub fn as_ref(&self) -> Option<&AtIdentifier<'static>> { 181 + self.0.as_ref().as_ref() 182 + } 183 + } 184 + 185 + pub fn use_notebook_handle(ident: Signal<Option<AtIdentifier<'static>>>) -> NotebookHandle { 186 + let ident = if let Some(ident) = &*ident.read() { 187 + if let Some(Ok(handle)) = &*use_handle(ident.clone()).read() { 188 + Some(handle.clone()) 189 + } else { 190 + Some(ident.clone()) 191 + } 192 + } else { 193 + ident.read().clone() 194 }; 195 + use_context_provider(|| NotebookHandle(Arc::new(ident))) 196 } 197 198 /// Hook to render markdown client-side only (no SSR). ··· 203 ) -> Result<Resource<Option<String>>, RenderError> { 204 let ident = use_signal(|| ident); 205 let content = use_signal(|| content); 206 + let fetcher = use_context::<crate::fetch::Fetcher>(); 207 Ok(use_server_future(move || { 208 let client = fetcher.get_client(); 209 async move { ··· 224 ) -> Result<Resource<Option<String>>, RenderError> { 225 let ident = use_signal(|| ident); 226 let content = use_signal(|| content); 227 + let fetcher = use_context::<crate::fetch::Fetcher>(); 228 Ok(use_resource(move || { 229 let client = fetcher.get_client(); 230 async move { ··· 246 }; 247 248 let ctx = ClientContext::<()>::new(content.clone(), did); 249 + let parser = 250 + markdown_weaver::Parser::new_ext(&content.content, weaver_renderer::default_md_options()); 251 let iter = ContextIterator::default(parser); 252 let processor = NotebookProcessor::new(ctx, iter); 253 ··· 262 #[cfg(feature = "fullstack-server")] 263 pub fn use_profile_data( 264 ident: AtIdentifier<'static>, 265 + ) -> Result<Memo<Option<ProfileDataView<'static>>>, RenderError> { 266 + let fetcher = use_context::<crate::fetch::Fetcher>(); 267 let ident = use_signal(|| ident); 268 let res = use_server_future(move || { 269 let fetcher = fetcher.clone(); ··· 278 })?; 279 Ok(use_memo(move || { 280 if let Some(Some(value)) = &*res.read_unchecked() { 281 + jacquard::from_json_value::<ProfileDataView>(value.clone()).ok() 282 } else { 283 None 284 } ··· 289 #[cfg(not(feature = "fullstack-server"))] 290 pub fn use_profile_data( 291 ident: AtIdentifier<'static>, 292 + ) -> Result<Memo<Option<ProfileDataView<'static>>>, RenderError> { 293 + let fetcher = use_context::<crate::fetch::Fetcher>(); 294 + let ident = use_signal(|| ident); 295 + let res = use_resource(move || { 296 let fetcher = fetcher.clone(); 297 async move { 298 fetcher 299 + .fetch_profile(&ident()) 300 .await 301 .ok() 302 + .map(|arc| serde_json::to_value(&*arc).ok()) 303 + .flatten() 304 } 305 + }); 306 Ok(use_memo(move || { 307 + if let Some(Some(value)) = &*res.read_unchecked() { 308 + jacquard::from_json_value::<ProfileDataView>(value.clone()).ok() 309 + } else { 310 + None 311 + } 312 })) 313 } 314 ··· 316 #[cfg(feature = "fullstack-server")] 317 pub fn use_notebooks_for_did( 318 ident: AtIdentifier<'static>, 319 + ) -> Result<Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>, RenderError> { 320 + let fetcher = use_context::<crate::fetch::Fetcher>(); 321 let ident = use_signal(|| ident); 322 let res = use_server_future(move || { 323 let fetcher = fetcher.clone(); ··· 340 values 341 .iter() 342 .map(|v| { 343 + jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(v.clone()).ok() 344 }) 345 .collect::<Option<Vec<_>>>() 346 } else { ··· 353 #[cfg(not(feature = "fullstack-server"))] 354 pub fn use_notebooks_for_did( 355 ident: AtIdentifier<'static>, 356 + ) -> Result<Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>, RenderError> { 357 + let fetcher = use_context::<crate::fetch::Fetcher>(); 358 + let ident = use_signal(|| ident); 359 + let res = use_resource(move || { 360 let fetcher = fetcher.clone(); 361 async move { 362 fetcher 363 + .fetch_notebooks_for_did(&ident()) 364 .await 365 .ok() 366 + .map(|notebooks| { 367 + notebooks 368 + .iter() 369 + .map(|arc| serde_json::to_value(arc.as_ref()).ok()) 370 + .collect::<Option<Vec<_>>>() 371 + }) 372 + .flatten() 373 } 374 + }); 375 Ok(use_memo(move || { 376 + if let Some(Some(values)) = &*res.read_unchecked() { 377 + values 378 + .iter() 379 + .map(|v| { 380 + jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(v.clone()).ok() 381 + }) 382 + .collect::<Option<Vec<_>>>() 383 + } else { 384 + None 385 + } 386 })) 387 } 388 389 /// Fetches notebooks from UFOS with SSR support in fullstack mode 390 #[cfg(feature = "fullstack-server")] 391 + pub fn use_notebooks_from_ufos() 392 + -> Result<Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>, RenderError> { 393 + let fetcher = use_context::<crate::fetch::Fetcher>(); 394 let res = use_server_future(move || { 395 let fetcher = fetcher.clone(); 396 async move { ··· 412 values 413 .iter() 414 .map(|v| { 415 + jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(v.clone()).ok() 416 }) 417 .collect::<Option<Vec<_>>>() 418 } else { ··· 423 424 /// Fetches notebooks from UFOS client-side only (no SSR) 425 #[cfg(not(feature = "fullstack-server"))] 426 + pub fn use_notebooks_from_ufos() 427 + -> Result<Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>, RenderError> { 428 + let fetcher = use_context::<crate::fetch::Fetcher>(); 429 + let res = use_resource(move || { 430 let fetcher = fetcher.clone(); 431 async move { 432 fetcher 433 .fetch_notebooks_from_ufos() 434 .await 435 .ok() 436 + .map(|notebooks| { 437 + notebooks 438 + .iter() 439 + .map(|arc| serde_json::to_value(arc.as_ref()).ok()) 440 + .collect::<Option<Vec<_>>>() 441 + }) 442 + .flatten() 443 } 444 }); 445 Ok(use_memo(move || { 446 + if let Some(Some(values)) = &*res.read_unchecked() { 447 + values 448 + .iter() 449 + .map(|v| { 450 + jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(v.clone()).ok() 451 + }) 452 + .collect::<Option<Vec<_>>>() 453 + } else { 454 + None 455 + } 456 })) 457 } 458 ··· 461 pub fn use_notebook( 462 ident: AtIdentifier<'static>, 463 book_title: SmolStr, 464 + ) -> Result<Memo<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>>, RenderError> { 465 + let fetcher = use_context::<crate::fetch::Fetcher>(); 466 let ident = use_signal(|| ident); 467 let book_title = use_signal(|| book_title); 468 let res = use_server_future(move || { ··· 479 })?; 480 Ok(use_memo(move || { 481 if let Some(Some(value)) = &*res.read_unchecked() { 482 + jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(value.clone()).ok() 483 } else { 484 None 485 } ··· 491 pub fn use_notebook( 492 ident: AtIdentifier<'static>, 493 book_title: SmolStr, 494 + ) -> Result<Memo<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>>, RenderError> { 495 + let fetcher = use_context::<crate::fetch::Fetcher>(); 496 + let ident = use_signal(|| ident); 497 + let book_title = use_signal(|| book_title); 498 + let res = use_resource(move || { 499 let fetcher = fetcher.clone(); 500 async move { 501 fetcher 502 + .get_notebook(ident(), book_title()) 503 .await 504 .ok() 505 .flatten() 506 + .map(|arc| serde_json::to_value(arc.as_ref()).ok()) 507 + .flatten() 508 } 509 + }); 510 Ok(use_memo(move || { 511 + if let Some(Some(value)) = &*res.read_unchecked() { 512 + jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(value.clone()).ok() 513 + } else { 514 + None 515 + } 516 })) 517 } 518 ··· 522 ident: AtIdentifier<'static>, 523 book_title: SmolStr, 524 ) -> Result<Memo<Option<Vec<BookEntryView<'static>>>>, RenderError> { 525 + let fetcher = use_context::<crate::fetch::Fetcher>(); 526 let ident = use_signal(|| ident); 527 let book_title = use_signal(|| book_title); 528 let res = use_server_future(move || { ··· 560 ident: AtIdentifier<'static>, 561 book_title: SmolStr, 562 ) -> Result<Memo<Option<Vec<BookEntryView<'static>>>>, RenderError> { 563 + let fetcher = use_context::<crate::fetch::Fetcher>(); 564 let r = use_resource(use_reactive!(|(ident, book_title)| { 565 let fetcher = fetcher.clone(); 566 async move {
+3 -3
crates/weaver-app/src/env.rs
··· 1 // This file is automatically generated by build.rs 2 3 #[allow(unused)] 4 - pub const WEAVER_APP_ENV: &'static str = "dev"; 5 #[allow(unused)] 6 - pub const WEAVER_APP_HOST: &'static str = "http://localhost"; 7 #[allow(unused)] 8 - pub const WEAVER_APP_DOMAIN: &'static str = ""; 9 #[allow(unused)] 10 pub const WEAVER_PORT: &'static str = "8080"; 11 #[allow(unused)]
··· 1 // This file is automatically generated by build.rs 2 3 #[allow(unused)] 4 + pub const WEAVER_APP_ENV: &'static str = "prod"; 5 #[allow(unused)] 6 + pub const WEAVER_APP_HOST: &'static str = "https://alpha.weaver.sh"; 7 #[allow(unused)] 8 + pub const WEAVER_APP_DOMAIN: &'static str = "https://alpha.weaver.sh"; 9 #[allow(unused)] 10 pub const WEAVER_PORT: &'static str = "8080"; 11 #[allow(unused)]
+319 -67
crates/weaver-app/src/fetch.rs
··· 326 } 327 } 328 329 #[derive(Clone)] 330 - pub struct CachedFetcher { 331 pub client: Arc<Client>, 332 - book_cache: cache_impl::Cache< 333 - (AtIdentifier<'static>, SmolStr), 334 - Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 335 - >, 336 - entry_cache: cache_impl::Cache< 337 - (AtIdentifier<'static>, SmolStr), 338 - Arc<(BookEntryView<'static>, Entry<'static>)>, 339 - >, 340 - profile_cache: cache_impl::Cache<AtIdentifier<'static>, Arc<ProfileDataView<'static>>>, 341 } 342 343 - /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM 344 - unsafe impl Sync for CachedFetcher {} 345 - 346 - /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM 347 - unsafe impl Send for CachedFetcher {} 348 - 349 - impl CachedFetcher { 350 pub fn new(client: OAuthClient<JacquardResolver, AuthStore>) -> Self { 351 Self { 352 client: Arc::new(Client::new(client)), 353 - book_cache: cache_impl::new_cache(100, Duration::from_secs(1200)), 354 - entry_cache: cache_impl::new_cache(100, Duration::from_secs(600)), 355 - profile_cache: cache_impl::new_cache(100, Duration::from_secs(1800)), 356 } 357 } 358 ··· 390 ident: AtIdentifier<'static>, 391 title: SmolStr, 392 ) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 393 - if let Some(entry) = cache_impl::get(&self.book_cache, &(ident.clone(), title.clone())) { 394 - Ok(Some(entry)) 395 } else { 396 - let client = self.get_client(); 397 - if let Some((notebook, entries)) = client 398 - .notebook_by_title(&ident, &title) 399 - .await 400 - .map_err(|e| dioxus::CapturedError::from_display(e))? 401 - { 402 - let stored = Arc::new((notebook, entries)); 403 - cache_impl::insert(&self.book_cache, (ident, title), stored.clone()); 404 - Ok(Some(stored)) 405 - } else { 406 - Ok(None) 407 - } 408 } 409 } 410 ··· 416 ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> { 417 if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 418 let (notebook, entries) = result.as_ref(); 419 - if let Some(entry) = 420 - cache_impl::get(&self.entry_cache, &(ident.clone(), entry_title.clone())) 421 { 422 - Ok(Some(entry)) 423 } else { 424 - let client = self.get_client(); 425 - if let Some(entry) = client 426 - .entry_by_title(notebook, entries.as_ref(), &entry_title) 427 - .await 428 - .map_err(|e| dioxus::CapturedError::from_display(e))? 429 - { 430 - let stored = Arc::new(entry); 431 - cache_impl::insert(&self.entry_cache, (ident, entry_title), stored.clone()); 432 - Ok(Some(stored)) 433 - } else { 434 - Ok(None) 435 - } 436 } 437 } else { 438 Ok(None) ··· 477 .unwrap_or_else(|| SmolStr::new("Untitled")); 478 479 let result = Arc::new((notebook, entries)); 480 - // Cache it 481 - cache_impl::insert(&self.book_cache, (ident, title), result.clone()); 482 notebooks.push(result); 483 } 484 Err(_) => continue, // Skip notebooks that fail to load ··· 546 .unwrap_or_else(|| SmolStr::new("Untitled")); 547 548 let result = Arc::new((notebook, entries)); 549 - // Cache it 550 - cache_impl::insert(&self.book_cache, (ident, title), result.clone()); 551 notebooks.push(result); 552 } 553 Err(_) => continue, // Skip notebooks that fail to load ··· 585 &self, 586 ident: &AtIdentifier<'_>, 587 ) -> Result<Arc<ProfileDataView<'static>>> { 588 - use jacquard::IntoStatic; 589 - 590 - let ident_static = ident.clone().into_static(); 591 - 592 - if let Some(cached) = cache_impl::get(&self.profile_cache, &ident_static) { 593 - return Ok(cached); 594 - } 595 - 596 let client = self.get_client(); 597 598 let did = match ident { ··· 609 .map_err(|e| dioxus::CapturedError::from_display(e))?; 610 611 let result = Arc::new(profile_view); 612 - cache_impl::insert(&self.profile_cache, ident_static, result.clone()); 613 614 Ok(result) 615 } 616 } 617 618 - impl HttpClient for CachedFetcher { 619 #[doc = " Error type returned by the HTTP client"] 620 type Error = reqwest::Error; 621 ··· 645 } 646 } 647 648 - impl XrpcClient for CachedFetcher { 649 #[doc = " Get the base URI for the client."] 650 fn base_uri(&self) -> impl Future<Output = CowStr<'static>> + Send { 651 self.client.base_uri() ··· 717 } 718 } 719 720 - impl IdentityResolver for CachedFetcher { 721 #[doc = " Access options for validation decisions in default methods"] 722 fn options(&self) -> &ResolverOptions { 723 self.client.options() ··· 766 } 767 } 768 769 - impl LexiconSchemaResolver for CachedFetcher { 770 #[cfg(not(target_arch = "wasm32"))] 771 async fn resolve_lexicon_schema( 772 &self, ··· 784 } 785 } 786 787 - impl AgentSession for CachedFetcher { 788 #[doc = " Identify the kind of session."] 789 fn session_kind(&self) -> AgentKind { 790 self.client.session_kind()
··· 326 } 327 } 328 329 + //#[cfg(not(feature = "server"))] 330 #[derive(Clone)] 331 + pub struct Fetcher { 332 pub client: Arc<Client>, 333 } 334 335 + //#[cfg(not(feature = "server"))] 336 + impl Fetcher { 337 pub fn new(client: OAuthClient<JacquardResolver, AuthStore>) -> Self { 338 Self { 339 client: Arc::new(Client::new(client)), 340 } 341 } 342 ··· 374 ident: AtIdentifier<'static>, 375 title: SmolStr, 376 ) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 377 + let client = self.get_client(); 378 + if let Some((notebook, entries)) = client 379 + .notebook_by_title(&ident, &title) 380 + .await 381 + .map_err(|e| dioxus::CapturedError::from_display(e))? 382 + { 383 + let stored = Arc::new((notebook, entries)); 384 + Ok(Some(stored)) 385 } else { 386 + Ok(None) 387 } 388 } 389 ··· 395 ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> { 396 if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 397 let (notebook, entries) = result.as_ref(); 398 + let client = self.get_client(); 399 + if let Some(entry) = client 400 + .entry_by_title(notebook, entries.as_ref(), &entry_title) 401 + .await 402 + .map_err(|e| dioxus::CapturedError::from_display(e))? 403 { 404 + let stored = Arc::new(entry); 405 + Ok(Some(stored)) 406 } else { 407 + Ok(None) 408 } 409 } else { 410 Ok(None) ··· 449 .unwrap_or_else(|| SmolStr::new("Untitled")); 450 451 let result = Arc::new((notebook, entries)); 452 notebooks.push(result); 453 } 454 Err(_) => continue, // Skip notebooks that fail to load ··· 516 .unwrap_or_else(|| SmolStr::new("Untitled")); 517 518 let result = Arc::new((notebook, entries)); 519 notebooks.push(result); 520 } 521 Err(_) => continue, // Skip notebooks that fail to load ··· 553 &self, 554 ident: &AtIdentifier<'_>, 555 ) -> Result<Arc<ProfileDataView<'static>>> { 556 let client = self.get_client(); 557 558 let did = match ident { ··· 569 .map_err(|e| dioxus::CapturedError::from_display(e))?; 570 571 let result = Arc::new(profile_view); 572 573 Ok(result) 574 } 575 } 576 577 + // //#[cfg(feature = "server")] 578 + // #[derive(Clone)] 579 + // pub struct Fetcher { 580 + // pub client: Arc<Client>, 581 + // book_cache: cache_impl::Cache< 582 + // (AtIdentifier<'static>, SmolStr), 583 + // Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 584 + // >, 585 + // entry_cache: cache_impl::Cache< 586 + // (AtIdentifier<'static>, SmolStr), 587 + // Arc<(BookEntryView<'static>, Entry<'static>)>, 588 + // >, 589 + // profile_cache: cache_impl::Cache<AtIdentifier<'static>, Arc<ProfileDataView<'static>>>, 590 + // } 591 + 592 + // // /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM 593 + // //#[cfg(feature = "server")] 594 + // unsafe impl Sync for Fetcher {} 595 + 596 + // // /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM 597 + // //#[cfg(feature = "server")] 598 + // unsafe impl Send for Fetcher {} 599 + 600 + // //#[cfg(feature = "server")] 601 + // impl Fetcher { 602 + // pub fn new(client: OAuthClient<JacquardResolver, AuthStore>) -> Self { 603 + // Self { 604 + // client: Arc::new(Client::new(client)), 605 + // book_cache: cache_impl::new_cache(100, Duration::from_secs(30)), 606 + // entry_cache: cache_impl::new_cache(100, Duration::from_secs(30)), 607 + // profile_cache: cache_impl::new_cache(100, Duration::from_secs(1800)), 608 + // } 609 + // } 610 + 611 + // pub async fn upgrade_to_authenticated( 612 + // &self, 613 + // session: OAuthSession<JacquardResolver, crate::auth::AuthStore>, 614 + // ) { 615 + // let mut session_slot = self.client.session.write().await; 616 + // *session_slot = Some(Arc::new(Agent::new(session))); 617 + // } 618 + 619 + // pub async fn downgrade_to_unauthenticated(&self) { 620 + // let mut session_slot = self.client.session.write().await; 621 + // if let Some(session) = session_slot.take() { 622 + // session.inner().logout().await.ok(); 623 + // } 624 + // } 625 + 626 + // #[allow(dead_code)] 627 + // pub async fn current_did(&self) -> Option<Did<'static>> { 628 + // let session_slot = self.client.session.read().await; 629 + // if let Some(session) = session_slot.as_ref() { 630 + // session.info().await.map(|(d, _)| d) 631 + // } else { 632 + // None 633 + // } 634 + // } 635 + 636 + // pub fn get_client(&self) -> Arc<Client> { 637 + // self.client.clone() 638 + // } 639 + 640 + // pub async fn get_notebook( 641 + // &self, 642 + // ident: AtIdentifier<'static>, 643 + // title: SmolStr, 644 + // ) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 645 + // if let Some(entry) = cache_impl::get(&self.book_cache, &(ident.clone(), title.clone())) { 646 + // Ok(Some(entry)) 647 + // } else { 648 + // let client = self.get_client(); 649 + // if let Some((notebook, entries)) = client 650 + // .notebook_by_title(&ident, &title) 651 + // .await 652 + // .map_err(|e| dioxus::CapturedError::from_display(e))? 653 + // { 654 + // let stored = Arc::new((notebook, entries)); 655 + // cache_impl::insert(&self.book_cache, (ident, title), stored.clone()); 656 + // Ok(Some(stored)) 657 + // } else { 658 + // Ok(None) 659 + // } 660 + // } 661 + // } 662 + 663 + // pub async fn get_entry( 664 + // &self, 665 + // ident: AtIdentifier<'static>, 666 + // book_title: SmolStr, 667 + // entry_title: SmolStr, 668 + // ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> { 669 + // if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 670 + // let (notebook, entries) = result.as_ref(); 671 + // if let Some(entry) = 672 + // cache_impl::get(&self.entry_cache, &(ident.clone(), entry_title.clone())) 673 + // { 674 + // Ok(Some(entry)) 675 + // } else { 676 + // let client = self.get_client(); 677 + // if let Some(entry) = client 678 + // .entry_by_title(notebook, entries.as_ref(), &entry_title) 679 + // .await 680 + // .map_err(|e| dioxus::CapturedError::from_display(e))? 681 + // { 682 + // let stored = Arc::new(entry); 683 + // cache_impl::insert(&self.entry_cache, (ident, entry_title), stored.clone()); 684 + // Ok(Some(stored)) 685 + // } else { 686 + // Ok(None) 687 + // } 688 + // } 689 + // } else { 690 + // Ok(None) 691 + // } 692 + // } 693 + 694 + // pub async fn fetch_notebooks_from_ufos( 695 + // &self, 696 + // ) -> Result<Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 697 + // use jacquard::{IntoStatic, types::aturi::AtUri}; 698 + 699 + // let url = "https://ufos-api.microcosm.blue/records?collection=sh.weaver.notebook.book"; 700 + // let response = reqwest::get(url) 701 + // .await 702 + // .map_err(|e| dioxus::CapturedError::from_display(e))?; 703 + 704 + // let records: Vec<UfosRecord> = response 705 + // .json() 706 + // .await 707 + // .map_err(|e| dioxus::CapturedError::from_display(e))?; 708 + 709 + // let mut notebooks = Vec::new(); 710 + // let client = self.get_client(); 711 + 712 + // for ufos_record in records { 713 + // // Construct URI 714 + // let uri_str = format!( 715 + // "at://{}/{}/{}", 716 + // ufos_record.did, ufos_record.collection, ufos_record.rkey 717 + // ); 718 + // let uri = AtUri::new_owned(uri_str) 719 + // .map_err(|e| dioxus::CapturedError::from_display(format!("Invalid URI: {}", e)))?; 720 + 721 + // // Fetch the full notebook view (which hydrates authors) 722 + // match client.view_notebook(&uri).await { 723 + // Ok((notebook, entries)) => { 724 + // let ident = uri.authority().clone().into_static(); 725 + // let title = notebook 726 + // .title 727 + // .as_ref() 728 + // .map(|t| SmolStr::new(t.as_ref())) 729 + // .unwrap_or_else(|| SmolStr::new("Untitled")); 730 + 731 + // let result = Arc::new((notebook, entries)); 732 + // // Cache it 733 + // cache_impl::insert(&self.book_cache, (ident, title), result.clone()); 734 + // notebooks.push(result); 735 + // } 736 + // Err(_) => continue, // Skip notebooks that fail to load 737 + // } 738 + // } 739 + 740 + // Ok(notebooks) 741 + // } 742 + 743 + // pub async fn fetch_notebooks_for_did( 744 + // &self, 745 + // ident: &AtIdentifier<'_>, 746 + // ) -> Result<Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 747 + // use jacquard::{ 748 + // IntoStatic, 749 + // types::{collection::Collection, nsid::Nsid}, 750 + // xrpc::XrpcExt, 751 + // }; 752 + // use weaver_api::{ 753 + // com_atproto::repo::list_records::ListRecords, sh_weaver::notebook::book::Book, 754 + // }; 755 + 756 + // let client = self.get_client(); 757 + 758 + // // Resolve DID and PDS 759 + // let (repo_did, pds_url) = match ident { 760 + // AtIdentifier::Did(did) => { 761 + // let pds = client 762 + // .pds_for_did(did) 763 + // .await 764 + // .map_err(|e| dioxus::CapturedError::from_display(e))?; 765 + // (did.clone(), pds) 766 + // } 767 + // AtIdentifier::Handle(handle) => client 768 + // .pds_for_handle(handle) 769 + // .await 770 + // .map_err(|e| dioxus::CapturedError::from_display(e))?, 771 + // }; 772 + 773 + // // Fetch all notebook records for this repo 774 + // let resp = client 775 + // .xrpc(pds_url) 776 + // .send( 777 + // &ListRecords::new() 778 + // .repo(repo_did) 779 + // .collection(Nsid::raw(Book::NSID)) 780 + // .limit(100) 781 + // .build(), 782 + // ) 783 + // .await 784 + // .map_err(|e| dioxus::CapturedError::from_display(e))?; 785 + 786 + // let mut notebooks = Vec::new(); 787 + 788 + // if let Ok(list) = resp.parse() { 789 + // for record in list.records { 790 + // // View the notebook (which hydrates authors) 791 + // match client.view_notebook(&record.uri).await { 792 + // Ok((notebook, entries)) => { 793 + // let ident = record.uri.authority().clone().into_static(); 794 + // let title = notebook 795 + // .title 796 + // .as_ref() 797 + // .map(|t| SmolStr::new(t.as_ref())) 798 + // .unwrap_or_else(|| SmolStr::new("Untitled")); 799 + 800 + // let result = Arc::new((notebook, entries)); 801 + // // Cache it 802 + // cache_impl::insert(&self.book_cache, (ident, title), result.clone()); 803 + // notebooks.push(result); 804 + // } 805 + // Err(_) => continue, // Skip notebooks that fail to load 806 + // } 807 + // } 808 + // } 809 + 810 + // Ok(notebooks) 811 + // } 812 + 813 + // pub async fn list_notebook_entries( 814 + // &self, 815 + // ident: AtIdentifier<'static>, 816 + // book_title: SmolStr, 817 + // ) -> Result<Option<Vec<BookEntryView<'static>>>> { 818 + // if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 819 + // let (notebook, entries) = result.as_ref(); 820 + // let mut book_entries = Vec::new(); 821 + // let client = self.get_client(); 822 + 823 + // for index in 0..entries.len() { 824 + // match client.view_entry(notebook, entries, index).await { 825 + // Ok(book_entry) => book_entries.push(book_entry), 826 + // Err(_) => continue, // Skip entries that fail to load 827 + // } 828 + // } 829 + 830 + // Ok(Some(book_entries)) 831 + // } else { 832 + // Ok(None) 833 + // } 834 + // } 835 + 836 + // pub async fn fetch_profile( 837 + // &self, 838 + // ident: &AtIdentifier<'_>, 839 + // ) -> Result<Arc<ProfileDataView<'static>>> { 840 + // use jacquard::IntoStatic; 841 + 842 + // let ident_static = ident.clone().into_static(); 843 + 844 + // if let Some(cached) = cache_impl::get(&self.profile_cache, &ident_static) { 845 + // return Ok(cached); 846 + // } 847 + 848 + // let client = self.get_client(); 849 + 850 + // let did = match ident { 851 + // AtIdentifier::Did(d) => d.clone(), 852 + // AtIdentifier::Handle(h) => client 853 + // .resolve_handle(h) 854 + // .await 855 + // .map_err(|e| dioxus::CapturedError::from_display(e))?, 856 + // }; 857 + 858 + // let (_uri, profile_view) = client 859 + // .hydrate_profile_view(&did) 860 + // .await 861 + // .map_err(|e| dioxus::CapturedError::from_display(e))?; 862 + 863 + // let result = Arc::new(profile_view); 864 + // cache_impl::insert(&self.profile_cache, ident_static, result.clone()); 865 + 866 + // Ok(result) 867 + // } 868 + // } 869 + 870 + impl HttpClient for Fetcher { 871 #[doc = " Error type returned by the HTTP client"] 872 type Error = reqwest::Error; 873 ··· 897 } 898 } 899 900 + impl XrpcClient for Fetcher { 901 #[doc = " Get the base URI for the client."] 902 fn base_uri(&self) -> impl Future<Output = CowStr<'static>> + Send { 903 self.client.base_uri() ··· 969 } 970 } 971 972 + impl IdentityResolver for Fetcher { 973 #[doc = " Access options for validation decisions in default methods"] 974 fn options(&self) -> &ResolverOptions { 975 self.client.options() ··· 1018 } 1019 } 1020 1021 + impl LexiconSchemaResolver for Fetcher { 1022 #[cfg(not(target_arch = "wasm32"))] 1023 async fn resolve_lexicon_schema( 1024 &self, ··· 1036 } 1037 } 1038 1039 + impl AgentSession for Fetcher { 1040 #[doc = " Identify the kind of session."] 1041 fn session_kind(&self) -> AgentKind { 1042 self.client.session_kind()
+21 -25
crates/weaver-app/src/main.rs
··· 1 // The dioxus prelude contains a ton of common items used in dioxus apps. It's a good idea to import wherever you 2 // need dioxus 3 - use components::{Entry, Repository, RepositoryIndex}; 4 #[allow(unused)] 5 use dioxus::{CapturedError, prelude::*}; 6 ··· 14 smol_str::SmolStr, 15 types::{cid::Cid, string::AtIdentifier}, 16 }; 17 - #[cfg(feature = "server")] 18 - use std::sync::Arc; 19 - use std::sync::LazyLock; 20 #[allow(unused)] 21 use views::{ 22 - Callback, Home, Navbar, Notebook, NotebookIndex, NotebookPage, RecordIndex, RecordView, 23 }; 24 25 use crate::{ ··· 37 mod data; 38 mod env; 39 mod fetch; 40 mod service_worker; 41 /// Define a views module that contains the UI for all Layouts and Routes for our app. 42 mod views; ··· 59 #[nest("/record")] 60 #[layout(RecordIndex)] 61 #[route("/:..uri")] 62 - RecordView { uri: Vec<String> }, 63 #[end_layout] 64 #[end_nest] 65 #[route("/callback?:state&:iss&:code")] ··· 73 #[route("/")] 74 NotebookIndex { ident: AtIdentifier<'static>, book_title: SmolStr }, 75 #[route("/:title")] 76 - Entry { ident: AtIdentifier<'static>, book_title: SmolStr, title: SmolStr } 77 78 } 79 const FAVICON: Asset = asset!("/assets/weaver_photo_sm.jpg"); ··· 122 #[cfg(feature = "fullstack-server")] 123 let router = { 124 use jacquard::client::UnauthenticatedSession; 125 - let fetcher = Arc::new(fetch::CachedFetcher::new(OAuthClient::new( 126 AuthStore::new(), 127 ClientData::new_public(CONFIG.oauth.clone()), 128 ))); ··· 132 axum::Router::new() 133 // Server side render the application, serve static assets, and register server functions 134 .serve_dioxus_application( 135 - ServeConfig::builder() 136 - // Enable incremental rendering 137 - .incremental( 138 - dioxus::server::IncrementalRendererConfig::new() 139 - .pre_render(true) 140 - .clear_cache(false), 141 - ) 142 - .enable_out_of_order_streaming(), 143 App, 144 ) 145 .layer(middleware::from_fn({ ··· 165 166 #[component] 167 fn App() -> Element { 168 - use_context_provider(|| { 169 - fetch::CachedFetcher::new(OAuthClient::new( 170 AuthStore::new(), 171 ClientData::new_public(CONFIG.oauth.clone()), 172 )) 173 }); 174 use_context_provider(|| Signal::new(AuthState::default())); 175 - 176 use_effect(move || { 177 spawn(async move { 178 - if let Err(e) = auth::restore_session().await { 179 - tracing::warn!("Session restoration failed: {}", e); 180 } 181 }); 182 }); ··· 186 target_os = "unknown", 187 not(feature = "fullstack-server") 188 ))] 189 - use_effect(move || { 190 - spawn(async move { 191 - let _ = service_worker::register_service_worker().await; 192 - }); 193 }); 194 195 rsx! {
··· 1 // The dioxus prelude contains a ton of common items used in dioxus apps. It's a good idea to import wherever you 2 // need dioxus 3 + use components::{EntryPage, Repository, RepositoryIndex}; 4 #[allow(unused)] 5 use dioxus::{CapturedError, prelude::*}; 6 ··· 14 smol_str::SmolStr, 15 types::{cid::Cid, string::AtIdentifier}, 16 }; 17 + use std::sync::{Arc, LazyLock}; 18 #[allow(unused)] 19 use views::{ 20 + Callback, Home, Navbar, Notebook, NotebookIndex, NotebookPage, RecordIndex, RecordPage, 21 }; 22 23 use crate::{ ··· 35 mod data; 36 mod env; 37 mod fetch; 38 + mod record_utils; 39 mod service_worker; 40 /// Define a views module that contains the UI for all Layouts and Routes for our app. 41 mod views; ··· 58 #[nest("/record")] 59 #[layout(RecordIndex)] 60 #[route("/:..uri")] 61 + RecordPage { uri: Vec<String> }, 62 #[end_layout] 63 #[end_nest] 64 #[route("/callback?:state&:iss&:code")] ··· 72 #[route("/")] 73 NotebookIndex { ident: AtIdentifier<'static>, book_title: SmolStr }, 74 #[route("/:title")] 75 + EntryPage { ident: AtIdentifier<'static>, book_title: SmolStr, title: SmolStr } 76 77 } 78 const FAVICON: Asset = asset!("/assets/weaver_photo_sm.jpg"); ··· 121 #[cfg(feature = "fullstack-server")] 122 let router = { 123 use jacquard::client::UnauthenticatedSession; 124 + let fetcher = Arc::new(fetch::Fetcher::new(OAuthClient::new( 125 AuthStore::new(), 126 ClientData::new_public(CONFIG.oauth.clone()), 127 ))); ··· 131 axum::Router::new() 132 // Server side render the application, serve static assets, and register server functions 133 .serve_dioxus_application( 134 + ServeConfig::builder(), // Enable incremental rendering 135 + // .incremental( 136 + // dioxus::server::IncrementalRendererConfig::new() 137 + // .pre_render(true) 138 + // .clear_cache(false), 139 + // ) 140 + //.enable_out_of_order_streaming(), 141 App, 142 ) 143 .layer(middleware::from_fn({ ··· 163 164 #[component] 165 fn App() -> Element { 166 + let fetcher = use_context_provider(|| { 167 + fetch::Fetcher::new(OAuthClient::new( 168 AuthStore::new(), 169 ClientData::new_public(CONFIG.oauth.clone()), 170 )) 171 }); 172 use_context_provider(|| Signal::new(AuthState::default())); 173 use_effect(move || { 174 + let fetcher = fetcher.clone(); 175 spawn(async move { 176 + if let Err(e) = auth::restore_session(fetcher).await { 177 + tracing::debug!("Session restoration failed: {}", e); 178 } 179 }); 180 }); ··· 184 target_os = "unknown", 185 not(feature = "fullstack-server") 186 ))] 187 + spawn(async move { 188 + let _ = service_worker::register_service_worker().await; 189 }); 190 191 rsx! {
+361
crates/weaver-app/src/record_utils.rs
···
··· 1 + use dioxus::prelude::*; 2 + use jacquard::bytes::Bytes; 3 + use jacquard::common::{Data, IntoStatic}; 4 + use jacquard::types::LexiconStringType; 5 + use jacquard::types::string::AtprotoStr; 6 + use jacquard_lexicon::validation::{ 7 + ConstraintError, StructuralError, ValidationError, ValidationResult, 8 + }; 9 + 10 + // ============================================================================ 11 + // Validation Helper Functions 12 + // ============================================================================ 13 + 14 + /// Parse UI path into segments (fields and indices only) 15 + fn parse_ui_path(ui_path: &str) -> Vec<UiPathSegment> { 16 + if ui_path.is_empty() { 17 + return vec![]; 18 + } 19 + 20 + let mut segments = Vec::new(); 21 + let mut current = String::new(); 22 + 23 + for ch in ui_path.chars() { 24 + match ch { 25 + '.' => { 26 + if !current.is_empty() { 27 + segments.push(UiPathSegment::Field(current.clone())); 28 + current.clear(); 29 + } 30 + } 31 + '[' => { 32 + if !current.is_empty() { 33 + segments.push(UiPathSegment::Field(current.clone())); 34 + current.clear(); 35 + } 36 + } 37 + ']' => { 38 + if !current.is_empty() { 39 + if let Ok(idx) = current.parse::<usize>() { 40 + segments.push(UiPathSegment::Index(idx)); 41 + } 42 + current.clear(); 43 + } 44 + } 45 + c => current.push(c), 46 + } 47 + } 48 + 49 + if !current.is_empty() { 50 + segments.push(UiPathSegment::Field(current)); 51 + } 52 + 53 + segments 54 + } 55 + 56 + #[derive(Debug, PartialEq)] 57 + enum UiPathSegment { 58 + Field(String), 59 + Index(usize), 60 + } 61 + 62 + /// Get all validation errors at exactly this path (not children) 63 + pub fn get_errors_at_exact_path( 64 + validation_result: &Option<ValidationResult>, 65 + ui_path: &str, 66 + ) -> Vec<String> { 67 + use jacquard_lexicon::validation::PathSegment; 68 + 69 + if let Some(result) = validation_result { 70 + let ui_segments = parse_ui_path(ui_path); 71 + 72 + result 73 + .all_errors() 74 + .filter_map(|err| { 75 + let validation_path = match &err { 76 + ValidationError::Structural(s) => match s { 77 + StructuralError::TypeMismatch { path, .. } => Some(path), 78 + StructuralError::MissingRequiredField { path, .. } => Some(path), 79 + StructuralError::MissingUnionDiscriminator { path } => Some(path), 80 + StructuralError::UnionNoMatch { path, .. } => Some(path), 81 + StructuralError::UnresolvedRef { path, .. } => Some(path), 82 + StructuralError::RefCycle { path, .. } => Some(path), 83 + StructuralError::MaxDepthExceeded { path, .. } => Some(path), 84 + }, 85 + ValidationError::Constraint(c) => match c { 86 + ConstraintError::MaxLength { path, .. } => Some(path), 87 + ConstraintError::MaxGraphemes { path, .. } => Some(path), 88 + ConstraintError::MinLength { path, .. } => Some(path), 89 + ConstraintError::MinGraphemes { path, .. } => Some(path), 90 + ConstraintError::Maximum { path, .. } => Some(path), 91 + ConstraintError::Minimum { path, .. } => Some(path), 92 + }, 93 + }; 94 + 95 + if let Some(path) = validation_path { 96 + // Convert validation path to UI segments 97 + let validation_ui_segments: Vec<_> = path 98 + .segments() 99 + .iter() 100 + .filter_map(|seg| match seg { 101 + PathSegment::Field(name) => { 102 + Some(UiPathSegment::Field(name.to_string())) 103 + } 104 + PathSegment::Index(idx) => Some(UiPathSegment::Index(*idx)), 105 + PathSegment::UnionVariant(_) => None, 106 + }) 107 + .collect(); 108 + 109 + // Exact match only 110 + if validation_ui_segments == ui_segments { 111 + return Some(err.to_string()); 112 + } 113 + } 114 + None 115 + }) 116 + .collect() 117 + } else { 118 + Vec::new() 119 + } 120 + } 121 + 122 + // ============================================================================ 123 + // Pretty Editor: Helper Functions 124 + // ============================================================================ 125 + 126 + /// Infer Data type from text input 127 + pub fn infer_data_from_text(text: &str) -> Result<Data<'static>, String> { 128 + let trimmed = text.trim(); 129 + 130 + if trimmed == "true" || trimmed == "false" { 131 + Ok(Data::Boolean(trimmed == "true")) 132 + } else if trimmed == "{}" { 133 + use jacquard::types::value::Object; 134 + use std::collections::BTreeMap; 135 + Ok(Data::Object(Object(BTreeMap::new()))) 136 + } else if trimmed == "[]" { 137 + use jacquard::types::value::Array; 138 + Ok(Data::Array(Array(Vec::new()))) 139 + } else if trimmed == "null" { 140 + Ok(Data::Null) 141 + } else if let Ok(num) = trimmed.parse::<i64>() { 142 + Ok(Data::Integer(num)) 143 + } else { 144 + // Smart string parsing 145 + use jacquard::types::value::parsing; 146 + Ok(Data::String(parsing::parse_string(trimmed).into_static())) 147 + } 148 + } 149 + 150 + /// Parse text as specific AtprotoStr type, preserving type information 151 + pub fn try_parse_as_type( 152 + text: &str, 153 + string_type: LexiconStringType, 154 + ) -> Result<AtprotoStr<'static>, String> { 155 + use jacquard::types::string::*; 156 + use std::str::FromStr; 157 + 158 + match string_type { 159 + LexiconStringType::Datetime => Datetime::from_str(text) 160 + .map(AtprotoStr::Datetime) 161 + .map_err(|e| format!("Invalid datetime: {}", e)), 162 + LexiconStringType::Did => Did::new(text) 163 + .map(|v| AtprotoStr::Did(v.into_static())) 164 + .map_err(|e| format!("Invalid DID: {}", e)), 165 + LexiconStringType::Handle => Handle::new(text) 166 + .map(|v| AtprotoStr::Handle(v.into_static())) 167 + .map_err(|e| format!("Invalid handle: {}", e)), 168 + LexiconStringType::AtUri => AtUri::new(text) 169 + .map(|v| AtprotoStr::AtUri(v.into_static())) 170 + .map_err(|e| format!("Invalid AT-URI: {}", e)), 171 + LexiconStringType::AtIdentifier => AtIdentifier::new(text) 172 + .map(|v| AtprotoStr::AtIdentifier(v.into_static())) 173 + .map_err(|e| format!("Invalid identifier: {}", e)), 174 + LexiconStringType::Nsid => Nsid::new(text) 175 + .map(|v| AtprotoStr::Nsid(v.into_static())) 176 + .map_err(|e| format!("Invalid NSID: {}", e)), 177 + LexiconStringType::Tid => Tid::new(text) 178 + .map(|v| AtprotoStr::Tid(v.into_static())) 179 + .map_err(|e| format!("Invalid TID: {}", e)), 180 + LexiconStringType::RecordKey => Rkey::new(text) 181 + .map(|rk| AtprotoStr::RecordKey(RecordKey::from(rk))) 182 + .map_err(|e| format!("Invalid record key: {}", e)), 183 + LexiconStringType::Cid => Cid::new(text.as_bytes()) 184 + .map(|v| AtprotoStr::Cid(v.into_static())) 185 + .map_err(|_| "Invalid CID".to_string()), 186 + LexiconStringType::Language => Language::new(text) 187 + .map(AtprotoStr::Language) 188 + .map_err(|e| format!("Invalid language: {}", e)), 189 + LexiconStringType::Uri(_) => Uri::new(text) 190 + .map(|u| AtprotoStr::Uri(u.into_static())) 191 + .map_err(|e| format!("Invalid URI: {}", e)), 192 + LexiconStringType::String => { 193 + // Plain strings: use smart inference 194 + use jacquard::types::value::parsing; 195 + Ok(parsing::parse_string(text).into_static()) 196 + } 197 + } 198 + } 199 + 200 + /// Create default value for new array item by cloning structure of existing items 201 + pub fn create_array_item_default(arr: &jacquard::types::value::Array) -> Data<'static> { 202 + if let Some(existing) = arr.0.first() { 203 + clone_structure(existing) 204 + } else { 205 + // Empty array, default to null (user can change type) 206 + Data::Null 207 + } 208 + } 209 + 210 + /// Clone structure of Data, setting sensible defaults for leaf values 211 + pub fn clone_structure(data: &Data) -> Data<'static> { 212 + use jacquard::types::string::*; 213 + use jacquard::types::value::{Array, Object}; 214 + use jacquard::types::{LexiconStringType, blob::*}; 215 + use std::collections::BTreeMap; 216 + 217 + match data { 218 + Data::Object(obj) => { 219 + let mut new_obj = BTreeMap::new(); 220 + for (key, value) in obj.0.iter() { 221 + new_obj.insert(key.clone(), clone_structure(value)); 222 + } 223 + Data::Object(Object(new_obj)) 224 + } 225 + Data::Array(_) => Data::Array(Array(Vec::new())), 226 + Data::String(s) => match s.string_type() { 227 + LexiconStringType::Datetime => { 228 + // Sensible default: now 229 + Data::String(AtprotoStr::Datetime(Datetime::now())) 230 + } 231 + LexiconStringType::Tid => Data::String(AtprotoStr::Tid(Tid::now_0())), 232 + _ => { 233 + // Empty string, type inference will handle it 234 + Data::String(AtprotoStr::String("".into())) 235 + } 236 + }, 237 + Data::Integer(_) => Data::Integer(0), 238 + Data::Boolean(_) => Data::Boolean(false), 239 + Data::Blob(blob) => { 240 + // Placeholder blob 241 + Data::Blob( 242 + Blob { 243 + r#ref: CidLink::str( 244 + "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 245 + ), 246 + mime_type: blob.mime_type.clone(), 247 + size: 0, 248 + } 249 + .into_static(), 250 + ) 251 + } 252 + Data::CidLink(_) => Data::CidLink(Cid::str( 253 + "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 254 + )), 255 + Data::Bytes(_) => Data::Bytes(Bytes::new()), 256 + Data::Null => Data::Null, 257 + } 258 + } 259 + 260 + /// Get expected string format from schema by navigating path 261 + pub fn get_expected_string_format( 262 + root_data: &Data<'_>, 263 + current_path: &str, 264 + ) -> Option<jacquard_lexicon::lexicon::LexStringFormat> { 265 + use jacquard_lexicon::lexicon::*; 266 + 267 + // Get root type discriminator 268 + let root_nsid = root_data.type_discriminator()?; 269 + 270 + // Look up schema in global registry 271 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 272 + let registry = validator.registry(); 273 + let schema = registry.get(root_nsid)?; 274 + 275 + // Navigate to the property at this path 276 + let segments: Vec<&str> = if current_path.is_empty() { 277 + vec![] 278 + } else { 279 + current_path.split('.').collect() 280 + }; 281 + 282 + // Start with the record's main object definition 283 + let main_obj = match schema.defs.get("main")? { 284 + LexUserType::Record(rec) => match &rec.record { 285 + LexRecordRecord::Object(obj) => obj, 286 + }, 287 + _ => return None, 288 + }; 289 + 290 + // Track current position in schema 291 + enum SchemaType<'a> { 292 + ObjectProp(&'a LexObjectProperty<'a>), 293 + ArrayItem(&'a LexArrayItem<'a>), 294 + } 295 + 296 + let mut current_type: Option<SchemaType> = None; 297 + let mut current_obj = Some(main_obj); 298 + 299 + for segment in segments { 300 + // Handle array indices - strip them to get field name 301 + let field_name = segment.trim_end_matches(|c: char| c.is_numeric() || c == '[' || c == ']'); 302 + 303 + if field_name.is_empty() { 304 + continue; // Pure array index like [0], skip 305 + } 306 + 307 + if let Some(obj) = current_obj.take() { 308 + if let Some(prop) = obj.properties.get(field_name) { 309 + current_type = Some(SchemaType::ObjectProp(prop)); 310 + } 311 + } 312 + 313 + // Process current type 314 + match current_type { 315 + Some(SchemaType::ObjectProp(LexObjectProperty::Array(arr))) => { 316 + // Array - unwrap to item type 317 + current_type = Some(SchemaType::ArrayItem(&arr.items)); 318 + } 319 + Some(SchemaType::ObjectProp(LexObjectProperty::Object(obj))) => { 320 + // Nested object - descend into it 321 + current_obj = Some(obj); 322 + current_type = None; 323 + } 324 + Some(SchemaType::ArrayItem(LexArrayItem::Object(obj))) => { 325 + // Array of objects - descend into object 326 + current_obj = Some(obj); 327 + current_type = None; 328 + } 329 + _ => {} 330 + } 331 + } 332 + 333 + // Check if final type is a string with format 334 + match current_type? { 335 + SchemaType::ObjectProp(LexObjectProperty::String(lex_string)) => lex_string.format, 336 + SchemaType::ArrayItem(LexArrayItem::String(lex_string)) => lex_string.format, 337 + _ => None, 338 + } 339 + } 340 + 341 + pub fn get_hex_rep(byte_array: &mut [u8]) -> String { 342 + let build_string_vec: Vec<String> = byte_array 343 + .chunks(2) 344 + .enumerate() 345 + .map(|(i, c)| { 346 + let sep = if i % 16 == 0 && i > 0 { 347 + "\n" 348 + } else if i == 0 { 349 + "" 350 + } else { 351 + " " 352 + }; 353 + if c.len() == 2 { 354 + format!("{}{:02x}{:02x}", sep, c[0], c[1]) 355 + } else { 356 + format!("{}{:02x}", sep, c[0]) 357 + } 358 + }) 359 + .collect(); 360 + build_string_vec.join("") 361 + }
+1 -1
crates/weaver-app/src/service_worker.rs
··· 25 ident: &jacquard::types::ident::AtIdentifier<'_>, 26 book_title: &str, 27 images: &weaver_api::sh_weaver::embed::images::Images<'_>, 28 - fetcher: &crate::fetch::CachedFetcher, 29 ) -> Result<(), JsValue> { 30 use jacquard::prelude::IdentityResolver; 31 use std::collections::HashMap;
··· 25 ident: &jacquard::types::ident::AtIdentifier<'_>, 26 book_title: &str, 27 images: &weaver_api::sh_weaver::embed::images::Images<'_>, 28 + fetcher: &crate::fetch::Fetcher, 29 ) -> Result<(), JsValue> { 30 use jacquard::prelude::IdentityResolver; 31 use std::collections::HashMap;
+5 -3
crates/weaver-app/src/views/callback.rs
··· 1 use crate::auth::AuthState; 2 - use crate::fetch::CachedFetcher; 3 use dioxus::prelude::*; 4 use jacquard::{ 5 IntoStatic, ··· 15 iss: ReadSignal<SmolStr>, 16 code: ReadSignal<SmolStr>, 17 ) -> Element { 18 - let fetcher = use_context::<CachedFetcher>(); 19 let mut auth = use_context::<Signal<AuthState>>(); 20 #[cfg(feature = "web")] 21 let result = { ··· 43 }; 44 #[cfg(not(feature = "web"))] 45 let result = { use_resource(move || async { Ok::<(), OAuthError>(()) }) }; 46 47 match &*result.read_unchecked() { 48 Some(Ok(())) => { ··· 52 let mut prev = gloo_storage::LocalStorage::get::<String>("cached_route").ok(); 53 if let Some(prev) = prev.take() { 54 tracing::info!("Navigating to previous page"); 55 - let nav = use_navigator(); 56 gloo_storage::LocalStorage::delete("cached_route"); 57 nav.replace(prev); 58 }
··· 1 use crate::auth::AuthState; 2 + use crate::fetch::Fetcher; 3 use dioxus::prelude::*; 4 use jacquard::{ 5 IntoStatic, ··· 15 iss: ReadSignal<SmolStr>, 16 code: ReadSignal<SmolStr>, 17 ) -> Element { 18 + let fetcher = use_context::<Fetcher>(); 19 let mut auth = use_context::<Signal<AuthState>>(); 20 #[cfg(feature = "web")] 21 let result = { ··· 43 }; 44 #[cfg(not(feature = "web"))] 45 let result = { use_resource(move || async { Ok::<(), OAuthError>(()) }) }; 46 + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 47 + let nav = use_navigator(); 48 49 match &*result.read_unchecked() { 50 Some(Ok(())) => { ··· 54 let mut prev = gloo_storage::LocalStorage::get::<String>("cached_route").ok(); 55 if let Some(prev) = prev.take() { 56 tracing::info!("Navigating to previous page"); 57 + 58 gloo_storage::LocalStorage::delete("cached_route"); 59 nav.replace(prev); 60 }
crates/weaver-app/src/views/editor.rs

This is a binary file and will not be displayed.

+17 -21
crates/weaver-app/src/views/home.rs
··· 8 #[component] 9 pub fn Home() -> Element { 10 // Fetch notebooks from UFOS with SSR support 11 - let notebooks = data::use_notebooks_from_ufos().ok(); 12 let navigator = use_navigator(); 13 let mut uri_input = use_signal(|| String::new()); 14 ··· 16 let input_uri = uri_input.read().clone(); 17 if !input_uri.is_empty() { 18 if let Ok(parsed) = AtUri::new(&input_uri) { 19 - navigator.push(Route::RecordView { 20 uri: vec![parsed.to_string()], 21 }); 22 } ··· 44 } 45 } 46 div { class: "notebooks-list", 47 - if let Some(notebooks_memo) = &notebooks { 48 - match &*notebooks_memo.read_unchecked() { 49 - Some(notebook_list) => rsx! { 50 - for notebook in notebook_list.iter() { 51 - { 52 - let view = &notebook.0; 53 - let entries = &notebook.1; 54 - rsx! { 55 - div { 56 - key: "{view.cid}", 57 - NotebookCard { 58 - notebook: view.clone(), 59 - entry_refs: entries.clone() 60 - } 61 } 62 } 63 } 64 } 65 - }, 66 - None => rsx! { 67 - div { "Loading notebooks..." } 68 } 69 } 70 - } else { 71 - div { "Loading notebooks..." } 72 } 73 } 74 }
··· 8 #[component] 9 pub fn Home() -> Element { 10 // Fetch notebooks from UFOS with SSR support 11 + let notebooks = data::use_notebooks_from_ufos()?; 12 let navigator = use_navigator(); 13 let mut uri_input = use_signal(|| String::new()); 14 ··· 16 let input_uri = uri_input.read().clone(); 17 if !input_uri.is_empty() { 18 if let Ok(parsed) = AtUri::new(&input_uri) { 19 + navigator.push(Route::RecordPage { 20 uri: vec![parsed.to_string()], 21 }); 22 } ··· 44 } 45 } 46 div { class: "notebooks-list", 47 + match &*notebooks.read() { 48 + Some(notebook_list) => rsx! { 49 + for notebook in notebook_list.iter() { 50 + { 51 + let view = &notebook.0; 52 + let entries = &notebook.1; 53 + rsx! { 54 + div { 55 + key: "{view.cid}", 56 + NotebookCard { 57 + notebook: view.clone(), 58 + entry_refs: entries.clone() 59 } 60 } 61 } 62 } 63 } 64 + }, 65 + _ => rsx! { 66 + div { "Loading notebooks..." } 67 } 68 } 69 } 70 }
+3 -1
crates/weaver-app/src/views/mod.rs
··· 21 pub use notebook::{Notebook, NotebookIndex}; 22 23 mod record; 24 - pub use record::{RecordIndex, RecordView}; 25 26 mod callback; 27 pub use callback::Callback;
··· 21 pub use notebook::{Notebook, NotebookIndex}; 22 23 mod record; 24 + pub use record::{RecordIndex, RecordPage, RecordView}; 25 26 mod callback; 27 pub use callback::Callback; 28 + 29 + mod editor;
+44 -30
crates/weaver-app/src/views/navbar.rs
··· 1 use crate::Route; 2 use crate::components::button::{Button, ButtonVariant}; 3 use crate::components::login::LoginModal; 4 - use crate::data::use_handle; 5 - use crate::fetch::CachedFetcher; 6 use dioxus::prelude::*; 7 - use jacquard::types::string::AtIdentifier; 8 9 const THEME_DEFAULTS_CSS: Asset = asset!("/assets/styling/theme-defaults.css"); 10 const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css"); ··· 19 let route = use_route::<Route>(); 20 let mut auth_state = use_context::<Signal<crate::auth::AuthState>>(); 21 let mut show_login_modal = use_signal(|| false); 22 - let fetcher = use_context::<CachedFetcher>(); 23 24 rsx! { 25 document::Link { rel: "stylesheet", href: THEME_DEFAULTS_CSS } ··· 36 37 // Show repository breadcrumb if we're on a repository page 38 match route { 39 - Route::RepositoryIndex { ident } => rsx! { 40 - span { class: "breadcrumb-separator", " > " } 41 - span { class: "breadcrumb breadcrumb-current", "@{use_handle(ident.clone())?}" } 42 - }, 43 - Route::NotebookIndex { ident, book_title } => rsx! { 44 - span { class: "breadcrumb-separator", " > " } 45 - Link { 46 - to: Route::RepositoryIndex { ident: ident.clone() }, 47 - class: "breadcrumb", 48 - "@{use_handle(ident.clone())?}" 49 } 50 - span { class: "breadcrumb-separator", " > " } 51 - span { class: "breadcrumb breadcrumb-current", "{book_title}" } 52 }, 53 - Route::Entry { ident, book_title, .. } => rsx! { 54 - span { class: "breadcrumb-separator", " > " } 55 - Link { 56 - to: Route::RepositoryIndex { ident: ident.clone() }, 57 - class: "breadcrumb", 58 - "@{use_handle(ident.clone())?}" 59 } 60 - span { class: "breadcrumb-separator", " > " } 61 - Link { 62 - to: Route::NotebookIndex { ident: ident.clone(), book_title: book_title.clone() }, 63 - class: "breadcrumb", 64 - "{book_title}" 65 } 66 }, 67 _ => rsx! {} ··· 78 fetcher.downgrade_to_unauthenticated().await; 79 } 80 }, 81 - span { class: "auth-handle", "@{use_handle(AtIdentifier::Did(did.clone()))?}" } 82 } 83 } 84 } else { 85 div { 86 class: "auth-button", ··· 97 } 98 } 99 100 - // The `Outlet` component is used to render the next component inside the layout. In this case, it will render either 101 - // the [`Home`] or [`Blog`] component depending on the current route. 102 Outlet::<Route> {} 103 } 104 }
··· 1 use crate::Route; 2 use crate::components::button::{Button, ButtonVariant}; 3 use crate::components::login::LoginModal; 4 + use crate::data::{get_handle, use_notebook_handle}; 5 + use crate::fetch::Fetcher; 6 use dioxus::prelude::*; 7 8 const THEME_DEFAULTS_CSS: Asset = asset!("/assets/styling/theme-defaults.css"); 9 const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css"); ··· 18 let route = use_route::<Route>(); 19 let mut auth_state = use_context::<Signal<crate::auth::AuthState>>(); 20 let mut show_login_modal = use_signal(|| false); 21 + let fetcher = use_context::<Fetcher>(); 22 + let route_handle = use_signal(|| match &route { 23 + Route::EntryPage { ident, .. } => Some(ident.clone()), 24 + Route::RepositoryIndex { ident } => Some(ident.clone()), 25 + Route::NotebookIndex { ident, .. } => Some(ident.clone()), 26 + _ => None, 27 + }); 28 + let notebook_handle = use_notebook_handle(route_handle); 29 30 rsx! { 31 document::Link { rel: "stylesheet", href: THEME_DEFAULTS_CSS } ··· 42 43 // Show repository breadcrumb if we're on a repository page 44 match route { 45 + Route::RepositoryIndex { .. } => { 46 + let handle = notebook_handle.as_ref().unwrap(); 47 + rsx! { 48 + span { class: "breadcrumb-separator", " > " } 49 + span { class: "breadcrumb breadcrumb-current", "@{handle}" } 50 } 51 }, 52 + Route::NotebookIndex { ident, book_title } => { 53 + let handle = notebook_handle.as_ref().unwrap(); 54 + rsx! { 55 + span { class: "breadcrumb-separator", " > " } 56 + Link { 57 + to: Route::RepositoryIndex { ident: ident.clone() }, 58 + class: "breadcrumb", 59 + "@{handle}" 60 + } 61 + span { class: "breadcrumb-separator", " > " } 62 + span { class: "breadcrumb breadcrumb-current", "{book_title}" } 63 } 64 + }, 65 + Route::EntryPage { ident, book_title, .. } => { 66 + let handle = notebook_handle.as_ref().unwrap(); 67 + rsx! { 68 + span { class: "breadcrumb-separator", " > " } 69 + Link { 70 + to: Route::RepositoryIndex { ident: ident.clone() }, 71 + class: "breadcrumb", 72 + "@{handle}" 73 + } 74 + span { class: "breadcrumb-separator", " > " } 75 + Link { 76 + to: Route::NotebookIndex { ident: ident.clone(), book_title: book_title.clone() }, 77 + class: "breadcrumb", 78 + "{book_title}" 79 + } 80 } 81 }, 82 _ => rsx! {} ··· 93 fetcher.downgrade_to_unauthenticated().await; 94 } 95 }, 96 + span { class: "auth-handle", "@{get_handle(did.clone())}" } 97 } 98 } 99 + 100 } else { 101 div { 102 class: "auth-button", ··· 113 } 114 } 115 116 Outlet::<Route> {} 117 } 118 }
+23 -27
crates/weaver-app/src/views/notebook.rs
··· 29 book_title: ReadSignal<SmolStr>, 30 ) -> Element { 31 // Fetch full notebook metadata with SSR support 32 - let notebook_data = data::use_notebook(ident(), book_title()).ok(); 33 34 // Fetch entries with SSR support 35 - let entries_resource = data::use_notebook_entries(ident(), book_title()).ok(); 36 37 rsx! { 38 document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 39 40 - if let (Some(notebook_memo), Some(entries_memo)) = (&notebook_data, &entries_resource) { 41 - match (&*notebook_memo.read_unchecked(), &*entries_memo.read_unchecked()) { 42 - (Some(data), Some(entries)) => { 43 - let (notebook_view, _) = data; 44 - let author_count = notebook_view.authors.len(); 45 46 - rsx! { 47 - div { class: "notebook-layout", 48 - aside { class: "notebook-sidebar", 49 - NotebookCover { 50 - notebook: notebook_view.clone(), 51 - title: book_title().to_string() 52 - } 53 } 54 55 - main { class: "notebook-main", 56 - div { class: "entries-list", 57 - for entry in entries { 58 - EntryCard { 59 - entry: entry.clone(), 60 - book_title: book_title(), 61 - author_count 62 - } 63 } 64 } 65 } 66 } 67 } 68 - }, 69 - _ => rsx! { div { class: "loading", "Loading..." } } 70 - } 71 - } else { 72 - div { class: "loading", "Loading..." } 73 } 74 } 75 }
··· 29 book_title: ReadSignal<SmolStr>, 30 ) -> Element { 31 // Fetch full notebook metadata with SSR support 32 + let notebook_data = data::use_notebook(ident(), book_title())?; 33 34 // Fetch entries with SSR support 35 + let entries_resource = data::use_notebook_entries(ident(), book_title())?; 36 37 rsx! { 38 document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 39 40 + match (&*notebook_data.read(), &*entries_resource.read()) { 41 + (Some(data), Some(entries)) => { 42 + let (notebook_view, _) = data; 43 + let author_count = notebook_view.authors.len(); 44 45 + rsx! { 46 + div { class: "notebook-layout", 47 + aside { class: "notebook-sidebar", 48 + NotebookCover { 49 + notebook: notebook_view.clone(), 50 + title: book_title().to_string() 51 } 52 + } 53 54 + main { class: "notebook-main", 55 + div { class: "entries-list", 56 + for entry in entries { 57 + EntryCard { 58 + entry: entry.clone(), 59 + book_title: book_title(), 60 + author_count 61 } 62 } 63 } 64 } 65 } 66 + } 67 + }, 68 + _ => rsx! { div { class: "loading", "Loading..." } } 69 } 70 } 71 }
+17 -2615
crates/weaver-app/src/views/record.rs
··· 1 use crate::Route; 2 use crate::auth::AuthState; 3 - use crate::components::accordion::{Accordion, AccordionContent, AccordionItem, AccordionTrigger}; 4 - use crate::components::dialog::{DialogContent, DialogDescription, DialogRoot, DialogTitle}; 5 - use crate::fetch::CachedFetcher; 6 - use dioxus::prelude::Event; 7 - use dioxus::{CapturedError, prelude::*}; 8 - use humansize::format_size; 9 - use jacquard::api::com_atproto::repo::get_record::GetRecordOutput; 10 - use jacquard::bytes::Bytes; 11 - use jacquard::client::AgentError; 12 use jacquard::common::to_data; 13 use jacquard::smol_str::ToSmolStr; 14 - use jacquard::types::LexiconStringType; 15 - use jacquard::types::string::AtprotoStr; 16 - use jacquard::{atproto, prelude::*}; 17 use jacquard::{ 18 client::AgentSessionExt, 19 - common::{Data, IntoStatic}, 20 identity::lexicon_resolver::LexiconSchemaResolver, 21 - types::{aturi::AtUri, cid::Cid, ident::AtIdentifier, string::Nsid}, 22 }; 23 use jacquard_lexicon::lexicon::LexiconDoc; 24 - use jacquard_lexicon::validation::{ 25 - ConstraintError, StructuralError, ValidationError, ValidationPath, ValidationResult, 26 - }; 27 - use mime_sniffer::MimeTypeSniffer; 28 - use weaver_api::com_atproto::repo::{ 29 - create_record::CreateRecord, delete_record::DeleteRecord, put_record::PutRecord, 30 - }; 31 - use weaver_renderer::{code_pretty::highlight_code, css::generate_default_css}; 32 - 33 - #[derive(Clone, Copy, PartialEq)] 34 - enum ViewMode { 35 - Pretty, 36 - Json, 37 - Schema, 38 - } 39 40 #[component] 41 pub fn RecordIndex() -> Element { ··· 78 } 79 80 #[component] 81 pub fn RecordView(uri: ReadSignal<Vec<String>>) -> Element { 82 - let fetcher = use_context::<CachedFetcher>(); 83 info!("Uri:{:?}", uri().join("/")); 84 let at_uri = AtUri::new_owned(&*uri.read().join("/")); 85 if at_uri.is_err() { ··· 109 110 // Helper to recursively resolve a schema and its refs 111 fn resolve_schema_with_refs<'a>( 112 - fetcher: &'a CachedFetcher, 113 type_str: &'a str, 114 validator: &'a jacquard_lexicon::validation::SchemaValidator, 115 resolved: &'a mut std::collections::HashSet<String>, ··· 278 rsx! {} 279 } 280 } 281 - 282 - #[component] 283 - fn PrettyRecordView( 284 - record: Data<'static>, 285 - uri: AtUri<'static>, 286 - schema: ReadSignal<Option<LexiconDoc<'static>>>, 287 - ) -> Element { 288 - let did = uri.authority().to_string(); 289 - let root_data = use_signal(|| record.clone()); 290 - 291 - // Validate the record and provide via context - only after schema is loaded 292 - let mut validation_result = use_signal(|| None); 293 - use_effect(move || { 294 - // Wait for schema to be loaded 295 - if schema().is_some() { 296 - if let Some(nsid_str) = root_data.read().type_discriminator() { 297 - let validator = jacquard_lexicon::validation::SchemaValidator::global(); 298 - let result = validator.validate_by_nsid(nsid_str, &*root_data.read()); 299 - validation_result.set(Some(result)); 300 - } 301 - } 302 - }); 303 - use_context_provider(|| validation_result); 304 - 305 - rsx! { 306 - div { 307 - class: "pretty-record", 308 - DataView { data: record, root_data, path: String::new(), did } 309 - } 310 - } 311 - } 312 - 313 - #[component] 314 - fn SchemaView(schema: ReadSignal<Option<LexiconDoc<'static>>>) -> Element { 315 - if let Some(schema_doc) = schema() { 316 - // Convert LexiconDoc to Data for display 317 - let schema_data = use_memo(move || to_data(&schema_doc).ok().map(|d| d.into_static())); 318 - 319 - if let Some(data) = schema_data() { 320 - let root_data = use_signal(|| data.clone()); 321 - rsx! { 322 - div { 323 - class: "pretty-record", 324 - DataView { data: data, root_data, path: String::new(), did: String::new() } 325 - } 326 - } 327 - } else { 328 - rsx! { 329 - div { class: "schema-error", "Failed to convert schema to displayable format" } 330 - } 331 - } 332 - } else { 333 - rsx! { 334 - div { class: "schema-loading", "Loading schema..." } 335 - } 336 - } 337 - } 338 - fn get_hex_rep(byte_array: &mut [u8]) -> String { 339 - let build_string_vec: Vec<String> = byte_array 340 - .chunks(2) 341 - .enumerate() 342 - .map(|(i, c)| { 343 - let sep = if i % 16 == 0 && i > 0 { 344 - "\n" 345 - } else if i == 0 { 346 - "" 347 - } else { 348 - " " 349 - }; 350 - if c.len() == 2 { 351 - format!("{}{:02x}{:02x}", sep, c[0], c[1]) 352 - } else { 353 - format!("{}{:02x}", sep, c[0]) 354 - } 355 - }) 356 - .collect(); 357 - build_string_vec.join("") 358 - } 359 - 360 - #[component] 361 - fn PathLabel(path: String) -> Element { 362 - if path.is_empty() { 363 - return rsx! {}; 364 - } 365 - 366 - // Find the last separator 367 - let last_sep = path.rfind(|c| c == '.'); 368 - 369 - if let Some(idx) = last_sep { 370 - let prefix = &path[..idx + 1]; 371 - let final_segment = &path[idx + 1..]; 372 - rsx! { 373 - span { class: "field-label", 374 - span { class: "path-prefix", "{prefix}" } 375 - span { class: "path-final", "{final_segment}" } 376 - } 377 - } 378 - } else { 379 - rsx! { 380 - span { class: "field-label","{path}" } 381 - } 382 - } 383 - } 384 - 385 - /// Get expected string format from schema by navigating path 386 - fn get_expected_string_format( 387 - root_data: &Data<'_>, 388 - current_path: &str, 389 - ) -> Option<jacquard_lexicon::lexicon::LexStringFormat> { 390 - use jacquard_lexicon::lexicon::*; 391 - 392 - // Get root type discriminator 393 - let root_nsid = root_data.type_discriminator()?; 394 - 395 - // Look up schema in global registry 396 - let validator = jacquard_lexicon::validation::SchemaValidator::global(); 397 - let registry = validator.registry(); 398 - let schema = registry.get(root_nsid)?; 399 - 400 - // Navigate to the property at this path 401 - let segments: Vec<&str> = if current_path.is_empty() { 402 - vec![] 403 - } else { 404 - current_path.split('.').collect() 405 - }; 406 - 407 - // Start with the record's main object definition 408 - let main_obj = match schema.defs.get("main")? { 409 - LexUserType::Record(rec) => match &rec.record { 410 - LexRecordRecord::Object(obj) => obj, 411 - }, 412 - _ => return None, 413 - }; 414 - 415 - // Track current position in schema 416 - enum SchemaType<'a> { 417 - ObjectProp(&'a LexObjectProperty<'a>), 418 - ArrayItem(&'a LexArrayItem<'a>), 419 - } 420 - 421 - let mut current_type: Option<SchemaType> = None; 422 - let mut current_obj = Some(main_obj); 423 - 424 - for segment in segments { 425 - // Handle array indices - strip them to get field name 426 - let field_name = segment.trim_end_matches(|c: char| c.is_numeric() || c == '[' || c == ']'); 427 - 428 - if field_name.is_empty() { 429 - continue; // Pure array index like [0], skip 430 - } 431 - 432 - if let Some(obj) = current_obj.take() { 433 - if let Some(prop) = obj.properties.get(field_name) { 434 - current_type = Some(SchemaType::ObjectProp(prop)); 435 - } 436 - } 437 - 438 - // Process current type 439 - match current_type { 440 - Some(SchemaType::ObjectProp(LexObjectProperty::Array(arr))) => { 441 - // Array - unwrap to item type 442 - current_type = Some(SchemaType::ArrayItem(&arr.items)); 443 - } 444 - Some(SchemaType::ObjectProp(LexObjectProperty::Object(obj))) => { 445 - // Nested object - descend into it 446 - current_obj = Some(obj); 447 - current_type = None; 448 - } 449 - Some(SchemaType::ArrayItem(LexArrayItem::Object(obj))) => { 450 - // Array of objects - descend into object 451 - current_obj = Some(obj); 452 - current_type = None; 453 - } 454 - _ => {} 455 - } 456 - } 457 - 458 - // Check if final type is a string with format 459 - match current_type? { 460 - SchemaType::ObjectProp(LexObjectProperty::String(lex_string)) => lex_string.format, 461 - SchemaType::ArrayItem(LexArrayItem::String(lex_string)) => lex_string.format, 462 - _ => None, 463 - } 464 - } 465 - 466 - #[component] 467 - fn DataView( 468 - data: Data<'static>, 469 - root_data: ReadSignal<Data<'static>>, 470 - path: String, 471 - did: String, 472 - ) -> Element { 473 - // Try to get validation result from context and get errors exactly at this path 474 - let validation_result = try_use_context::<Signal<Option<ValidationResult>>>(); 475 - 476 - let errors = if let Some(vr_signal) = validation_result { 477 - get_errors_at_exact_path(&*vr_signal.read(), &path) 478 - } else { 479 - Vec::new() 480 - }; 481 - 482 - let has_errors = !errors.is_empty(); 483 - 484 - match &data { 485 - Data::Null => rsx! { 486 - div { class: if has_errors { "record-field field-error" } else { "record-field" }, 487 - PathLabel { path: path.clone() } 488 - span { class: "field-value muted", "null" } 489 - if has_errors { 490 - for error in &errors { 491 - div { class: "field-error-message", "{error}" } 492 - } 493 - } 494 - } 495 - }, 496 - Data::Boolean(b) => rsx! { 497 - div { class: if has_errors { "record-field field-error" } else { "record-field" }, 498 - PathLabel { path: path.clone() } 499 - span { class: "field-value", "{b}" } 500 - if has_errors { 501 - for error in &errors { 502 - div { class: "field-error-message", "{error}" } 503 - } 504 - } 505 - } 506 - }, 507 - Data::Integer(i) => rsx! { 508 - div { class: if has_errors { "record-field field-error" } else { "record-field" }, 509 - PathLabel { path: path.clone() } 510 - span { class: "field-value", "{i}" } 511 - if has_errors { 512 - for error in &errors { 513 - div { class: "field-error-message", "{error}" } 514 - } 515 - } 516 - } 517 - }, 518 - Data::String(s) => { 519 - use jacquard::types::string::AtprotoStr; 520 - use jacquard_lexicon::lexicon::LexStringFormat; 521 - 522 - // Get expected format from schema 523 - let expected_format = get_expected_string_format(&*root_data.read(), &path); 524 - 525 - // Get actual type from data 526 - let actual_type_label = match s { 527 - AtprotoStr::Datetime(_) => "datetime", 528 - AtprotoStr::Language(_) => "language", 529 - AtprotoStr::Tid(_) => "tid", 530 - AtprotoStr::Nsid(_) => "nsid", 531 - AtprotoStr::Did(_) => "did", 532 - AtprotoStr::Handle(_) => "handle", 533 - AtprotoStr::AtIdentifier(_) => "at-identifier", 534 - AtprotoStr::AtUri(_) => "at-uri", 535 - AtprotoStr::Uri(_) => "uri", 536 - AtprotoStr::Cid(_) => "cid", 537 - AtprotoStr::RecordKey(_) => "record-key", 538 - AtprotoStr::String(_) => "string", 539 - }; 540 - 541 - // Prefer schema format if available, otherwise use actual type 542 - let type_label = if let Some(fmt) = expected_format { 543 - match fmt { 544 - LexStringFormat::Datetime => "datetime", 545 - LexStringFormat::Uri => "uri", 546 - LexStringFormat::AtUri => "at-uri", 547 - LexStringFormat::Did => "did", 548 - LexStringFormat::Handle => "handle", 549 - LexStringFormat::AtIdentifier => "at-identifier", 550 - LexStringFormat::Nsid => "nsid", 551 - LexStringFormat::Cid => "cid", 552 - LexStringFormat::Language => "language", 553 - LexStringFormat::Tid => "tid", 554 - LexStringFormat::RecordKey => "record-key", 555 - } 556 - } else { 557 - actual_type_label 558 - }; 559 - 560 - rsx! { 561 - div { class: if has_errors { "record-field field-error" } else { "record-field" }, 562 - PathLabel { path: path.clone() } 563 - span { class: "field-value", 564 - 565 - HighlightedString { string_type: s.clone() } 566 - if type_label != "string" { 567 - span { class: "string-type-tag", " [{type_label}]" } 568 - } 569 - } 570 - if has_errors { 571 - for error in &errors { 572 - div { class: "field-error-message", "{error}" } 573 - } 574 - } 575 - } 576 - } 577 - } 578 - Data::Bytes(b) => { 579 - let hex_string = get_hex_rep(&mut b.to_vec()); 580 - let byte_size = if b.len() > 128 { 581 - format_size(b.len(), humansize::BINARY) 582 - } else { 583 - format!("{} bytes", b.len()) 584 - }; 585 - rsx! { 586 - div { class: if has_errors { "record-field field-error" } else { "record-field" }, 587 - PathLabel { path: path.clone() } 588 - pre { class: "field-value bytes", "{hex_string} [{byte_size}]" } 589 - if has_errors { 590 - for error in &errors { 591 - div { class: "field-error-message", "{error}" } 592 - } 593 - } 594 - } 595 - } 596 - } 597 - Data::CidLink(cid) => rsx! { 598 - div { class: if has_errors { "record-field field-error" } else { "record-field" }, 599 - span { class: "field-label", "{path}" } 600 - span { class: "field-value", "{cid}" } 601 - if has_errors { 602 - for error in &errors { 603 - div { class: "field-error-message", "{error}" } 604 - } 605 - } 606 - } 607 - }, 608 - Data::Array(arr) => { 609 - let label = path.split('.').last().unwrap_or(&path); 610 - rsx! { 611 - div { class: "record-section", 612 - Accordion { 613 - id: "array-{path}", 614 - collapsible: true, 615 - AccordionItem { 616 - default_open: true, 617 - index: 0, 618 - AccordionTrigger { 619 - div { class: "section-label", "{label}" span { class: "array-len", "[{arr.len()}]" } } 620 - } 621 - AccordionContent { 622 - if has_errors { 623 - for error in &errors { 624 - div { class: "field-error-message", "{error}" } 625 - } 626 - } 627 - div { class: "section-content", 628 - for (idx, item) in arr.iter().enumerate() { 629 - { 630 - let item_path = format!("{}[{}]", label, idx); 631 - let is_object = matches!(item, Data::Object(_)); 632 - 633 - if is_object { 634 - rsx! { 635 - div { 636 - class: "array-item", 637 - div { class: "record-section", 638 - div { class: "section-label", "{item_path}" } 639 - div { class: "section-content", 640 - DataView { 641 - data: item.clone(), 642 - root_data, 643 - path: item_path.clone(), 644 - did: did.clone() 645 - } 646 - } 647 - } 648 - } 649 - } 650 - } else { 651 - rsx! { 652 - div { 653 - class: "array-item", 654 - DataView { 655 - data: item.clone(), 656 - root_data, 657 - path: item_path, 658 - did: did.clone() 659 - } 660 - } 661 - } 662 - } 663 - } 664 - } 665 - } 666 - } 667 - } 668 - } 669 - } 670 - } 671 - } 672 - Data::Object(obj) => { 673 - let is_root = path.is_empty(); 674 - let is_array_item = path.split('.').last().unwrap_or(&path).contains('['); 675 - 676 - if is_root || is_array_item { 677 - // Root object or array item: just render children (array items already wrapped) 678 - rsx! { 679 - div { class: if !is_root { "record-section" } else {""}, 680 - if has_errors { 681 - for error in &errors { 682 - div { class: "field-error-message", "{error}" } 683 - } 684 - } 685 - for (key, value) in obj.iter() { 686 - { 687 - let new_path = if is_root { 688 - key.to_string() 689 - } else { 690 - format!("{}.{}", path, key) 691 - }; 692 - let did_clone = did.clone(); 693 - rsx! { 694 - DataView { data: value.clone(), root_data, path: new_path, did: did_clone } 695 - } 696 - } 697 - } 698 - } 699 - } 700 - } else { 701 - // Nested object (not array item): wrap in section 702 - let label = path.split('.').last().unwrap_or(&path); 703 - rsx! { 704 - div { class: "record-section", 705 - Accordion { 706 - id: "object-{path}", 707 - collapsible: true, 708 - AccordionItem { 709 - default_open: true, 710 - index: 0, 711 - AccordionTrigger { 712 - div { class: "section-label", "{label}" } 713 - } 714 - AccordionContent { 715 - if has_errors { 716 - for error in &errors { 717 - div { class: "field-error-message", "{error}" } 718 - } 719 - } 720 - div { class: "section-content", 721 - for (key, value) in obj.iter() { 722 - { 723 - let new_path = format!("{}.{}", path, key); 724 - let did_clone = did.clone(); 725 - rsx! { 726 - DataView { data: value.clone(), root_data, path: new_path, did: did_clone } 727 - } 728 - } 729 - } 730 - } 731 - } 732 - } 733 - } 734 - } 735 - } 736 - } 737 - } 738 - Data::Blob(blob) => { 739 - let is_image = blob.mime_type.starts_with("image/"); 740 - let format = blob.mime_type.strip_prefix("image/").unwrap_or("jpeg"); 741 - let image_url = format!( 742 - "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@{}", 743 - did, 744 - blob.cid(), 745 - format 746 - ); 747 - 748 - let blob_size = format_size(blob.size, humansize::BINARY); 749 - rsx! { 750 - div { class: "record-field", 751 - span { class: "field-label", "{path}" } 752 - span { class: "field-value mime", "[mimeType: {blob.mime_type}, size: {blob_size}]" } 753 - if is_image { 754 - img { 755 - src: "{image_url}", 756 - alt: "Blob image", 757 - class: "blob-image", 758 - } 759 - } 760 - } 761 - } 762 - } 763 - } 764 - } 765 - 766 - #[component] 767 - fn HighlightedUri(uri: AtUri<'static>) -> Element { 768 - let s = uri.as_str(); 769 - let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, s); 770 - 771 - if let Some(rest) = s.strip_prefix("at://") { 772 - let parts: Vec<&str> = rest.splitn(3, '/').collect(); 773 - return rsx! { 774 - a { 775 - href: link, 776 - class: "uri-link", 777 - span { class: "string-at-uri", 778 - span { class: "aturi-scheme", "at://" } 779 - span { class: "aturi-authority", "{uri.authority()}" } 780 - 781 - if parts.len() > 1 { 782 - span { class: "aturi-slash", "/" } 783 - if let Some(collection) = uri.collection() { 784 - span { class: "aturi-collection", "{collection.as_ref()}" } 785 - } 786 - } 787 - if parts.len() > 2 { 788 - span { class: "aturi-slash", "/" } 789 - if let Some(rkey) = uri.rkey() { 790 - span { class: "aturi-rkey", "{rkey.as_ref()}" } 791 - } 792 - } 793 - } 794 - } 795 - }; 796 - } 797 - 798 - rsx! { a { class: "string-at-uri", href: s } } 799 - } 800 - 801 - #[component] 802 - fn HighlightedString(string_type: AtprotoStr<'static>) -> Element { 803 - use jacquard::types::string::AtprotoStr; 804 - 805 - match &string_type { 806 - AtprotoStr::Nsid(nsid) => { 807 - let parts: Vec<&str> = nsid.as_str().split('.').collect(); 808 - rsx! { 809 - span { class: "string-nsid", 810 - for (i, part) in parts.iter().enumerate() { 811 - span { class: "nsid-segment nsid-segment-{i % 3}", "{part}" } 812 - if i < parts.len() - 1 { 813 - span { class: "nsid-dot", "." } 814 - } 815 - } 816 - } 817 - } 818 - } 819 - AtprotoStr::Did(did) => { 820 - let s = did.as_str(); 821 - if let Some(rest) = s.strip_prefix("did:") { 822 - if let Some((method, identifier)) = rest.split_once(':') { 823 - return rsx! { 824 - span { class: "string-did", 825 - span { class: "did-scheme", "did:" } 826 - span { class: "did-method", "{method}" } 827 - span { class: "did-separator", ":" } 828 - span { class: "did-identifier", "{identifier}" } 829 - } 830 - }; 831 - } 832 - } 833 - rsx! { span { class: "string-did", "{s}" } } 834 - } 835 - AtprotoStr::Handle(handle) => { 836 - let parts: Vec<&str> = handle.as_str().split('.').collect(); 837 - rsx! { 838 - span { class: "string-handle", 839 - for (i, part) in parts.iter().enumerate() { 840 - span { class: "handle-segment handle-segment-{i % 2}", "{part}" } 841 - if i < parts.len() - 1 { 842 - span { class: "handle-dot", "." } 843 - } 844 - } 845 - } 846 - } 847 - } 848 - AtprotoStr::AtUri(uri) => { 849 - rsx! { 850 - HighlightedUri { uri: uri.clone().into_static() } 851 - } 852 - } 853 - AtprotoStr::Uri(uri) => { 854 - let s = uri.as_str(); 855 - if let Ok(at_uri) = AtUri::new(s) { 856 - return rsx! { 857 - HighlightedUri { uri: at_uri.into_static() } 858 - }; 859 - } 860 - 861 - // Try to parse scheme 862 - if let Some((scheme, rest)) = s.split_once("://") { 863 - // Split authority and path 864 - let (authority, path) = if let Some(idx) = rest.find('/') { 865 - (&rest[..idx], &rest[idx..]) 866 - } else { 867 - (rest, "") 868 - }; 869 - 870 - return rsx! { 871 - a { 872 - href: "{s}", 873 - target: "_blank", 874 - rel: "noopener noreferrer", 875 - class: "uri-link", 876 - span { class: "string-uri", 877 - span { class: "uri-scheme", "{scheme}" } 878 - span { class: "uri-separator", "://" } 879 - span { class: "uri-authority", "{authority}" } 880 - if !path.is_empty() { 881 - span { class: "uri-path", "{path}" } 882 - } 883 - } 884 - } 885 - }; 886 - } 887 - 888 - rsx! { span { class: "string-uri", "{s}" } } 889 - } 890 - _ => { 891 - let value = string_type.as_str(); 892 - rsx! { "{value}" } 893 - } 894 - } 895 - } 896 - 897 - #[derive(Props, Clone, PartialEq)] 898 - pub struct CodeViewProps { 899 - #[props(default)] 900 - id: Signal<String>, 901 - #[props(default)] 902 - class: Signal<String>, 903 - code: ReadSignal<String>, 904 - lang: Option<String>, 905 - } 906 - 907 - #[component] 908 - fn JsonEditor( 909 - data: Signal<Data<'static>>, 910 - nsid: ReadSignal<Option<String>>, 911 - schema: ReadSignal<Option<LexiconDoc<'static>>>, 912 - ) -> Element { 913 - let mut json_text = 914 - use_signal(|| serde_json::to_string_pretty(&*data.read()).unwrap_or_default()); 915 - 916 - let height = use_memo(move || { 917 - let line_count = json_text().lines().count(); 918 - let min_lines = 10; 919 - let lines = line_count.max(min_lines); 920 - // line-height is 1.5, font-size is 0.9rem (approx 14.4px), so each line is ~21.6px 921 - // Add padding (1rem top + 1rem bottom = 2rem = 32px) 922 - format!("{}px", lines * 22 + 32) 923 - }); 924 - 925 - let validation = use_resource(move || { 926 - let text = json_text(); 927 - let nsid_val = nsid(); 928 - let _ = schema(); // Track schema changes 929 - 930 - async move { 931 - // Only validate if we have an NSID 932 - let nsid_str = nsid_val?; 933 - 934 - // Parse JSON to Data 935 - let parsed = match serde_json::from_str::<Data>(&text) { 936 - Ok(val) => val.into_static(), 937 - Err(e) => { 938 - return Some((None, Some(e.to_string()))); 939 - } 940 - }; 941 - 942 - // Use global validator (schema already registered) 943 - let validator = jacquard_lexicon::validation::SchemaValidator::global(); 944 - let result = validator.validate_by_nsid(&nsid_str, &parsed); 945 - 946 - Some((Some(result), None)) 947 - } 948 - }); 949 - 950 - rsx! { 951 - div { class: "json-editor", 952 - textarea { 953 - class: "json-textarea", 954 - style: "height: {height};", 955 - value: "{json_text}", 956 - oninput: move |evt| { 957 - json_text.set(evt.value()); 958 - // Update data signal on successful parse 959 - if let Ok(parsed) = serde_json::from_str::<Data>(&evt.value()) { 960 - data.set(parsed.into_static()); 961 - } 962 - }, 963 - } 964 - 965 - ValidationPanel { 966 - validation: validation, 967 - } 968 - } 969 - } 970 - } 971 - 972 - #[component] 973 - fn ActionButtons( 974 - on_update: EventHandler<()>, 975 - on_save_new: EventHandler<()>, 976 - on_replace: EventHandler<()>, 977 - on_delete: EventHandler<()>, 978 - on_cancel: EventHandler<()>, 979 - ) -> Element { 980 - let mut show_save_dropdown = use_signal(|| false); 981 - let mut show_replace_warning = use_signal(|| false); 982 - let mut show_delete_confirm = use_signal(|| false); 983 - 984 - rsx! { 985 - div { class: "action-buttons-group", 986 - button { 987 - class: "tab-button action-button", 988 - onclick: move |_| on_update.call(()), 989 - "Update" 990 - } 991 - 992 - div { class: "dropdown-wrapper", 993 - button { 994 - class: "tab-button action-button", 995 - onclick: move |_| show_save_dropdown.toggle(), 996 - "Save as New ▼" 997 - } 998 - if show_save_dropdown() { 999 - div { class: "dropdown-menu", 1000 - button { 1001 - onclick: move |_| { 1002 - show_save_dropdown.set(false); 1003 - on_save_new.call(()); 1004 - }, 1005 - "Save as New" 1006 - } 1007 - button { 1008 - onclick: move |_| { 1009 - show_save_dropdown.set(false); 1010 - show_replace_warning.set(true); 1011 - }, 1012 - "Replace" 1013 - } 1014 - } 1015 - } 1016 - } 1017 - 1018 - if show_replace_warning() { 1019 - div { class: "inline-warning", 1020 - "⚠️ This will delete the current record and create a new one with a different rkey. " 1021 - button { 1022 - onclick: move |_| { 1023 - show_replace_warning.set(false); 1024 - on_replace.call(()); 1025 - }, 1026 - "Yes" 1027 - } 1028 - button { 1029 - onclick: move |_| show_replace_warning.set(false), 1030 - "No" 1031 - } 1032 - } 1033 - } 1034 - 1035 - button { 1036 - class: "tab-button action-button action-button-danger", 1037 - onclick: move |_| show_delete_confirm.set(true), 1038 - "Delete" 1039 - } 1040 - 1041 - DialogRoot { 1042 - open: Some(show_delete_confirm()), 1043 - on_open_change: move |open: bool| { 1044 - show_delete_confirm.set(open); 1045 - }, 1046 - DialogContent { 1047 - DialogTitle { "Delete Record?" } 1048 - DialogDescription { 1049 - "This action cannot be undone." 1050 - } 1051 - div { class: "dialog-actions", 1052 - button { 1053 - onclick: move |_| { 1054 - show_delete_confirm.set(false); 1055 - on_delete.call(()); 1056 - }, 1057 - "Delete" 1058 - } 1059 - button { 1060 - onclick: move |_| show_delete_confirm.set(false), 1061 - "Cancel" 1062 - } 1063 - } 1064 - } 1065 - } 1066 - 1067 - button { 1068 - class: "tab-button action-button", 1069 - onclick: move |_| on_cancel.call(()), 1070 - "Cancel" 1071 - } 1072 - } 1073 - } 1074 - } 1075 - 1076 - #[component] 1077 - fn ValidationPanel( 1078 - validation: Resource<Option<(Option<ValidationResult>, Option<String>)>>, 1079 - ) -> Element { 1080 - rsx! { 1081 - div { class: "validation-panel", 1082 - if let Some(Some((result_opt, parse_error_opt))) = validation.read().as_ref() { 1083 - if let Some(parse_err) = parse_error_opt { 1084 - div { class: "parse-error", 1085 - "❌ Invalid JSON: {parse_err}" 1086 - } 1087 - } 1088 - 1089 - if let Some(result) = result_opt { 1090 - // Structural validity 1091 - if result.is_structurally_valid() { 1092 - div { class: "validation-success", "✓ Structurally valid" } 1093 - } else { 1094 - div { class: "parse-error", "❌ Structurally invalid" } 1095 - } 1096 - 1097 - // Overall validity 1098 - if result.is_valid() { 1099 - div { class: "validation-success", "✓ Fully valid" } 1100 - } else { 1101 - div { class: "validation-warning", "⚠ Has errors" } 1102 - } 1103 - 1104 - // Show errors if any 1105 - if !result.is_valid() { 1106 - div { class: "validation-errors", 1107 - h4 { "Validation Errors:" } 1108 - for error in result.all_errors() { 1109 - div { class: "error", "{error}" } 1110 - } 1111 - } 1112 - } 1113 - } 1114 - } else { 1115 - div { "Validating..." } 1116 - } 1117 - } 1118 - } 1119 - } 1120 - 1121 - // ============================================================================ 1122 - // Validation Helper Functions 1123 - // ============================================================================ 1124 - 1125 - /// Parse UI path into segments (fields and indices only) 1126 - fn parse_ui_path(ui_path: &str) -> Vec<UiPathSegment> { 1127 - if ui_path.is_empty() { 1128 - return vec![]; 1129 - } 1130 - 1131 - let mut segments = Vec::new(); 1132 - let mut current = String::new(); 1133 - 1134 - for ch in ui_path.chars() { 1135 - match ch { 1136 - '.' => { 1137 - if !current.is_empty() { 1138 - segments.push(UiPathSegment::Field(current.clone())); 1139 - current.clear(); 1140 - } 1141 - } 1142 - '[' => { 1143 - if !current.is_empty() { 1144 - segments.push(UiPathSegment::Field(current.clone())); 1145 - current.clear(); 1146 - } 1147 - } 1148 - ']' => { 1149 - if !current.is_empty() { 1150 - if let Ok(idx) = current.parse::<usize>() { 1151 - segments.push(UiPathSegment::Index(idx)); 1152 - } 1153 - current.clear(); 1154 - } 1155 - } 1156 - c => current.push(c), 1157 - } 1158 - } 1159 - 1160 - if !current.is_empty() { 1161 - segments.push(UiPathSegment::Field(current)); 1162 - } 1163 - 1164 - segments 1165 - } 1166 - 1167 - #[derive(Debug, PartialEq)] 1168 - enum UiPathSegment { 1169 - Field(String), 1170 - Index(usize), 1171 - } 1172 - 1173 - /// Get all validation errors at exactly this path (not children) 1174 - fn get_errors_at_exact_path( 1175 - validation_result: &Option<ValidationResult>, 1176 - ui_path: &str, 1177 - ) -> Vec<String> { 1178 - use jacquard_lexicon::validation::PathSegment; 1179 - 1180 - if let Some(result) = validation_result { 1181 - let ui_segments = parse_ui_path(ui_path); 1182 - 1183 - result 1184 - .all_errors() 1185 - .filter_map(|err| { 1186 - let validation_path = match &err { 1187 - ValidationError::Structural(s) => match s { 1188 - StructuralError::TypeMismatch { path, .. } => Some(path), 1189 - StructuralError::MissingRequiredField { path, .. } => Some(path), 1190 - StructuralError::MissingUnionDiscriminator { path } => Some(path), 1191 - StructuralError::UnionNoMatch { path, .. } => Some(path), 1192 - StructuralError::UnresolvedRef { path, .. } => Some(path), 1193 - StructuralError::RefCycle { path, .. } => Some(path), 1194 - StructuralError::MaxDepthExceeded { path, .. } => Some(path), 1195 - }, 1196 - ValidationError::Constraint(c) => match c { 1197 - ConstraintError::MaxLength { path, .. } => Some(path), 1198 - ConstraintError::MaxGraphemes { path, .. } => Some(path), 1199 - ConstraintError::MinLength { path, .. } => Some(path), 1200 - ConstraintError::MinGraphemes { path, .. } => Some(path), 1201 - ConstraintError::Maximum { path, .. } => Some(path), 1202 - ConstraintError::Minimum { path, .. } => Some(path), 1203 - }, 1204 - }; 1205 - 1206 - if let Some(path) = validation_path { 1207 - // Convert validation path to UI segments 1208 - let validation_ui_segments: Vec<_> = path 1209 - .segments() 1210 - .iter() 1211 - .filter_map(|seg| match seg { 1212 - PathSegment::Field(name) => { 1213 - Some(UiPathSegment::Field(name.to_string())) 1214 - } 1215 - PathSegment::Index(idx) => Some(UiPathSegment::Index(*idx)), 1216 - PathSegment::UnionVariant(_) => None, 1217 - }) 1218 - .collect(); 1219 - 1220 - // Exact match only 1221 - if validation_ui_segments == ui_segments { 1222 - return Some(err.to_string()); 1223 - } 1224 - } 1225 - None 1226 - }) 1227 - .collect() 1228 - } else { 1229 - Vec::new() 1230 - } 1231 - } 1232 - 1233 - // ============================================================================ 1234 - // Pretty Editor: Helper Functions 1235 - // ============================================================================ 1236 - 1237 - /// Infer Data type from text input 1238 - fn infer_data_from_text(text: &str) -> Result<Data<'static>, String> { 1239 - let trimmed = text.trim(); 1240 - 1241 - if trimmed == "true" || trimmed == "false" { 1242 - Ok(Data::Boolean(trimmed == "true")) 1243 - } else if trimmed == "{}" { 1244 - use jacquard::types::value::Object; 1245 - use std::collections::BTreeMap; 1246 - Ok(Data::Object(Object(BTreeMap::new()))) 1247 - } else if trimmed == "[]" { 1248 - use jacquard::types::value::Array; 1249 - Ok(Data::Array(Array(Vec::new()))) 1250 - } else if trimmed == "null" { 1251 - Ok(Data::Null) 1252 - } else if let Ok(num) = trimmed.parse::<i64>() { 1253 - Ok(Data::Integer(num)) 1254 - } else { 1255 - // Smart string parsing 1256 - use jacquard::types::value::parsing; 1257 - Ok(Data::String(parsing::parse_string(trimmed).into_static())) 1258 - } 1259 - } 1260 - 1261 - /// Parse text as specific AtprotoStr type, preserving type information 1262 - fn try_parse_as_type( 1263 - text: &str, 1264 - string_type: LexiconStringType, 1265 - ) -> Result<AtprotoStr<'static>, String> { 1266 - use jacquard::types::string::*; 1267 - use std::str::FromStr; 1268 - 1269 - match string_type { 1270 - LexiconStringType::Datetime => Datetime::from_str(text) 1271 - .map(AtprotoStr::Datetime) 1272 - .map_err(|e| format!("Invalid datetime: {}", e)), 1273 - LexiconStringType::Did => Did::new(text) 1274 - .map(|v| AtprotoStr::Did(v.into_static())) 1275 - .map_err(|e| format!("Invalid DID: {}", e)), 1276 - LexiconStringType::Handle => Handle::new(text) 1277 - .map(|v| AtprotoStr::Handle(v.into_static())) 1278 - .map_err(|e| format!("Invalid handle: {}", e)), 1279 - LexiconStringType::AtUri => AtUri::new(text) 1280 - .map(|v| AtprotoStr::AtUri(v.into_static())) 1281 - .map_err(|e| format!("Invalid AT-URI: {}", e)), 1282 - LexiconStringType::AtIdentifier => AtIdentifier::new(text) 1283 - .map(|v| AtprotoStr::AtIdentifier(v.into_static())) 1284 - .map_err(|e| format!("Invalid identifier: {}", e)), 1285 - LexiconStringType::Nsid => Nsid::new(text) 1286 - .map(|v| AtprotoStr::Nsid(v.into_static())) 1287 - .map_err(|e| format!("Invalid NSID: {}", e)), 1288 - LexiconStringType::Tid => Tid::new(text) 1289 - .map(|v| AtprotoStr::Tid(v.into_static())) 1290 - .map_err(|e| format!("Invalid TID: {}", e)), 1291 - LexiconStringType::RecordKey => Rkey::new(text) 1292 - .map(|rk| AtprotoStr::RecordKey(RecordKey::from(rk))) 1293 - .map_err(|e| format!("Invalid record key: {}", e)), 1294 - LexiconStringType::Cid => Cid::new(text.as_bytes()) 1295 - .map(|v| AtprotoStr::Cid(v.into_static())) 1296 - .map_err(|_| "Invalid CID".to_string()), 1297 - LexiconStringType::Language => Language::new(text) 1298 - .map(AtprotoStr::Language) 1299 - .map_err(|e| format!("Invalid language: {}", e)), 1300 - LexiconStringType::Uri(_) => Uri::new(text) 1301 - .map(|u| AtprotoStr::Uri(u.into_static())) 1302 - .map_err(|e| format!("Invalid URI: {}", e)), 1303 - LexiconStringType::String => { 1304 - // Plain strings: use smart inference 1305 - use jacquard::types::value::parsing; 1306 - Ok(parsing::parse_string(text).into_static()) 1307 - } 1308 - } 1309 - } 1310 - 1311 - /// Create default value for new array item by cloning structure of existing items 1312 - fn create_array_item_default(arr: &jacquard::types::value::Array) -> Data<'static> { 1313 - if let Some(existing) = arr.0.first() { 1314 - clone_structure(existing) 1315 - } else { 1316 - // Empty array, default to null (user can change type) 1317 - Data::Null 1318 - } 1319 - } 1320 - 1321 - /// Clone structure of Data, setting sensible defaults for leaf values 1322 - fn clone_structure(data: &Data) -> Data<'static> { 1323 - use jacquard::types::string::*; 1324 - use jacquard::types::value::{Array, Object}; 1325 - use jacquard::types::{LexiconStringType, blob::*}; 1326 - use std::collections::BTreeMap; 1327 - 1328 - match data { 1329 - Data::Object(obj) => { 1330 - let mut new_obj = BTreeMap::new(); 1331 - for (key, value) in obj.0.iter() { 1332 - new_obj.insert(key.clone(), clone_structure(value)); 1333 - } 1334 - Data::Object(Object(new_obj)) 1335 - } 1336 - Data::Array(_) => Data::Array(Array(Vec::new())), 1337 - Data::String(s) => match s.string_type() { 1338 - LexiconStringType::Datetime => { 1339 - // Sensible default: now 1340 - Data::String(AtprotoStr::Datetime(Datetime::now())) 1341 - } 1342 - LexiconStringType::Tid => Data::String(AtprotoStr::Tid(Tid::now_0())), 1343 - _ => { 1344 - // Empty string, type inference will handle it 1345 - Data::String(AtprotoStr::String("".into())) 1346 - } 1347 - }, 1348 - Data::Integer(_) => Data::Integer(0), 1349 - Data::Boolean(_) => Data::Boolean(false), 1350 - Data::Blob(blob) => { 1351 - // Placeholder blob 1352 - Data::Blob( 1353 - Blob { 1354 - r#ref: CidLink::str( 1355 - "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1356 - ), 1357 - mime_type: blob.mime_type.clone(), 1358 - size: 0, 1359 - } 1360 - .into_static(), 1361 - ) 1362 - } 1363 - Data::CidLink(_) => Data::CidLink(Cid::str( 1364 - "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1365 - )), 1366 - Data::Bytes(_) => Data::Bytes(Bytes::new()), 1367 - Data::Null => Data::Null, 1368 - } 1369 - } 1370 - 1371 - // ============================================================================ 1372 - // Pretty Editor: Component Hierarchy 1373 - // ============================================================================ 1374 - 1375 - /// Main dispatcher - routes to specific field editors based on Data type 1376 - #[component] 1377 - fn EditableDataView( 1378 - root: Signal<Data<'static>>, 1379 - path: String, 1380 - did: String, 1381 - #[props(default)] remove_button: Option<Element>, 1382 - ) -> Element { 1383 - let path_for_memo = path.clone(); 1384 - let root_read = root.read(); 1385 - 1386 - match root_read 1387 - .get_at_path(&path_for_memo) 1388 - .map(|d| d.clone().into_static()) 1389 - { 1390 - Some(Data::Object(_)) => { 1391 - rsx! { EditableObjectField { root, path: path.clone(), did, remove_button } } 1392 - } 1393 - Some(Data::Array(_)) => rsx! { EditableArrayField { root, path: path.clone(), did } }, 1394 - Some(Data::String(_)) => { 1395 - rsx! { EditableStringField { root, path: path.clone(), remove_button } } 1396 - } 1397 - Some(Data::Integer(_)) => { 1398 - rsx! { EditableIntegerField { root, path: path.clone(), remove_button } } 1399 - } 1400 - Some(Data::Boolean(_)) => { 1401 - rsx! { EditableBooleanField { root, path: path.clone(), remove_button } } 1402 - } 1403 - Some(Data::Null) => rsx! { EditableNullField { root, path: path.clone(), remove_button } }, 1404 - Some(Data::Blob(_)) => { 1405 - rsx! { EditableBlobField { root, path: path.clone(), did, remove_button } } 1406 - } 1407 - Some(Data::Bytes(_)) => { 1408 - rsx! { EditableBytesField { root, path: path.clone(), remove_button } } 1409 - } 1410 - Some(Data::CidLink(_)) => { 1411 - rsx! { EditableCidLinkField { root, path: path.clone(), remove_button } } 1412 - } 1413 - 1414 - None => rsx! { div { class: "field-error", "❌ Path not found: {path}" } }, 1415 - } 1416 - } 1417 - 1418 - // ============================================================================ 1419 - // Primitive Field Editors 1420 - // ============================================================================ 1421 - 1422 - /// String field with type preservation 1423 - #[component] 1424 - fn EditableStringField( 1425 - root: Signal<Data<'static>>, 1426 - path: String, 1427 - #[props(default)] remove_button: Option<Element>, 1428 - ) -> Element { 1429 - use jacquard::types::LexiconStringType; 1430 - 1431 - let path_for_text = path.clone(); 1432 - let path_for_type = path.clone(); 1433 - 1434 - // Get current string value 1435 - let current_text = use_memo(move || { 1436 - root.read() 1437 - .get_at_path(&path_for_text) 1438 - .and_then(|d| d.as_str()) 1439 - .map(|s| s.to_string()) 1440 - .unwrap_or_default() 1441 - }); 1442 - 1443 - // Get string type (Copy, cheap to store) 1444 - let string_type = use_memo(move || { 1445 - root.read() 1446 - .get_at_path(&path_for_type) 1447 - .and_then(|d| match d { 1448 - Data::String(s) => Some(s.string_type()), 1449 - _ => None, 1450 - }) 1451 - .unwrap_or(LexiconStringType::String) 1452 - }); 1453 - 1454 - // Local state for invalid input 1455 - let mut input_text = use_signal(|| current_text()); 1456 - let mut parse_error = use_signal(|| None::<String>); 1457 - 1458 - // Sync input when current changes 1459 - use_effect(move || { 1460 - input_text.set(current_text()); 1461 - }); 1462 - 1463 - let path_for_mutation = path.clone(); 1464 - let handle_input = move |evt: Event<FormData>| { 1465 - let new_text = evt.value(); 1466 - input_text.set(new_text.clone()); 1467 - 1468 - match try_parse_as_type(&new_text, string_type()) { 1469 - Ok(new_atproto_str) => { 1470 - parse_error.set(None); 1471 - let mut new_data = root.read().clone(); 1472 - new_data.set_at_path(&path_for_mutation, Data::String(new_atproto_str)); 1473 - root.set(new_data); 1474 - } 1475 - Err(e) => { 1476 - parse_error.set(Some(e)); 1477 - } 1478 - } 1479 - }; 1480 - 1481 - let type_label = format!("{:?}", string_type()).to_lowercase(); 1482 - let is_plain_string = string_type() == LexiconStringType::String; 1483 - 1484 - // Dynamic width based on content length 1485 - let input_width = use_memo(move || { 1486 - let len = input_text().len(); 1487 - let min_width = match string_type() { 1488 - LexiconStringType::Cid => 60, 1489 - LexiconStringType::Nsid => 40, 1490 - LexiconStringType::Did => 50, 1491 - LexiconStringType::AtUri => 50, 1492 - _ => 20, 1493 - }; 1494 - format!("{}ch", len.max(min_width)) 1495 - }); 1496 - 1497 - rsx! { 1498 - div { class: "record-field", 1499 - div { class: "field-header", 1500 - PathLabel { path: path.clone() } 1501 - if type_label != "string" { 1502 - span { class: "string-type-tag", " [{type_label}]" } 1503 - } 1504 - {remove_button} 1505 - } 1506 - if is_plain_string { 1507 - textarea { 1508 - value: "{input_text}", 1509 - oninput: handle_input, 1510 - class: if parse_error().is_some() { "invalid" } else { "" }, 1511 - rows: "1", 1512 - } 1513 - } else { 1514 - input { 1515 - r#type: "text", 1516 - value: "{input_text}", 1517 - style: "width: {input_width}", 1518 - oninput: handle_input, 1519 - class: if parse_error().is_some() { "invalid" } else { "" }, 1520 - } 1521 - } 1522 - if let Some(err) = parse_error() { 1523 - span { class: "field-error", " ❌ {err}" } 1524 - } 1525 - } 1526 - } 1527 - } 1528 - 1529 - /// Integer field with validation 1530 - #[component] 1531 - fn EditableIntegerField( 1532 - root: Signal<Data<'static>>, 1533 - path: String, 1534 - #[props(default)] remove_button: Option<Element>, 1535 - ) -> Element { 1536 - let path_for_memo = path.clone(); 1537 - let current_value = use_memo(move || { 1538 - root.read() 1539 - .get_at_path(&path_for_memo) 1540 - .and_then(|d| d.as_integer()) 1541 - .unwrap_or(0) 1542 - }); 1543 - 1544 - let mut input_text = use_signal(|| current_value().to_string()); 1545 - let mut parse_error = use_signal(|| None::<String>); 1546 - 1547 - use_effect(move || { 1548 - input_text.set(current_value().to_string()); 1549 - }); 1550 - 1551 - let path_for_mutation = path.clone(); 1552 - 1553 - rsx! { 1554 - div { class: "record-field", 1555 - div { class: "field-header", 1556 - PathLabel { path: path.clone() } 1557 - {remove_button} 1558 - } 1559 - input { 1560 - r#type: "number", 1561 - value: "{input_text}", 1562 - oninput: move |evt| { 1563 - let text = evt.value(); 1564 - input_text.set(text.clone()); 1565 - 1566 - match text.parse::<i64>() { 1567 - Ok(num) => { 1568 - parse_error.set(None); 1569 - let mut data_edit = root.write_unchecked(); 1570 - data_edit.set_at_path(&path_for_mutation, Data::Integer(num)); 1571 - } 1572 - Err(_) => { 1573 - parse_error.set(Some("Must be a valid integer".to_string())); 1574 - } 1575 - } 1576 - } 1577 - } 1578 - if let Some(err) = parse_error() { 1579 - span { class: "field-error", " ❌ {err}" } 1580 - } 1581 - } 1582 - } 1583 - } 1584 - 1585 - /// Boolean field (toggle button) 1586 - #[component] 1587 - fn EditableBooleanField( 1588 - root: Signal<Data<'static>>, 1589 - path: String, 1590 - #[props(default)] remove_button: Option<Element>, 1591 - ) -> Element { 1592 - let path_for_memo = path.clone(); 1593 - let current_value = use_memo(move || { 1594 - root.read() 1595 - .get_at_path(&path_for_memo) 1596 - .and_then(|d| d.as_boolean()) 1597 - .unwrap_or(false) 1598 - }); 1599 - 1600 - let path_for_mutation = path.clone(); 1601 - rsx! { 1602 - div { class: "record-field", 1603 - div { class: "field-header", 1604 - PathLabel { path: path.clone() } 1605 - {remove_button} 1606 - } 1607 - button { 1608 - class: if current_value() { "boolean-toggle boolean-toggle-true" } else { "boolean-toggle boolean-toggle-false" }, 1609 - onclick: move |_| { 1610 - root.with_mut(|data| { 1611 - if let Some(target) = data.get_at_path_mut(path_for_mutation.as_str()) { 1612 - if let Some(bool_val) = target.as_boolean() { 1613 - *target = Data::Boolean(!bool_val); 1614 - } 1615 - } 1616 - }); 1617 - }, 1618 - "{current_value()}" 1619 - } 1620 - } 1621 - } 1622 - } 1623 - 1624 - /// Null field with type inference 1625 - #[component] 1626 - fn EditableNullField( 1627 - root: Signal<Data<'static>>, 1628 - path: String, 1629 - #[props(default)] remove_button: Option<Element>, 1630 - ) -> Element { 1631 - let mut input_text = use_signal(|| String::new()); 1632 - let mut parse_error = use_signal(|| None::<String>); 1633 - 1634 - let path_for_mutation = path.clone(); 1635 - rsx! { 1636 - div { class: "record-field", 1637 - div { class: "field-header", 1638 - PathLabel { path: path.clone() } 1639 - span { class: "field-value muted", "null" } 1640 - {remove_button} 1641 - } 1642 - input { 1643 - r#type: "text", 1644 - placeholder: "Enter value (or {{}}, [], true, 123)...", 1645 - value: "{input_text}", 1646 - oninput: move |evt| { 1647 - input_text.set(evt.value()); 1648 - }, 1649 - onkeydown: move |evt| { 1650 - use dioxus::prelude::keyboard_types::Key; 1651 - if evt.key() == Key::Enter { 1652 - let text = input_text(); 1653 - match infer_data_from_text(&text) { 1654 - Ok(new_value) => { 1655 - root.with_mut(|data| { 1656 - if let Some(target) = data.get_at_path_mut(path_for_mutation.as_str()) { 1657 - *target = new_value; 1658 - } 1659 - }); 1660 - input_text.set(String::new()); 1661 - parse_error.set(None); 1662 - } 1663 - Err(e) => { 1664 - parse_error.set(Some(e)); 1665 - } 1666 - } 1667 - } 1668 - } 1669 - } 1670 - if let Some(err) = parse_error() { 1671 - span { class: "field-error", " ❌ {err}" } 1672 - } 1673 - } 1674 - } 1675 - } 1676 - 1677 - /// Blob field - shows CID, size (editable), mime type (read-only), file upload 1678 - #[component] 1679 - fn EditableBlobField( 1680 - root: Signal<Data<'static>>, 1681 - path: String, 1682 - did: String, 1683 - #[props(default)] remove_button: Option<Element>, 1684 - ) -> Element { 1685 - let path_for_memo = path.clone(); 1686 - let blob_data = use_memo(move || { 1687 - root.read() 1688 - .get_at_path(&path_for_memo) 1689 - .and_then(|d| match d { 1690 - Data::Blob(blob) => Some(( 1691 - blob.r#ref.to_string(), 1692 - blob.size, 1693 - blob.mime_type.as_str().to_string(), 1694 - )), 1695 - _ => None, 1696 - }) 1697 - }); 1698 - 1699 - let mut cid_input = use_signal(|| String::new()); 1700 - let mut size_input = use_signal(|| String::new()); 1701 - let mut cid_error = use_signal(|| None::<String>); 1702 - let mut size_error = use_signal(|| None::<String>); 1703 - let mut uploading = use_signal(|| false); 1704 - let mut upload_error = use_signal(|| None::<String>); 1705 - let mut preview_data_url = use_signal(|| None::<String>); 1706 - 1707 - // Sync inputs when blob data changes 1708 - use_effect(move || { 1709 - if let Some((cid, size, _)) = blob_data() { 1710 - cid_input.set(cid); 1711 - size_input.set(size.to_string()); 1712 - } 1713 - }); 1714 - 1715 - let fetcher = use_context::<CachedFetcher>(); 1716 - let path_for_upload = path.clone(); 1717 - let handle_file = move |evt: Event<dioxus::prelude::FormData>| { 1718 - let fetcher = fetcher.clone(); 1719 - let path_upload_clone = path_for_upload.clone(); 1720 - spawn(async move { 1721 - uploading.set(true); 1722 - upload_error.set(None); 1723 - 1724 - let files = evt.files(); 1725 - for file_data in files { 1726 - match file_data.read_bytes().await { 1727 - Ok(bytes_data) => { 1728 - // Convert to jacquard Bytes and sniff MIME type 1729 - let bytes = jacquard::bytes::Bytes::from(bytes_data.to_vec()); 1730 - let mime_str = bytes 1731 - .sniff_mime_type() 1732 - .unwrap_or("application/octet-stream"); 1733 - let mime_type = jacquard::types::blob::MimeType::new_owned(mime_str); 1734 - 1735 - // Create data URL for immediate preview if it's an image 1736 - if mime_str.starts_with("image/") { 1737 - let base64_data = base64::Engine::encode( 1738 - &base64::engine::general_purpose::STANDARD, 1739 - &bytes, 1740 - ); 1741 - let data_url = format!("data:{};base64,{}", mime_str, base64_data); 1742 - preview_data_url.set(Some(data_url.clone())); 1743 - 1744 - // Try to decode dimensions and populate aspectRatio field 1745 - #[cfg(target_arch = "wasm32")] 1746 - { 1747 - let path_clone = path_upload_clone.clone(); 1748 - spawn(async move { 1749 - if let Some((width, height)) = 1750 - decode_image_dimensions(&data_url).await 1751 - { 1752 - populate_aspect_ratio( 1753 - root, 1754 - &path_clone, 1755 - width as i64, 1756 - height as i64, 1757 - ); 1758 - } 1759 - }); 1760 - } 1761 - } 1762 - 1763 - // Upload blob 1764 - let client = fetcher.get_client(); 1765 - match client.upload_blob(bytes, mime_type).await { 1766 - Ok(new_blob) => { 1767 - // Update blob in record 1768 - let path_ref = path_upload_clone.clone(); 1769 - root.with_mut(|record_data| { 1770 - if let Some(Data::Blob(blob)) = 1771 - record_data.get_at_path_mut(&path_ref) 1772 - { 1773 - *blob = new_blob; 1774 - } 1775 - }); 1776 - upload_error.set(None); 1777 - } 1778 - Err(e) => { 1779 - upload_error.set(Some(format!("Upload failed: {:?}", e))); 1780 - } 1781 - } 1782 - } 1783 - Err(e) => { 1784 - upload_error.set(Some(format!("Failed to read file: {}", e))); 1785 - } 1786 - } 1787 - } 1788 - 1789 - uploading.set(false); 1790 - }); 1791 - }; 1792 - 1793 - let path_for_cid = path.clone(); 1794 - let handle_cid_change = move |evt: Event<dioxus::prelude::FormData>| { 1795 - let text = evt.value(); 1796 - cid_input.set(text.clone()); 1797 - 1798 - match jacquard::types::cid::CidLink::new_owned(text.as_bytes()) { 1799 - Ok(new_cid_link) => { 1800 - cid_error.set(None); 1801 - root.with_mut(|data| { 1802 - if let Some(Data::Blob(blob)) = data.get_at_path_mut(&path_for_cid) { 1803 - blob.r#ref = new_cid_link; 1804 - } 1805 - }); 1806 - } 1807 - Err(_) => { 1808 - cid_error.set(Some("Invalid CID format".to_string())); 1809 - } 1810 - } 1811 - }; 1812 - 1813 - let path_for_size = path.clone(); 1814 - let handle_size_change = move |evt: Event<dioxus::prelude::FormData>| { 1815 - let text = evt.value(); 1816 - size_input.set(text.clone()); 1817 - 1818 - match text.parse::<usize>() { 1819 - Ok(new_size) => { 1820 - size_input.set(format_size(new_size, humansize::BINARY)); 1821 - size_error.set(None); 1822 - root.with_mut(|data| { 1823 - if let Some(Data::Blob(blob)) = data.get_at_path_mut(&path_for_size) { 1824 - blob.size = new_size; 1825 - } 1826 - }); 1827 - } 1828 - Err(_) => { 1829 - size_error.set(Some("Must be a non-negative integer".to_string())); 1830 - } 1831 - } 1832 - }; 1833 - 1834 - let placeholder_cid = "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 1835 - let is_placeholder = blob_data() 1836 - .map(|(cid, _, _)| cid == placeholder_cid) 1837 - .unwrap_or(true); 1838 - let is_image = blob_data() 1839 - .map(|(_, _, mime)| mime.starts_with("image/")) 1840 - .unwrap_or(false); 1841 - 1842 - // Use preview data URL if available (fresh upload), otherwise CDN 1843 - let image_url = if let Some(data_url) = preview_data_url() { 1844 - Some(data_url) 1845 - } else if !is_placeholder && is_image { 1846 - blob_data().map(|(cid, _, mime)| { 1847 - let format = mime.strip_prefix("image/").unwrap_or("jpeg"); 1848 - format!( 1849 - "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@{}", 1850 - did, cid, format 1851 - ) 1852 - }) 1853 - } else { 1854 - None 1855 - }; 1856 - 1857 - rsx! { 1858 - div { class: "record-field blob-field", 1859 - div { class: "field-header", 1860 - PathLabel { path: path.clone() } 1861 - span { class: "string-type-tag", " [blob]" } 1862 - {remove_button} 1863 - } 1864 - div { class: "blob-fields", 1865 - div { class: "blob-field-row blob-field-cid", 1866 - label { "CID:" } 1867 - input { 1868 - r#type: "text", 1869 - value: "{cid_input}", 1870 - oninput: handle_cid_change, 1871 - class: if cid_error().is_some() { "invalid" } else { "" }, 1872 - } 1873 - if let Some(err) = cid_error() { 1874 - span { class: "field-error", " ❌ {err}" } 1875 - } 1876 - } 1877 - div { class: "blob-field-row", 1878 - label { "Size:" } 1879 - input { 1880 - r#type: "number", 1881 - value: "{size_input}", 1882 - oninput: handle_size_change, 1883 - class: if size_error().is_some() { "invalid" } else { "" }, 1884 - } 1885 - if let Some(err) = size_error() { 1886 - span { class: "field-error", " ❌ {err}" } 1887 - } 1888 - } 1889 - div { class: "blob-field-row", 1890 - label { "MIME Type:" } 1891 - span { class: "readonly", 1892 - "{blob_data().map(|(_, _, mime)| mime).unwrap_or_default()}" 1893 - } 1894 - } 1895 - if let Some(url) = image_url { 1896 - img { 1897 - src: "{url}", 1898 - alt: "Blob preview", 1899 - class: "blob-image", 1900 - } 1901 - } 1902 - div { class: "blob-upload-section", 1903 - input { 1904 - r#type: "file", 1905 - accept: if is_image { "image/*" } else { "*/*" }, 1906 - onchange: handle_file, 1907 - disabled: uploading(), 1908 - } 1909 - if uploading() { 1910 - span { class: "upload-status", "Uploading..." } 1911 - } 1912 - if let Some(err) = upload_error() { 1913 - div { class: "field-error", "❌ {err}" } 1914 - } 1915 - } 1916 - } 1917 - } 1918 - } 1919 - } 1920 - 1921 - /// Decode image dimensions from data URL using browser Image API 1922 - #[cfg(target_arch = "wasm32")] 1923 - async fn decode_image_dimensions(data_url: &str) -> Option<(u32, u32)> { 1924 - use wasm_bindgen::JsCast; 1925 - use wasm_bindgen::prelude::*; 1926 - use wasm_bindgen_futures::JsFuture; 1927 - 1928 - let window = web_sys::window()?; 1929 - let document = window.document()?; 1930 - 1931 - let img = document.create_element("img").ok()?; 1932 - let img = img.dyn_into::<web_sys::HtmlImageElement>().ok()?; 1933 - 1934 - img.set_src(data_url); 1935 - 1936 - // Wait for image to load 1937 - let promise = js_sys::Promise::new(&mut |resolve, _reject| { 1938 - let onload = Closure::wrap(Box::new(move || { 1939 - resolve.call0(&JsValue::NULL).ok(); 1940 - }) as Box<dyn FnMut()>); 1941 - 1942 - img.set_onload(Some(onload.as_ref().unchecked_ref())); 1943 - onload.forget(); 1944 - }); 1945 - 1946 - JsFuture::from(promise).await.ok()?; 1947 - 1948 - Some((img.natural_width(), img.natural_height())) 1949 - } 1950 - 1951 - /// Find and populate aspectRatio field for a blob 1952 - #[allow(unused)] 1953 - fn populate_aspect_ratio( 1954 - mut root: Signal<Data<'static>>, 1955 - blob_path: &str, 1956 - width: i64, 1957 - height: i64, 1958 - ) { 1959 - // Query for all aspectRatio fields and collect the path we want 1960 - let aspect_path_to_update = { 1961 - let data = root.read(); 1962 - let query_result = data.query("...aspectRatio"); 1963 - 1964 - query_result.multiple().and_then(|matches| { 1965 - // Find aspectRatio that's a sibling of our blob 1966 - // e.g. blob at "embed.images[0].image" -> look for "embed.images[0].aspectRatio" 1967 - let blob_parent = blob_path.rsplit_once('.').map(|(parent, _)| parent); 1968 - matches.iter().find_map(|query_match| { 1969 - let aspect_parent = query_match.path.rsplit_once('.').map(|(parent, _)| parent); 1970 - 1971 - // Check if they share the same parent 1972 - if blob_parent == aspect_parent { 1973 - Some(query_match.path.clone()) 1974 - } else { 1975 - None 1976 - } 1977 - }) 1978 - }) 1979 - }; 1980 - 1981 - // Update the aspectRatio if we found a matching field 1982 - if let Some(aspect_path) = aspect_path_to_update { 1983 - let aspect_obj = atproto! {{ 1984 - "width": width, 1985 - "height": height 1986 - }}; 1987 - 1988 - root.with_mut(|record_data| { 1989 - record_data.set_at_path(&aspect_path, aspect_obj); 1990 - }); 1991 - } 1992 - } 1993 - 1994 - /// Bytes field with hex/base64 auto-detection 1995 - #[component] 1996 - fn EditableBytesField( 1997 - root: Signal<Data<'static>>, 1998 - path: String, 1999 - #[props(default)] remove_button: Option<Element>, 2000 - ) -> Element { 2001 - let path_for_memo = path.clone(); 2002 - let current_bytes = use_memo(move || { 2003 - root.read() 2004 - .get_at_path(&path_for_memo) 2005 - .and_then(|d| match d { 2006 - Data::Bytes(b) => Some(bytes_to_hex(b)), 2007 - _ => None, 2008 - }) 2009 - }); 2010 - 2011 - let mut input_text = use_signal(|| String::new()); 2012 - let mut parse_error = use_signal(|| None::<String>); 2013 - let mut detected_format = use_signal(|| None::<String>); 2014 - 2015 - // Sync input when bytes change 2016 - use_effect(move || { 2017 - if let Some(hex) = current_bytes() { 2018 - input_text.set(hex); 2019 - } 2020 - }); 2021 - 2022 - let path_for_mutation = path.clone(); 2023 - let handle_input = move |evt: Event<dioxus::prelude::FormData>| { 2024 - let text = evt.value(); 2025 - input_text.set(text.clone()); 2026 - 2027 - match parse_bytes_input(&text) { 2028 - Ok((bytes, format)) => { 2029 - parse_error.set(None); 2030 - detected_format.set(Some(format)); 2031 - root.with_mut(|data| { 2032 - if let Some(target) = data.get_at_path_mut(&path_for_mutation) { 2033 - *target = Data::Bytes(bytes); 2034 - } 2035 - }); 2036 - } 2037 - Err(e) => { 2038 - parse_error.set(Some(e)); 2039 - detected_format.set(None); 2040 - } 2041 - } 2042 - }; 2043 - 2044 - let byte_count = current_bytes() 2045 - .map(|hex| hex.chars().filter(|c| c.is_ascii_hexdigit()).count() / 2) 2046 - .unwrap_or(0); 2047 - let size_label = if byte_count > 128 { 2048 - format_size(byte_count, humansize::BINARY) 2049 - } else { 2050 - format!("{} bytes", byte_count) 2051 - }; 2052 - 2053 - rsx! { 2054 - div { class: "record-field bytes-field", 2055 - div { class: "field-header", 2056 - PathLabel { path: path.clone() } 2057 - span { class: "string-type-tag", " [bytes: {size_label}]" } 2058 - if let Some(format) = detected_format() { 2059 - span { class: "bytes-format-tag", " ({format})" } 2060 - } 2061 - {remove_button} 2062 - } 2063 - textarea { 2064 - value: "{input_text}", 2065 - placeholder: "Paste hex (1a2b3c...) or base64 (YWJj...)", 2066 - oninput: handle_input, 2067 - class: if parse_error().is_some() { "invalid" } else { "" }, 2068 - rows: "3", 2069 - } 2070 - if let Some(err) = parse_error() { 2071 - span { class: "field-error", " ❌ {err}" } 2072 - } 2073 - } 2074 - } 2075 - } 2076 - 2077 - /// Parse bytes from hex or base64, auto-detecting format 2078 - fn parse_bytes_input(text: &str) -> Result<(jacquard::bytes::Bytes, String), String> { 2079 - let trimmed = text.trim(); 2080 - if trimmed.is_empty() { 2081 - return Err("Input is empty".to_string()); 2082 - } 2083 - 2084 - // Remove common whitespace/separators 2085 - let cleaned: String = trimmed 2086 - .chars() 2087 - .filter(|c| !c.is_whitespace() && *c != ':' && *c != '-') 2088 - .collect(); 2089 - 2090 - // Try hex first (more restrictive) 2091 - if cleaned.chars().all(|c| c.is_ascii_hexdigit()) { 2092 - parse_hex_bytes(&cleaned).map(|b| (b, "hex".to_string())) 2093 - } else { 2094 - // Try base64 2095 - parse_base64_bytes(&cleaned).map(|b| (b, "base64".to_string())) 2096 - } 2097 - } 2098 - 2099 - /// Parse hex string to bytes 2100 - fn parse_hex_bytes(hex: &str) -> Result<jacquard::bytes::Bytes, String> { 2101 - if hex.len() % 2 != 0 { 2102 - return Err("Hex string must have even length".to_string()); 2103 - } 2104 - 2105 - let mut bytes = Vec::with_capacity(hex.len() / 2); 2106 - for chunk in hex.as_bytes().chunks(2) { 2107 - let hex_byte = std::str::from_utf8(chunk).map_err(|e| format!("Invalid UTF-8: {}", e))?; 2108 - let byte = 2109 - u8::from_str_radix(hex_byte, 16).map_err(|e| format!("Invalid hex digit: {}", e))?; 2110 - bytes.push(byte); 2111 - } 2112 - 2113 - Ok(jacquard::bytes::Bytes::from(bytes)) 2114 - } 2115 - 2116 - /// Parse base64 string to bytes 2117 - fn parse_base64_bytes(b64: &str) -> Result<jacquard::bytes::Bytes, String> { 2118 - use base64::Engine; 2119 - let engine = base64::engine::general_purpose::STANDARD; 2120 - 2121 - engine 2122 - .decode(b64) 2123 - .map(jacquard::bytes::Bytes::from) 2124 - .map_err(|e| format!("Invalid base64: {}", e)) 2125 - } 2126 - 2127 - /// Convert bytes to hex display string (with spacing every 4 chars) 2128 - fn bytes_to_hex(bytes: &jacquard::bytes::Bytes) -> String { 2129 - bytes 2130 - .iter() 2131 - .enumerate() 2132 - .map(|(i, b)| { 2133 - let hex = format!("{:02x}", b); 2134 - if i > 0 && i % 2 == 0 { 2135 - format!(" {}", hex) 2136 - } else { 2137 - hex 2138 - } 2139 - }) 2140 - .collect() 2141 - } 2142 - 2143 - /// CidLink field with validation 2144 - #[component] 2145 - fn EditableCidLinkField( 2146 - root: Signal<Data<'static>>, 2147 - path: String, 2148 - #[props(default)] remove_button: Option<Element>, 2149 - ) -> Element { 2150 - let path_for_memo = path.clone(); 2151 - let current_cid = use_memo(move || { 2152 - root.read() 2153 - .get_at_path(&path_for_memo) 2154 - .map(|d| match d { 2155 - Data::CidLink(cid) => cid.to_string(), 2156 - _ => String::new(), 2157 - }) 2158 - .unwrap_or_default() 2159 - }); 2160 - 2161 - let mut input_text = use_signal(|| String::new()); 2162 - let mut parse_error = use_signal(|| None::<String>); 2163 - 2164 - use_effect(move || { 2165 - input_text.set(current_cid()); 2166 - }); 2167 - 2168 - let input_width = use_memo(move || { 2169 - let len = input_text().len(); 2170 - format!("{}ch", len.max(60)) 2171 - }); 2172 - 2173 - let path_for_mutation = path.clone(); 2174 - let handle_input = move |evt: Event<dioxus::prelude::FormData>| { 2175 - let text = evt.value(); 2176 - input_text.set(text.clone()); 2177 - 2178 - match jacquard::types::cid::Cid::new_owned(text.as_bytes()) { 2179 - Ok(new_cid) => { 2180 - parse_error.set(None); 2181 - root.with_mut(|data| { 2182 - if let Some(target) = data.get_at_path_mut(&path_for_mutation) { 2183 - *target = Data::CidLink(new_cid); 2184 - } 2185 - }); 2186 - } 2187 - Err(_) => { 2188 - parse_error.set(Some("Invalid CID format".to_string())); 2189 - } 2190 - } 2191 - }; 2192 - 2193 - rsx! { 2194 - div { class: "record-field cidlink-field", 2195 - div { class: "field-header", 2196 - PathLabel { path: path.clone() } 2197 - span { class: "string-type-tag", " [cid-link]" } 2198 - {remove_button} 2199 - } 2200 - input { 2201 - r#type: "text", 2202 - value: "{input_text}", 2203 - style: "width: {input_width}", 2204 - placeholder: "bafyrei...", 2205 - oninput: handle_input, 2206 - class: if parse_error().is_some() { "invalid" } else { "" }, 2207 - } 2208 - if let Some(err) = parse_error() { 2209 - span { class: "field-error", " ❌ {err}" } 2210 - } 2211 - } 2212 - } 2213 - } 2214 - 2215 - // ============================================================================ 2216 - // Field with Remove Button Wrapper 2217 - // ============================================================================ 2218 - 2219 - /// Wraps a field with an optional remove button in the header 2220 - #[component] 2221 - fn FieldWithRemove( 2222 - root: Signal<Data<'static>>, 2223 - path: String, 2224 - did: String, 2225 - is_removable: bool, 2226 - parent_path: String, 2227 - field_key: String, 2228 - ) -> Element { 2229 - let remove_button = if is_removable { 2230 - Some(rsx! { 2231 - button { 2232 - class: "field-remove-button", 2233 - onclick: move |_| { 2234 - let mut new_data = root.read().clone(); 2235 - if let Some(Data::Object(obj)) = new_data.get_at_path_mut(parent_path.as_str()) { 2236 - obj.0.remove(field_key.as_str()); 2237 - } 2238 - root.set(new_data); 2239 - }, 2240 - "Remove" 2241 - } 2242 - }) 2243 - } else { 2244 - None 2245 - }; 2246 - 2247 - rsx! { 2248 - EditableDataView { 2249 - root: root, 2250 - path: path.clone(), 2251 - did: did.clone(), 2252 - remove_button: remove_button, 2253 - } 2254 - } 2255 - } 2256 - 2257 - // ============================================================================ 2258 - // Array Field Editor (enables recursion) 2259 - // ============================================================================ 2260 - 2261 - /// Array field - iterates items and renders child EditableDataView for each 2262 - #[component] 2263 - fn EditableArrayField(root: Signal<Data<'static>>, path: String, did: String) -> Element { 2264 - let path_for_memo = path.clone(); 2265 - let array_len = use_memo(move || { 2266 - root.read() 2267 - .get_at_path(&path_for_memo) 2268 - .and_then(|d| d.as_array()) 2269 - .map(|arr| arr.0.len()) 2270 - .unwrap_or(0) 2271 - }); 2272 - 2273 - let path_for_add = path.clone(); 2274 - 2275 - rsx! { 2276 - div { class: "record-section array-section", 2277 - Accordion { 2278 - id: "edit-array-{path}", 2279 - collapsible: true, 2280 - AccordionItem { 2281 - default_open: true, 2282 - index: 0, 2283 - AccordionTrigger { 2284 - div { class: "section-header", 2285 - div { class: "section-label", 2286 - { 2287 - let parts: Vec<&str> = path.split('.').collect(); 2288 - let final_part = parts.last().unwrap_or(&""); 2289 - rsx! { "{final_part}" } 2290 - } 2291 - } 2292 - span { class: "array-length", "[{array_len}]" } 2293 - } 2294 - } 2295 - AccordionContent { 2296 - div { class: "section-content", 2297 - for idx in 0..array_len() { 2298 - { 2299 - let item_path = format!("{}[{}]", path, idx); 2300 - let path_for_remove = path.clone(); 2301 - 2302 - rsx! { 2303 - div { 2304 - class: "array-item", 2305 - key: "{item_path}", 2306 - 2307 - EditableDataView { 2308 - root: root, 2309 - path: item_path.clone(), 2310 - did: did.clone(), 2311 - remove_button: rsx! { 2312 - button { 2313 - class: "field-remove-button", 2314 - onclick: move |_| { 2315 - root.with_mut(|data| { 2316 - if let Some(Data::Array(arr)) = data.get_at_path_mut(&path_for_remove) { 2317 - arr.0.remove(idx); 2318 - } 2319 - }); 2320 - }, 2321 - "Remove" 2322 - } 2323 - } 2324 - } 2325 - } 2326 - } 2327 - } 2328 - } 2329 - div { 2330 - class: "array-item", 2331 - div { 2332 - class: "add-field-widget", 2333 - button { 2334 - onclick: move |_| { 2335 - root.with_mut(|data| { 2336 - if let Some(Data::Array(arr)) = data.get_at_path_mut(&path_for_add) { 2337 - let new_item = create_array_item_default(arr); 2338 - arr.0.push(new_item); 2339 - } 2340 - }); 2341 - }, 2342 - "+ Add Item" 2343 - } 2344 - } 2345 - } 2346 - } 2347 - } 2348 - } 2349 - } 2350 - } 2351 - } 2352 - } 2353 - 2354 - // ============================================================================ 2355 - // Object Field Editor (enables recursion) 2356 - // ============================================================================ 2357 - 2358 - /// Object field - iterates fields and renders child EditableDataView for each 2359 - #[component] 2360 - fn EditableObjectField( 2361 - root: Signal<Data<'static>>, 2362 - path: String, 2363 - did: String, 2364 - #[props(default)] remove_button: Option<Element>, 2365 - ) -> Element { 2366 - let path_for_memo = path.clone(); 2367 - let field_keys = use_memo(move || { 2368 - root.read() 2369 - .get_at_path(&path_for_memo) 2370 - .and_then(|d| d.as_object()) 2371 - .map(|obj| obj.0.keys().cloned().collect::<Vec<_>>()) 2372 - .unwrap_or_default() 2373 - }); 2374 - 2375 - let is_root = path.is_empty(); 2376 - 2377 - rsx! { 2378 - if !is_root { 2379 - div { class: "record-section object-section", 2380 - Accordion { 2381 - id: "edit-object-{path}", 2382 - collapsible: true, 2383 - AccordionItem { 2384 - default_open: true, 2385 - index: 0, 2386 - AccordionTrigger { 2387 - div { class: "section-header", 2388 - div { class: "section-label", 2389 - { 2390 - let parts: Vec<&str> = path.split('.').collect(); 2391 - let final_part = parts.last().unwrap_or(&""); 2392 - rsx! { "{final_part}" } 2393 - } 2394 - } 2395 - {remove_button} 2396 - } 2397 - } 2398 - AccordionContent { 2399 - div { class: "section-content", 2400 - for key in field_keys() { 2401 - { 2402 - let field_path = if path.is_empty() { 2403 - key.to_string() 2404 - } else { 2405 - format!("{}.{}", path, key) 2406 - }; 2407 - let is_type_field = key == "$type"; 2408 - 2409 - rsx! { 2410 - FieldWithRemove { 2411 - key: "{field_path}", 2412 - root: root, 2413 - path: field_path.clone(), 2414 - did: did.clone(), 2415 - is_removable: !is_type_field, 2416 - parent_path: path.clone(), 2417 - field_key: key.clone(), 2418 - } 2419 - } 2420 - } 2421 - } 2422 - 2423 - AddFieldWidget { root: root, path: path.clone() } 2424 - } 2425 - } 2426 - } 2427 - } 2428 - } 2429 - } else { 2430 - for key in field_keys() { 2431 - { 2432 - let field_path = key.to_string(); 2433 - let is_type_field = key == "$type"; 2434 - 2435 - rsx! { 2436 - FieldWithRemove { 2437 - key: "{field_path}", 2438 - root: root, 2439 - path: field_path.clone(), 2440 - did: did.clone(), 2441 - is_removable: !is_type_field, 2442 - parent_path: path.clone(), 2443 - field_key: key.clone(), 2444 - } 2445 - } 2446 - } 2447 - } 2448 - 2449 - AddFieldWidget { root: root, path: path.clone() } 2450 - } 2451 - } 2452 - } 2453 - 2454 - /// Widget for adding new fields to objects 2455 - #[component] 2456 - fn AddFieldWidget(root: Signal<Data<'static>>, path: String) -> Element { 2457 - let mut field_name = use_signal(|| String::new()); 2458 - let mut field_value = use_signal(|| String::new()); 2459 - let mut error = use_signal(|| None::<String>); 2460 - let mut show_form = use_signal(|| false); 2461 - 2462 - let path_for_enter = path.clone(); 2463 - let path_for_button = path.clone(); 2464 - 2465 - rsx! { 2466 - div { class: "add-field-widget", 2467 - if !show_form() { 2468 - button { 2469 - class: "add-button", 2470 - onclick: move |_| show_form.set(true), 2471 - "+ Add Field" 2472 - } 2473 - } else { 2474 - div { class: "add-field-form", 2475 - input { 2476 - r#type: "text", 2477 - placeholder: "Field name", 2478 - value: "{field_name}", 2479 - oninput: move |evt| field_name.set(evt.value()), 2480 - } 2481 - input { 2482 - r#type: "text", 2483 - placeholder: r#"Value: {{}}, [], true, 123, "text""#, 2484 - value: "{field_value}", 2485 - oninput: move |evt| field_value.set(evt.value()), 2486 - onkeydown: move |evt| { 2487 - use dioxus::prelude::keyboard_types::Key; 2488 - if evt.key() == Key::Enter { 2489 - let name = field_name(); 2490 - let value_text = field_value(); 2491 - 2492 - if name.is_empty() { 2493 - error.set(Some("Field name required".to_string())); 2494 - return; 2495 - } 2496 - 2497 - let new_value = match infer_data_from_text(&value_text) { 2498 - Ok(data) => data, 2499 - Err(e) => { 2500 - error.set(Some(e)); 2501 - return; 2502 - } 2503 - }; 2504 - 2505 - let mut new_data = root.read().clone(); 2506 - if let Some(Data::Object(obj)) = new_data.get_at_path_mut(path_for_enter.as_str()) { 2507 - obj.0.insert(name.into(), new_value); 2508 - } 2509 - root.set(new_data); 2510 - 2511 - // Reset form 2512 - field_name.set(String::new()); 2513 - field_value.set(String::new()); 2514 - show_form.set(false); 2515 - error.set(None); 2516 - } 2517 - } 2518 - } 2519 - button { 2520 - class: "add-field-widget-edit", 2521 - onclick: move |_| { 2522 - let name = field_name(); 2523 - let value_text = field_value(); 2524 - 2525 - if name.is_empty() { 2526 - error.set(Some("Field name required".to_string())); 2527 - return; 2528 - } 2529 - 2530 - let new_value = match infer_data_from_text(&value_text) { 2531 - Ok(data) => data, 2532 - Err(e) => { 2533 - error.set(Some(e)); 2534 - return; 2535 - } 2536 - }; 2537 - 2538 - let mut new_data = root.read().clone(); 2539 - if let Some(Data::Object(obj)) = new_data.get_at_path_mut(path_for_button.as_str()) { 2540 - obj.0.insert(name.into(), new_value); 2541 - } 2542 - root.set(new_data); 2543 - 2544 - // Reset form 2545 - field_name.set(String::new()); 2546 - field_value.set(String::new()); 2547 - show_form.set(false); 2548 - error.set(None); 2549 - }, 2550 - "Add" 2551 - } 2552 - button { 2553 - class: "add-field-widget-edit", 2554 - onclick: move |_| { 2555 - show_form.set(false); 2556 - field_name.set(String::new()); 2557 - field_value.set(String::new()); 2558 - error.set(None); 2559 - }, 2560 - "Cancel" 2561 - } 2562 - if let Some(err) = error() { 2563 - div { class: "field-error", "❌ {err}" } 2564 - } 2565 - } 2566 - } 2567 - } 2568 - } 2569 - } 2570 - 2571 - /// Layout component for record view - handles header, metadata, and wraps children 2572 - #[component] 2573 - fn RecordViewLayout( 2574 - uri: AtUri<'static>, 2575 - cid: Option<Cid<'static>>, 2576 - schema: ReadSignal<Option<LexiconDoc<'static>>>, 2577 - record_value: Data<'static>, 2578 - children: Element, 2579 - ) -> Element { 2580 - // Validate the record if schema is available 2581 - let validation_status = use_memo(move || { 2582 - let _schema_doc = schema()?; 2583 - let nsid_str = record_value.type_discriminator()?; 2584 - 2585 - let validator = jacquard_lexicon::validation::SchemaValidator::global(); 2586 - let result = validator.validate_by_nsid(nsid_str, &record_value); 2587 - 2588 - Some(result.is_valid()) 2589 - }); 2590 - 2591 - rsx! { 2592 - div { 2593 - class: "record-metadata", 2594 - div { class: "metadata-row", 2595 - span { class: "metadata-label", "URI" } 2596 - span { class: "metadata-value", 2597 - HighlightedUri { uri: uri.clone() } 2598 - } 2599 - } 2600 - if let Some(cid) = cid { 2601 - div { class: "metadata-row", 2602 - span { class: "metadata-label", "CID" } 2603 - code { class: "metadata-value", "{cid}" } 2604 - } 2605 - } 2606 - if let Some(is_valid) = validation_status() { 2607 - div { class: "metadata-row", 2608 - span { class: "metadata-label", "Schema" } 2609 - span { 2610 - class: if is_valid { "metadata-value schema-valid" } else { "metadata-value schema-invalid" }, 2611 - if is_valid { "Valid" } else { "Invalid" } 2612 - } 2613 - } 2614 - } 2615 - } 2616 - 2617 - {children} 2618 - 2619 - } 2620 - } 2621 - 2622 - /// Render some text as markdown. 2623 - #[component] 2624 - fn EditableRecordContent( 2625 - record_value: Data<'static>, 2626 - uri: ReadSignal<AtUri<'static>>, 2627 - view_mode: Signal<ViewMode>, 2628 - edit_mode: Signal<bool>, 2629 - record_resource: Resource<Result<GetRecordOutput<'static>, AgentError>>, 2630 - schema: ReadSignal<Option<LexiconDoc<'static>>>, 2631 - ) -> Element { 2632 - let mut edit_data = use_signal(use_reactive!(|record_value| record_value.clone())); 2633 - let nsid = use_memo(move || edit_data().type_discriminator().map(|s| s.to_string())); 2634 - let navigator = use_navigator(); 2635 - let fetcher = use_context::<CachedFetcher>(); 2636 - 2637 - // Validate edit_data whenever it changes and provide via context 2638 - let mut validation_result = use_signal(|| None); 2639 - use_effect(move || { 2640 - let _ = schema(); // Track schema changes 2641 - if let Some(nsid_str) = nsid() { 2642 - let data = edit_data(); 2643 - let validator = jacquard_lexicon::validation::SchemaValidator::global(); 2644 - let result = validator.validate_by_nsid(&nsid_str, &data); 2645 - validation_result.set(Some(result)); 2646 - } 2647 - }); 2648 - use_context_provider(|| validation_result); 2649 - 2650 - let update_fetcher = fetcher.clone(); 2651 - let create_fetcher = fetcher.clone(); 2652 - let replace_fetcher = fetcher.clone(); 2653 - let delete_fetcher = fetcher.clone(); 2654 - 2655 - rsx! { 2656 - div { 2657 - class: "tab-bar", 2658 - button { 2659 - class: if view_mode() == ViewMode::Pretty { "tab-button active" } else { "tab-button" }, 2660 - onclick: move |_| view_mode.set(ViewMode::Pretty), 2661 - "View" 2662 - } 2663 - button { 2664 - class: if view_mode() == ViewMode::Json { "tab-button active" } else { "tab-button" }, 2665 - onclick: move |_| view_mode.set(ViewMode::Json), 2666 - "JSON" 2667 - } 2668 - button { 2669 - class: if view_mode() == ViewMode::Schema { "tab-button active" } else { "tab-button" }, 2670 - onclick: move |_| view_mode.set(ViewMode::Schema), 2671 - "Schema" 2672 - } 2673 - ActionButtons { 2674 - on_update: move |_| { 2675 - let fetcher = update_fetcher.clone(); 2676 - let uri = uri(); 2677 - let data = edit_data(); 2678 - spawn(async move { 2679 - if let Some((did, _)) = fetcher.session_info().await { 2680 - if let (Some(collection_str), Some(rkey)) = (uri.collection(), uri.rkey()) { 2681 - let collection = Nsid::new(collection_str.as_str()).ok(); 2682 - if let Some(collection) = collection { 2683 - let request = PutRecord::new() 2684 - .repo(AtIdentifier::Did(did)) 2685 - .collection(collection) 2686 - .rkey(rkey.clone()) 2687 - .record(data.clone()) 2688 - .build(); 2689 - 2690 - match fetcher.send(request).await { 2691 - Ok(output) => { 2692 - if output.status() == StatusCode::OK { 2693 - tracing::info!("Record updated successfully"); 2694 - edit_data.set(data.clone()); 2695 - edit_mode.set(false); 2696 - } else { 2697 - tracing::error!("Unexpected status code: {:?}", output.status()); 2698 - } 2699 - } 2700 - Err(e) => { 2701 - tracing::error!("Failed to update record: {:?}", e); 2702 - } 2703 - } 2704 - } 2705 - } 2706 - } 2707 - }); 2708 - }, 2709 - on_save_new: move |_| { 2710 - let fetcher = create_fetcher.clone(); 2711 - let data = edit_data(); 2712 - let nav = navigator.clone(); 2713 - spawn(async move { 2714 - if let Some((did, _)) = fetcher.session_info().await { 2715 - if let Some(collection_str) = data.type_discriminator() { 2716 - let collection = Nsid::new(collection_str).ok(); 2717 - if let Some(collection) = collection { 2718 - let request = CreateRecord::new() 2719 - .repo(AtIdentifier::Did(did)) 2720 - .collection(collection) 2721 - .record(data.clone()) 2722 - .build(); 2723 - 2724 - match fetcher.send(request).await { 2725 - Ok(response) => { 2726 - if let Ok(output) = response.into_output() { 2727 - tracing::info!("Record created: {}", output.uri); 2728 - let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, output.uri); 2729 - nav.push(link); 2730 - } 2731 - } 2732 - Err(e) => { 2733 - tracing::error!("Failed to create record: {:?}", e); 2734 - } 2735 - } 2736 - } 2737 - } 2738 - } 2739 - }); 2740 - }, 2741 - on_replace: move |_| { 2742 - let fetcher = replace_fetcher.clone(); 2743 - let uri = uri(); 2744 - let data = edit_data(); 2745 - let nav = navigator.clone(); 2746 - spawn(async move { 2747 - if let Some((did, _)) = fetcher.session_info().await { 2748 - if let Some(new_collection_str) = data.type_discriminator() { 2749 - let new_collection = Nsid::new(new_collection_str).ok(); 2750 - if let Some(new_collection) = new_collection { 2751 - // Create new record 2752 - let create_req = CreateRecord::new() 2753 - .repo(AtIdentifier::Did(did.clone())) 2754 - .collection(new_collection) 2755 - .record(data.clone()) 2756 - .build(); 2757 - 2758 - match fetcher.send(create_req).await { 2759 - Ok(response) => { 2760 - if let Ok(create_output) = response.into_output() { 2761 - // Delete old record 2762 - if let (Some(old_collection_str), Some(old_rkey)) = (uri.collection(), uri.rkey()) { 2763 - let old_collection = Nsid::new(old_collection_str.as_str()).ok(); 2764 - if let Some(old_collection) = old_collection { 2765 - let delete_req = DeleteRecord::new() 2766 - .repo(AtIdentifier::Did(did)) 2767 - .collection(old_collection) 2768 - .rkey(old_rkey.clone()) 2769 - .build(); 2770 - 2771 - if let Err(e) = fetcher.send(delete_req).await { 2772 - tracing::warn!("Created new record but failed to delete old: {:?}", e); 2773 - } 2774 - } 2775 - } 2776 - 2777 - tracing::info!("Record replaced: {}", create_output.uri); 2778 - let link = format!("{}/record/{}", crate::env::WEAVER_APP_DOMAIN, create_output.uri); 2779 - nav.push(link); 2780 - } 2781 - } 2782 - Err(e) => { 2783 - tracing::error!("Failed to replace record: {:?}", e); 2784 - } 2785 - } 2786 - } 2787 - } 2788 - } 2789 - }); 2790 - }, 2791 - on_delete: move |_| { 2792 - let fetcher = delete_fetcher.clone(); 2793 - let uri = uri(); 2794 - let nav = navigator.clone(); 2795 - spawn(async move { 2796 - if let Some((did, _)) = fetcher.session_info().await { 2797 - if let (Some(collection_str), Some(rkey)) = (uri.collection(), uri.rkey()) { 2798 - let collection = Nsid::new(collection_str.as_str()).ok(); 2799 - if let Some(collection) = collection { 2800 - let request = DeleteRecord::new() 2801 - .repo(AtIdentifier::Did(did)) 2802 - .collection(collection) 2803 - .rkey(rkey.clone()) 2804 - .build(); 2805 - 2806 - match fetcher.send(request).await { 2807 - Ok(_) => { 2808 - tracing::info!("Record deleted"); 2809 - nav.push(Route::Home {}); 2810 - } 2811 - Err(e) => { 2812 - tracing::error!("Failed to delete record: {:?}", e); 2813 - } 2814 - } 2815 - } 2816 - } 2817 - } 2818 - }); 2819 - }, 2820 - on_cancel: move |_| { 2821 - edit_data.set(record_value.clone()); 2822 - edit_mode.set(false); 2823 - }, 2824 - } 2825 - } 2826 - div { 2827 - class: "tab-content", 2828 - match view_mode() { 2829 - ViewMode::Pretty => rsx! { 2830 - div { class: "pretty-record", 2831 - EditableDataView { 2832 - root: edit_data, 2833 - path: String::new(), 2834 - did: uri().authority().to_string(), 2835 - } 2836 - } 2837 - }, 2838 - ViewMode::Json => rsx! { 2839 - JsonEditor { data: edit_data, nsid, schema } 2840 - }, 2841 - ViewMode::Schema => rsx! { 2842 - SchemaView { schema } 2843 - }, 2844 - } 2845 - } 2846 - } 2847 - } 2848 - 2849 - #[component] 2850 - pub fn CodeView(props: CodeViewProps) -> Element { 2851 - let code = &*props.code.read(); 2852 - 2853 - let mut html_buf = String::new(); 2854 - highlight_code(props.lang.as_deref(), code, &mut html_buf).unwrap(); 2855 - 2856 - rsx! { 2857 - document::Style { {generate_default_css().unwrap()}} 2858 - div { 2859 - id: "{&*props.id.read()}", 2860 - class: "{&*props.class.read()}", 2861 - dangerous_inner_html: "{html_buf}" 2862 - } 2863 - } 2864 - }
··· 1 use crate::Route; 2 use crate::auth::AuthState; 3 + use crate::components::record_editor::EditableRecordContent; 4 + use crate::components::record_view::{ 5 + CodeView, PrettyRecordView, RecordViewLayout, SchemaView, ViewMode, 6 + }; 7 + use crate::fetch::Fetcher; 8 + use dioxus::prelude::*; 9 use jacquard::common::to_data; 10 use jacquard::smol_str::ToSmolStr; 11 use jacquard::{ 12 client::AgentSessionExt, 13 + common::IntoStatic, 14 identity::lexicon_resolver::LexiconSchemaResolver, 15 + types::{aturi::AtUri, ident::AtIdentifier, string::Nsid}, 16 }; 17 use jacquard_lexicon::lexicon::LexiconDoc; 18 19 #[component] 20 pub fn RecordIndex() -> Element { ··· 57 } 58 59 #[component] 60 + pub fn RecordPage(uri: ReadSignal<Vec<String>>) -> Element { 61 + rsx! { 62 + {std::iter::once(rsx! {RecordView {uri}})} 63 + } 64 + } 65 + 66 + #[component] 67 pub fn RecordView(uri: ReadSignal<Vec<String>>) -> Element { 68 + let fetcher = use_context::<Fetcher>(); 69 info!("Uri:{:?}", uri().join("/")); 70 let at_uri = AtUri::new_owned(&*uri.read().join("/")); 71 if at_uri.is_err() { ··· 95 96 // Helper to recursively resolve a schema and its refs 97 fn resolve_schema_with_refs<'a>( 98 + fetcher: &'a Fetcher, 99 type_str: &'a str, 100 validator: &'a jacquard_lexicon::validation::SchemaValidator, 101 resolved: &'a mut std::collections::HashSet<String>, ··· 264 rsx! {} 265 } 266 }
-1
crates/weaver-cli/Cargo.toml
··· 17 miette = { workspace = true, features = ["fancy"] } 18 19 jacquard = { workspace = true, features = ["loopback", "dns"] } 20 - jacquard-api = { workspace = true, features = ["sh_weaver"] } 21 22 markdown-weaver = { workspace = true } 23 markdown-weaver-escape = { workspace = true }
··· 17 miette = { workspace = true, features = ["fancy"] } 18 19 jacquard = { workspace = true, features = ["loopback", "dns"] } 20 21 markdown-weaver = { workspace = true } 22 markdown-weaver-escape = { workspace = true }
+9 -23
crates/weaver-common/Cargo.toml
··· 14 n0-future = { workspace = true } 15 weaver-api = { version = "0.1", path = "../weaver-api" } 16 markdown-weaver = { workspace = true } 17 - #libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } 18 - 19 http = "1.3.1" 20 - 21 jacquard = { workspace = true } 22 jacquard-common = { workspace = true } 23 trait-variant = "0.1" 24 - 25 serde = { workspace = true } 26 serde_json = { version = "1.0.140", features = ["preserve_order", "raw_value"] } 27 - serde_ipld_dagcbor = { version = "0.6.1", features = ["codec"] } 28 - serde_html_form = "0.2.7" 29 - serde_bytes = "0.11.17" 30 tokio = { version = "1.44", features = ["sync"] } 31 - 32 - minijinja = { workspace = true, features = [ 33 - "builtins", 34 - "debug", 35 - "deserialization", 36 - "macros", 37 - "multi_template", 38 - "adjacent_loop_items", 39 - "std_collections", 40 - "serde", 41 - ] } 42 - 43 - 44 miette = { workspace = true } 45 - owo-colors = { workspace = true } 46 thiserror = { workspace = true } 47 tracing = { workspace = true } 48 - reqwest = "0.12.15" 49 - regex = "1.11.1" 50 markdown-weaver-escape = { workspace = true, features = ["std"] } 51 mime-sniffer = "^0.1" 52 53 # wasm-in-browser dependencies 54 [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 55 futures-util = "0.3" 56 js-sys = "0.3" 57 pin-project = "1"
··· 14 n0-future = { workspace = true } 15 weaver-api = { version = "0.1", path = "../weaver-api" } 16 markdown-weaver = { workspace = true } 17 http = "1.3.1" 18 jacquard = { workspace = true } 19 jacquard-common = { workspace = true } 20 trait-variant = "0.1" 21 serde = { workspace = true } 22 serde_json = { version = "1.0.140", features = ["preserve_order", "raw_value"] } 23 tokio = { version = "1.44", features = ["sync"] } 24 miette = { workspace = true } 25 thiserror = { workspace = true } 26 tracing = { workspace = true } 27 + reqwest = { version = "0.12", default-features = false, features = [ 28 + "json", 29 + "rustls-tls", 30 + ] } 31 + 32 markdown-weaver-escape = { workspace = true, features = ["std"] } 33 mime-sniffer = "^0.1" 34 35 + [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] 36 + regex = "1.11.1" 37 + 38 # wasm-in-browser dependencies 39 [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 40 + regex-lite = { version = "0.1" } 41 futures-util = "0.3" 42 js-sys = "0.3" 43 pin-project = "1"
+4 -4
crates/weaver-common/src/agent.rs
··· 624 .map(|blob| { 625 let cid = blob.blob().cid(); 626 jacquard::types::string::Uri::new_owned(format!( 627 - "https://cdn.bsky.app/img/avatar/plain/{}/{}", 628 did, cid 629 )) 630 }) ··· 639 .map(|blob| { 640 let cid = blob.blob().cid(); 641 jacquard::types::string::Uri::new_owned(format!( 642 - "https://cdn.bsky.app/img/banner/plain/{}/{}", 643 did, cid 644 )) 645 }) ··· 728 .map(|blob| { 729 let cid = blob.blob().cid(); 730 jacquard::types::string::Uri::new_owned(format!( 731 - "https://cdn.bsky.app/img/avatar/plain/{}/{}", 732 did, cid 733 )) 734 }) ··· 743 .map(|blob| { 744 let cid = blob.blob().cid(); 745 jacquard::types::string::Uri::new_owned(format!( 746 - "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}", 747 did, cid 748 )) 749 })
··· 624 .map(|blob| { 625 let cid = blob.blob().cid(); 626 jacquard::types::string::Uri::new_owned(format!( 627 + "https://cdn.bsky.app/img/avatar/plain/{}/{}@jpeg", 628 did, cid 629 )) 630 }) ··· 639 .map(|blob| { 640 let cid = blob.blob().cid(); 641 jacquard::types::string::Uri::new_owned(format!( 642 + "https://cdn.bsky.app/img/banner/plain/{}/{}@jpeg", 643 did, cid 644 )) 645 }) ··· 728 .map(|blob| { 729 let cid = blob.blob().cid(); 730 jacquard::types::string::Uri::new_owned(format!( 731 + "https://cdn.bsky.app/img/avatar/plain/{}/{}@jpeg", 732 did, cid 733 )) 734 }) ··· 743 .map(|blob| { 744 let cid = blob.blob().cid(); 745 jacquard::types::string::Uri::new_owned(format!( 746 + "https://cdn.bsky.app/img/banner/plain/{}/{}@jpeg", 747 did, cid 748 )) 749 })
-6
crates/weaver-common/src/error.rs
··· 103 pub enum ParseErrorKind { 104 #[error(transparent)] 105 SerdeError(#[from] SerDeError), 106 - #[error(transparent)] 107 - MiniJinjaError(#[from] minijinja::Error), 108 #[error("error in markdown parsing or rendering: {0}")] 109 MarkdownError(markdown_weaver::CowStr<'static>), 110 } ··· 116 #[error(transparent)] 117 #[diagnostic_source] 118 Json(#[from] serde_json::Error), 119 - #[error(transparent)] 120 - DagCbor(#[from] serde_ipld_dagcbor::error::CodecError), 121 - #[error(transparent)] 122 - HtmlForm(#[from] serde_html_form::ser::Error), 123 } 124 125 impl From<serde_json::Error> for ParseError {
··· 103 pub enum ParseErrorKind { 104 #[error(transparent)] 105 SerdeError(#[from] SerDeError), 106 #[error("error in markdown parsing or rendering: {0}")] 107 MarkdownError(markdown_weaver::CowStr<'static>), 108 } ··· 114 #[error(transparent)] 115 #[diagnostic_source] 116 Json(#[from] serde_json::Error), 117 } 118 119 impl From<serde_json::Error> for ParseError {
-1
crates/weaver-index/Cargo.toml
··· 24 uuid = { version = "1.8.0", features = ["v7", "serde"] } 25 26 jacquard = { workspace = true, features = ["websocket", "zstd"] } 27 - jacquard-api = { workspace = true, features = ["sh_weaver"] } 28 jacquard-axum = { workspace = true } 29 30 axum = "0.8"
··· 24 uuid = { version = "1.8.0", features = ["v7", "serde"] } 25 26 jacquard = { workspace = true, features = ["websocket", "zstd"] } 27 jacquard-axum = { workspace = true } 28 29 axum = "0.8"
+5 -5
crates/weaver-renderer/Cargo.toml
··· 11 weaver-api = { path = "../weaver-api" } 12 jacquard.workspace = true 13 markdown-weaver = { workspace = true } 14 - compact_string = "0.1.0" 15 http = "1.3.1" 16 url = "2.5.4" 17 syntect = { workspace = true, default-features = false, features = ["default-fancy"]} 18 markdown-weaver-escape = { workspace = true, features = ["std"] } 19 thiserror.workspace = true 20 miette.workspace = true 21 unicode-normalization = "0.1.24" 22 yaml-rust2 = { version = "0.10.2" } 23 bitflags = "2.9.1" 24 - tracing = "0.1" 25 - 26 dashmap = "6.1.0" 27 - regex = "1.11.1" 28 pin-utils = "0.1.0" 29 pin-project = "1.1.10" 30 - dynosaur = "0.2.0" 31 smol_str = { version = "0.3", features = ["serde"] } 32 mime-sniffer = "0.1.3" 33 reqwest = { version = "0.12.7", default-features = false, features = [ ··· 36 ] } 37 38 [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] 39 tokio = { version = "1.28", features = ["rt", "time"] } 40 tokio-util = { version = "0.7.14", features = ["rt"] } 41 ignore = "0.4.23" 42 43 [dev-dependencies] 44 insta = { version = "1.40", features = ["yaml"] }
··· 11 weaver-api = { path = "../weaver-api" } 12 jacquard.workspace = true 13 markdown-weaver = { workspace = true } 14 http = "1.3.1" 15 url = "2.5.4" 16 syntect = { workspace = true, default-features = false, features = ["default-fancy"]} 17 markdown-weaver-escape = { workspace = true, features = ["std"] } 18 thiserror.workspace = true 19 + tracing.workspace = true 20 miette.workspace = true 21 unicode-normalization = "0.1.24" 22 yaml-rust2 = { version = "0.10.2" } 23 bitflags = "2.9.1" 24 dashmap = "6.1.0" 25 pin-utils = "0.1.0" 26 pin-project = "1.1.10" 27 smol_str = { version = "0.3", features = ["serde"] } 28 mime-sniffer = "0.1.3" 29 reqwest = { version = "0.12.7", default-features = false, features = [ ··· 32 ] } 33 34 [target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies] 35 + regex = { version = "1.12" } 36 tokio = { version = "1.28", features = ["rt", "time"] } 37 tokio-util = { version = "0.7.14", features = ["rt"] } 38 ignore = "0.4.23" 39 + 40 + [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 41 + regex-lite = { version = "0.1" } 42 43 [dev-dependencies] 44 insta = { version = "1.40", features = ["yaml"] }
+5 -7
crates/weaver-renderer/src/atproto/client.rs
··· 203 if let Some(embeds) = &entry.embeds { 204 if let Some(images) = &embeds.images { 205 for img in &images.images { 206 if let Some(name) = &img.name { 207 let blob_name = BlobName::from_filename(name.as_ref()); 208 map.insert(blob_name, img.image.blob().cid().clone().into_static()); ··· 227 /// - Bluesky list: `at://{actor}/app.bsky.graph.list/{rkey}` → `https://bsky.app/profile/{actor}/lists/{rkey}` 228 /// - Bluesky feed: `at://{actor}/app.bsky.feed.generator/{rkey}` → `https://bsky.app/profile/{actor}/feed/{rkey}` 229 /// - Bluesky starterpack: `at://{actor}/app.bsky.graph.starterpack/{rkey}` → `https://bsky.app/starter-pack/{actor}/{rkey}` 230 - /// - Weaver/other: `at://{actor}/{collection}/{rkey}` → `https://weaver.sh/{actor}/{collection}/{rkey}` 231 fn at_uri_to_web_url(at_uri: &AtUri<'_>) -> String { 232 let authority = at_uri.authority().as_ref(); 233 234 // Profile-only link (no collection/rkey) 235 if at_uri.collection().is_none() && at_uri.rkey().is_none() { 236 - return format!("https://weaver.sh/{}", authority); 237 } 238 239 // Record link ··· 257 } 258 // Weaver records and unknown collections go to weaver.sh 259 _ => { 260 - format!( 261 - "https://weaver.sh/{}/{}/{}", 262 - authority, collection_str, rkey_str 263 - ) 264 } 265 } 266 } else { 267 // Fallback for malformed URIs 268 - format!("https://weaver.sh/{}", authority) 269 } 270 } 271
··· 203 if let Some(embeds) = &entry.embeds { 204 if let Some(images) = &embeds.images { 205 for img in &images.images { 206 + tracing::info!("Image: {:?}", img); 207 if let Some(name) = &img.name { 208 let blob_name = BlobName::from_filename(name.as_ref()); 209 map.insert(blob_name, img.image.blob().cid().clone().into_static()); ··· 228 /// - Bluesky list: `at://{actor}/app.bsky.graph.list/{rkey}` → `https://bsky.app/profile/{actor}/lists/{rkey}` 229 /// - Bluesky feed: `at://{actor}/app.bsky.feed.generator/{rkey}` → `https://bsky.app/profile/{actor}/feed/{rkey}` 230 /// - Bluesky starterpack: `at://{actor}/app.bsky.graph.starterpack/{rkey}` → `https://bsky.app/starter-pack/{actor}/{rkey}` 231 + /// - Weaver/other: `at://{actor}/{collection}/{rkey}` → `https://weaver.sh/record/{at_uri}` 232 fn at_uri_to_web_url(at_uri: &AtUri<'_>) -> String { 233 let authority = at_uri.authority().as_ref(); 234 235 // Profile-only link (no collection/rkey) 236 if at_uri.collection().is_none() && at_uri.rkey().is_none() { 237 + return format!("https://alpha.weaver.sh/{}", authority); 238 } 239 240 // Record link ··· 258 } 259 // Weaver records and unknown collections go to weaver.sh 260 _ => { 261 + format!("https://alpha.weaver.sh/record/{}", at_uri) 262 } 263 } 264 } else { 265 // Fallback for malformed URIs 266 + format!("https://alpha.weaver.sh/{}", authority) 267 } 268 } 269
+8 -4
crates/weaver-renderer/src/atproto/writer.rs
··· 50 Body, 51 } 52 53 - impl<'a, I: Iterator<Item = Event<'a>>, W: StrWrite, E: EmbedContentProvider> ClientWriter<'a, I, W, E> { 54 pub fn new(events: I, writer: W) -> Self { 55 Self { 56 events, ··· 382 Tag::Superscript => self.write("<sup>"), 383 Tag::Emphasis => self.write("<em>"), 384 Tag::Strong => self.write("<strong>"), 385 - Tag::Strikethrough => self.write("<del>"), 386 Tag::Link { 387 link_type: LinkType::Email, 388 dest_url, ··· 551 TagEnd::Superscript => self.write("</sup>"), 552 TagEnd::Subscript => self.write("</sub>"), 553 TagEnd::Strong => self.write("</strong>"), 554 - TagEnd::Strikethrough => self.write("</del>"), 555 TagEnd::Link => self.write("</a>"), 556 TagEnd::Image => Ok(()), // No-op: raw_text() already consumed the End(Image) event 557 TagEnd::Embed => Ok(()), ··· 568 } 569 } 570 571 - impl<'a, I: Iterator<Item = Event<'a>>, W: StrWrite, E: EmbedContentProvider> ClientWriter<'a, I, W, E> { 572 fn write_embed( 573 &mut self, 574 embed_type: EmbedType,
··· 50 Body, 51 } 52 53 + impl<'a, I: Iterator<Item = Event<'a>>, W: StrWrite, E: EmbedContentProvider> 54 + ClientWriter<'a, I, W, E> 55 + { 56 pub fn new(events: I, writer: W) -> Self { 57 Self { 58 events, ··· 384 Tag::Superscript => self.write("<sup>"), 385 Tag::Emphasis => self.write("<em>"), 386 Tag::Strong => self.write("<strong>"), 387 + Tag::Strikethrough => self.write("<s>"), 388 Tag::Link { 389 link_type: LinkType::Email, 390 dest_url, ··· 553 TagEnd::Superscript => self.write("</sup>"), 554 TagEnd::Subscript => self.write("</sub>"), 555 TagEnd::Strong => self.write("</strong>"), 556 + TagEnd::Strikethrough => self.write("</s>"), 557 TagEnd::Link => self.write("</a>"), 558 TagEnd::Image => Ok(()), // No-op: raw_text() already consumed the End(Image) event 559 TagEnd::Embed => Ok(()), ··· 570 } 571 } 572 573 + impl<'a, I: Iterator<Item = Event<'a>>, W: StrWrite, E: EmbedContentProvider> 574 + ClientWriter<'a, I, W, E> 575 + { 576 fn write_embed( 577 &mut self, 578 embed_type: EmbedType,
+4
crates/weaver-renderer/src/lib.rs
··· 10 use yaml_rust2::Yaml; 11 use yaml_rust2::YamlLoader; 12 13 use regex::Regex; 14 use std::iter::Iterator; 15 use std::path::PathBuf; 16 use std::pin::Pin;
··· 10 use yaml_rust2::Yaml; 11 use yaml_rust2::YamlLoader; 12 13 + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 14 use regex::Regex; 15 + #[cfg(all(target_family = "wasm", target_os = "unknown"))] 16 + use regex_lite::Regex; 17 + 18 use std::iter::Iterator; 19 use std::path::PathBuf; 20 use std::pin::Pin;
+2 -2
crates/weaver-renderer/src/types.rs
··· 1 - use compact_string::CompactString; 2 use http::Uri; 3 use weaver_common::jacquard::types::string::{Cid, Did}; 4 5 pub struct Link<'a> { ··· 7 pub blob: BlobLink<'a>, 8 } 9 10 - pub type MimeType = CompactString; 11 12 pub enum BlobLink<'a> { 13 PDS {
··· 1 use http::Uri; 2 + use smol_str::SmolStr; 3 use weaver_common::jacquard::types::string::{Cid, Did}; 4 5 pub struct Link<'a> { ··· 7 pub blob: BlobLink<'a>, 8 } 9 10 + pub type MimeType = SmolStr; 11 12 pub enum BlobLink<'a> { 13 PDS {
+5
crates/weaver-renderer/src/utils.rs
··· 2 use markdown_weaver::{CodeBlockKind, CowStr, Event, Tag}; 3 use miette::IntoDiagnostic; 4 use n0_future::TryFutureExt; 5 use regex::Regex; 6 use markdown_weaver::BrokenLink; 7 use std::path::PathBuf; 8 use std::sync::Arc;
··· 2 use markdown_weaver::{CodeBlockKind, CowStr, Event, Tag}; 3 use miette::IntoDiagnostic; 4 use n0_future::TryFutureExt; 5 + 6 + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 7 use regex::Regex; 8 + #[cfg(all(target_family = "wasm", target_os = "unknown"))] 9 + use regex_lite::Regex; 10 + 11 use markdown_weaver::BrokenLink; 12 use std::path::PathBuf; 13 use std::sync::Arc;
+1
flake.nix
··· 267 dioxus-cli 268 wasm-bindgen-cli 269 wasm-pack 270 binaryen.out 271 ]; 272 };
··· 267 dioxus-cli 268 wasm-bindgen-cli 269 wasm-pack 270 + twiggy 271 binaryen.out 272 ]; 273 };
+6 -5
weaver_notes/.obsidian/workspace.json
··· 41 "state": { 42 "type": "markdown", 43 "state": { 44 - "file": "bug notes.md", 45 "mode": "source", 46 "source": false 47 }, 48 "icon": "lucide-file", 49 - "title": "bug notes" 50 } 51 } 52 ], ··· 201 }, 202 "active": "6029beecc3d03bce", 203 "lastOpenFiles": [ 204 "bug notes.md", 205 "Why I rewrote pdsls in Rust (tm).md", 206 "meta.png", ··· 212 "pretty_editor.png", 213 "Arch.md", 214 "Weaver - Long-form writing.md", 215 - "weaver_photo_med.jpg", 216 - "xkcd_345_excerpt.png", 217 - "light_mode_excerpt.png" 218 ] 219 }
··· 41 "state": { 42 "type": "markdown", 43 "state": { 44 + "file": "Why I rewrote pdsls in Rust (tm).md", 45 "mode": "source", 46 "source": false 47 }, 48 "icon": "lucide-file", 49 + "title": "Why I rewrote pdsls in Rust (tm)" 50 } 51 } 52 ], ··· 201 }, 202 "active": "6029beecc3d03bce", 203 "lastOpenFiles": [ 204 + "light_mode_excerpt.png", 205 + "notebook_entry_preview.png", 206 + "xkcd_345_excerpt.png", 207 "bug notes.md", 208 "Why I rewrote pdsls in Rust (tm).md", 209 "meta.png", ··· 215 "pretty_editor.png", 216 "Arch.md", 217 "Weaver - Long-form writing.md", 218 + "weaver_photo_med.jpg" 219 ] 220 }
-3
weaver_notes/bug notes.md
··· 1 - - renderer/processor doesn't add http(s):// to urls without them (or handle autolinks currently) 2 - - mobile view doesn't narrow correctly 3 - -
···
weaver_notes/light_mode_excerpt.png

This is a binary file and will not be displayed.

weaver_notes/notebook_entry_preview.png

This is a binary file and will not be displayed.

weaver_notes/xkcd_345_excerpt.png

This is a binary file and will not be displayed.