cli publish workflow and basic minimal renderer/server works end-to-end.

Orual 8e78c389 0e3b5d2b

+523 -601
+49 -202
Cargo.lock
··· 208 ] 209 210 [[package]] 211 - name = "async-lock" 212 - version = "3.4.1" 213 - source = "registry+https://github.com/rust-lang/crates.io-index" 214 - checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" 215 - dependencies = [ 216 - "event-listener", 217 - "event-listener-strategy", 218 - "pin-project-lite", 219 - ] 220 - 221 - [[package]] 222 name = "async-recursion" 223 version = "1.1.1" 224 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 564 565 [[package]] 566 name = "borrow-or-share" 567 - version = "0.2.3" 568 source = "registry+https://github.com/rust-lang/crates.io-index" 569 - checksum = "8fa326467c5d528c03e479661320269e7716d6b7d5d49bafd30890ce0c725696" 570 571 [[package]] 572 name = "borsh" ··· 649 checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 650 651 [[package]] 652 - name = "bytecount" 653 - version = "0.6.9" 654 - source = "registry+https://github.com/rust-lang/crates.io-index" 655 - checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" 656 - 657 - [[package]] 658 name = "byteorder" 659 version = "1.5.0" 660 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 695 ] 696 697 [[package]] 698 - name = "camino" 699 - version = "1.2.1" 700 - source = "registry+https://github.com/rust-lang/crates.io-index" 701 - checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" 702 - dependencies = [ 703 - "serde_core", 704 - ] 705 - 706 - [[package]] 707 - name = "cargo-platform" 708 - version = "0.1.9" 709 - source = "registry+https://github.com/rust-lang/crates.io-index" 710 - checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 711 - dependencies = [ 712 - "serde", 713 - ] 714 - 715 - [[package]] 716 - name = "cargo_metadata" 717 - version = "0.14.2" 718 - source = "registry+https://github.com/rust-lang/crates.io-index" 719 - checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" 720 - dependencies = [ 721 - "camino", 722 - "cargo-platform", 723 - "semver", 724 - "serde", 725 - "serde_json", 726 - ] 727 - 728 - [[package]] 729 name = "cbor4ii" 730 version = "0.2.14" 731 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 998 "libc", 999 "once_cell", 1000 "windows-sys 0.59.0", 1001 ] 1002 1003 [[package]] ··· 1387 1388 [[package]] 1389 name = "dashmap" 1390 - version = "5.5.3" 1391 - source = "registry+https://github.com/rust-lang/crates.io-index" 1392 - checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 1393 - dependencies = [ 1394 - "cfg-if", 1395 - "hashbrown 0.14.5", 1396 - "lock_api", 1397 - "once_cell", 1398 - "parking_lot_core", 1399 - ] 1400 - 1401 - [[package]] 1402 - name = "dashmap" 1403 version = "6.1.0" 1404 source = "registry+https://github.com/rust-lang/crates.io-index" 1405 checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" ··· 1775 "global-hotkey", 1776 "infer", 1777 "jni", 1778 - "lazy-js-bundle 0.7.0", 1779 "libc", 1780 "muda", 1781 "ndk", ··· 1847 "futures-channel", 1848 "futures-util", 1849 "generational-box", 1850 - "lazy-js-bundle 0.7.0", 1851 "serde", 1852 "serde_json", 1853 "tracing", ··· 2006 "futures-util", 2007 "generational-box", 2008 "keyboard-types", 2009 - "lazy-js-bundle 0.7.0", 2010 "rustversion", 2011 "serde", 2012 "serde_json", ··· 2036 "dioxus-core-types", 2037 "dioxus-html", 2038 "js-sys", 2039 - "lazy-js-bundle 0.7.0", 2040 "rustc-hash 2.1.1", 2041 "serde", 2042 "sledgehammer_bindgen", ··· 2087 ] 2088 2089 [[package]] 2090 - name = "dioxus-primitives" 2091 - version = "0.0.1" 2092 - source = "git+https://github.com/DioxusLabs/components#8e25631c7d4234ee070509156ed2abebb7b1d6e9" 2093 - dependencies = [ 2094 - "dioxus", 2095 - "dioxus-time", 2096 - "lazy-js-bundle 0.6.2", 2097 - "num-integer", 2098 - "time", 2099 - "tracing", 2100 - ] 2101 - 2102 - [[package]] 2103 name = "dioxus-router" 2104 version = "0.7.0" 2105 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2160 "bytes", 2161 "chrono", 2162 "ciborium", 2163 - "dashmap 6.1.0", 2164 "dioxus-cli-config", 2165 "dioxus-core", 2166 "dioxus-core-macro", ··· 2257 ] 2258 2259 [[package]] 2260 - name = "dioxus-time" 2261 - version = "0.7.0" 2262 - source = "git+https://github.com/ealmloff/dioxus-std?branch=0.7#e5a74354cd36be440febdd6b701f584bd552bd62" 2263 - dependencies = [ 2264 - "dioxus", 2265 - "futures", 2266 - "gloo-timers", 2267 - "tokio", 2268 - ] 2269 - 2270 - [[package]] 2271 name = "dioxus-web" 2272 version = "0.7.0" 2273 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2288 "generational-box", 2289 "gloo-timers", 2290 "js-sys", 2291 - "lazy-js-bundle 0.7.0", 2292 "rustc-hash 2.1.1", 2293 "send_wrapper", 2294 "serde", ··· 2627 dependencies = [ 2628 "libc", 2629 "windows-sys 0.61.2", 2630 - ] 2631 - 2632 - [[package]] 2633 - name = "error-chain" 2634 - version = "0.12.4" 2635 - source = "registry+https://github.com/rust-lang/crates.io-index" 2636 - checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 2637 - dependencies = [ 2638 - "version_check", 2639 ] 2640 2641 [[package]] ··· 3230 "libc", 3231 "system-deps", 3232 ] 3233 - 3234 - [[package]] 3235 - name = "glob" 3236 - version = "0.3.3" 3237 - source = "registry+https://github.com/rust-lang/crates.io-index" 3238 - checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 3239 3240 [[package]] 3241 name = "global-hotkey" ··· 4017 [[package]] 4018 name = "jacquard" 4019 version = "0.8.0" 4020 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4021 dependencies = [ 4022 "bytes", 4023 "getrandom 0.2.16", ··· 4046 [[package]] 4047 name = "jacquard-api" 4048 version = "0.8.0" 4049 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4050 dependencies = [ 4051 "bon", 4052 "bytes", ··· 4064 [[package]] 4065 name = "jacquard-axum" 4066 version = "0.8.0" 4067 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4068 dependencies = [ 4069 "axum", 4070 "bytes", ··· 4086 [[package]] 4087 name = "jacquard-common" 4088 version = "0.8.0" 4089 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4090 dependencies = [ 4091 "base64 0.22.1", 4092 "bon", ··· 4128 [[package]] 4129 name = "jacquard-derive" 4130 version = "0.8.0" 4131 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4132 dependencies = [ 4133 "heck 0.5.0", 4134 "jacquard-lexicon", ··· 4140 [[package]] 4141 name = "jacquard-identity" 4142 version = "0.8.0" 4143 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4144 dependencies = [ 4145 "bon", 4146 "bytes", ··· 4150 "jacquard-common", 4151 "jacquard-lexicon", 4152 "miette 7.6.0", 4153 - "moka", 4154 "n0-future", 4155 "percent-encoding", 4156 "reqwest", ··· 4167 [[package]] 4168 name = "jacquard-lexicon" 4169 version = "0.8.0" 4170 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4171 dependencies = [ 4172 "cid", 4173 - "dashmap 6.1.0", 4174 "heck 0.5.0", 4175 "inventory", 4176 "jacquard-common", ··· 4193 [[package]] 4194 name = "jacquard-oauth" 4195 version = "0.8.0" 4196 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4197 dependencies = [ 4198 "base64 0.22.1", 4199 "bytes", 4200 "chrono", 4201 - "dashmap 6.1.0", 4202 "elliptic-curve", 4203 "http", 4204 "jacquard-common", ··· 4413 "static-regular-grammar", 4414 "thiserror 1.0.69", 4415 ] 4416 - 4417 - [[package]] 4418 - name = "lazy-js-bundle" 4419 - version = "0.6.2" 4420 - source = "registry+https://github.com/rust-lang/crates.io-index" 4421 - checksum = "e49596223b9d9d4947a14a25c142a6e7d8ab3f27eb3ade269d238bb8b5c267e2" 4422 4423 [[package]] 4424 name = "lazy-js-bundle" ··· 4932 4933 [[package]] 4934 name = "mini-moka" 4935 - version = "0.10.3" 4936 - source = "registry+https://github.com/rust-lang/crates.io-index" 4937 - checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" 4938 dependencies = [ 4939 "crossbeam-channel", 4940 "crossbeam-utils", 4941 - "dashmap 5.5.3", 4942 - "skeptic", 4943 "smallvec", 4944 "tagptr", 4945 "triomphe", 4946 ] 4947 4948 [[package]] ··· 4989 "libc", 4990 "wasi 0.11.1+wasi-snapshot-preview1", 4991 "windows-sys 0.61.2", 4992 - ] 4993 - 4994 - [[package]] 4995 - name = "moka" 4996 - version = "0.12.11" 4997 - source = "registry+https://github.com/rust-lang/crates.io-index" 4998 - checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" 4999 - dependencies = [ 5000 - "async-lock", 5001 - "crossbeam-channel", 5002 - "crossbeam-epoch", 5003 - "crossbeam-utils", 5004 - "equivalent", 5005 - "event-listener", 5006 - "futures-util", 5007 - "parking_lot", 5008 - "portable-atomic", 5009 - "rustc_version", 5010 - "smallvec", 5011 - "tagptr", 5012 - "uuid", 5013 ] 5014 5015 [[package]] ··· 5205 5206 [[package]] 5207 name = "num-bigint-dig" 5208 - version = "0.8.4" 5209 source = "registry+https://github.com/rust-lang/crates.io-index" 5210 - checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 5211 dependencies = [ 5212 - "byteorder", 5213 "lazy_static", 5214 "libm", 5215 "num-integer", ··· 5878 checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 5879 5880 [[package]] 5881 - name = "portable-atomic" 5882 - version = "1.11.1" 5883 - source = "registry+https://github.com/rust-lang/crates.io-index" 5884 - checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 5885 - 5886 - [[package]] 5887 name = "postgres-protocol" 5888 version = "0.6.9" 5889 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6069 ] 6070 6071 [[package]] 6072 - name = "pulldown-cmark" 6073 - version = "0.9.6" 6074 - source = "registry+https://github.com/rust-lang/crates.io-index" 6075 - checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" 6076 - dependencies = [ 6077 - "bitflags 2.10.0", 6078 - "memchr", 6079 - "unicase", 6080 - ] 6081 - 6082 - [[package]] 6083 name = "quick-error" 6084 version = "1.2.3" 6085 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6656 6657 [[package]] 6658 name = "schemars" 6659 - version = "1.0.4" 6660 source = "registry+https://github.com/rust-lang/crates.io-index" 6661 - checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" 6662 dependencies = [ 6663 "dyn-clone", 6664 "ref-cast", ··· 6771 version = "1.0.27" 6772 source = "registry+https://github.com/rust-lang/crates.io-index" 6773 checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 6774 - dependencies = [ 6775 - "serde", 6776 - "serde_core", 6777 - ] 6778 6779 [[package]] 6780 name = "send_wrapper" ··· 6960 "indexmap 1.9.3", 6961 "indexmap 2.12.0", 6962 "schemars 0.9.0", 6963 - "schemars 1.0.4", 6964 "serde_core", 6965 "serde_json", 6966 "serde_with_macros", ··· 7096 checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 7097 7098 [[package]] 7099 - name = "skeptic" 7100 - version = "0.13.7" 7101 - source = "registry+https://github.com/rust-lang/crates.io-index" 7102 - checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 7103 - dependencies = [ 7104 - "bytecount", 7105 - "cargo_metadata", 7106 - "error-chain", 7107 - "glob", 7108 - "pulldown-cmark", 7109 - "tempfile", 7110 - "walkdir", 7111 - ] 7112 - 7113 - [[package]] 7114 name = "slab" 7115 version = "0.4.11" 7116 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7640 dependencies = [ 7641 "deranged", 7642 "itoa", 7643 "libc", 7644 "num-conv", 7645 "num_threads", ··· 7850 7851 [[package]] 7852 name = "tokio-util" 7853 - version = "0.7.16" 7854 source = "registry+https://github.com/rust-lang/crates.io-index" 7855 - checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 7856 dependencies = [ 7857 "bytes", 7858 "futures-core", ··· 8655 "axum", 8656 "chrono", 8657 "clap", 8658 - "dashmap 6.1.0", 8659 "diesel", 8660 "diesel-async", 8661 "diesel_migrations", ··· 8738 dependencies = [ 8739 "bitflags 2.10.0", 8740 "compact_string", 8741 - "dashmap 6.1.0", 8742 "dynosaur", 8743 "http", 8744 "ignore", ··· 8770 version = "0.1.0" 8771 dependencies = [ 8772 "axum", 8773 - "dashmap 6.1.0", 8774 "dioxus", 8775 - "dioxus-primitives", 8776 "jacquard", 8777 "jacquard-axum", 8778 "markdown-weaver", 8779 "mini-moka", 8780 - "moka", 8781 "weaver-api", 8782 "weaver-common", 8783 "weaver-renderer", ··· 8894 8895 [[package]] 8896 name = "webpki-roots" 8897 - version = "1.0.3" 8898 source = "registry+https://github.com/rust-lang/crates.io-index" 8899 - checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" 8900 dependencies = [ 8901 "rustls-pki-types", 8902 ]
··· 208 ] 209 210 [[package]] 211 name = "async-recursion" 212 version = "1.1.1" 213 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 553 554 [[package]] 555 name = "borrow-or-share" 556 + version = "0.2.4" 557 source = "registry+https://github.com/rust-lang/crates.io-index" 558 + checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" 559 560 [[package]] 561 name = "borsh" ··· 638 checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 639 640 [[package]] 641 name = "byteorder" 642 version = "1.5.0" 643 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 678 ] 679 680 [[package]] 681 name = "cbor4ii" 682 version = "0.2.14" 683 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 950 "libc", 951 "once_cell", 952 "windows-sys 0.59.0", 953 + ] 954 + 955 + [[package]] 956 + name = "console_error_panic_hook" 957 + version = "0.1.7" 958 + source = "registry+https://github.com/rust-lang/crates.io-index" 959 + checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 960 + dependencies = [ 961 + "cfg-if", 962 + "wasm-bindgen", 963 ] 964 965 [[package]] ··· 1349 1350 [[package]] 1351 name = "dashmap" 1352 version = "6.1.0" 1353 source = "registry+https://github.com/rust-lang/crates.io-index" 1354 checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" ··· 1724 "global-hotkey", 1725 "infer", 1726 "jni", 1727 + "lazy-js-bundle", 1728 "libc", 1729 "muda", 1730 "ndk", ··· 1796 "futures-channel", 1797 "futures-util", 1798 "generational-box", 1799 + "lazy-js-bundle", 1800 "serde", 1801 "serde_json", 1802 "tracing", ··· 1955 "futures-util", 1956 "generational-box", 1957 "keyboard-types", 1958 + "lazy-js-bundle", 1959 "rustversion", 1960 "serde", 1961 "serde_json", ··· 1985 "dioxus-core-types", 1986 "dioxus-html", 1987 "js-sys", 1988 + "lazy-js-bundle", 1989 "rustc-hash 2.1.1", 1990 "serde", 1991 "sledgehammer_bindgen", ··· 2036 ] 2037 2038 [[package]] 2039 name = "dioxus-router" 2040 version = "0.7.0" 2041 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2096 "bytes", 2097 "chrono", 2098 "ciborium", 2099 + "dashmap", 2100 "dioxus-cli-config", 2101 "dioxus-core", 2102 "dioxus-core-macro", ··· 2193 ] 2194 2195 [[package]] 2196 name = "dioxus-web" 2197 version = "0.7.0" 2198 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2213 "generational-box", 2214 "gloo-timers", 2215 "js-sys", 2216 + "lazy-js-bundle", 2217 "rustc-hash 2.1.1", 2218 "send_wrapper", 2219 "serde", ··· 2552 dependencies = [ 2553 "libc", 2554 "windows-sys 0.61.2", 2555 ] 2556 2557 [[package]] ··· 3146 "libc", 3147 "system-deps", 3148 ] 3149 3150 [[package]] 3151 name = "global-hotkey" ··· 3927 [[package]] 3928 name = "jacquard" 3929 version = "0.8.0" 3930 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 3931 dependencies = [ 3932 "bytes", 3933 "getrandom 0.2.16", ··· 3956 [[package]] 3957 name = "jacquard-api" 3958 version = "0.8.0" 3959 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 3960 dependencies = [ 3961 "bon", 3962 "bytes", ··· 3974 [[package]] 3975 name = "jacquard-axum" 3976 version = "0.8.0" 3977 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 3978 dependencies = [ 3979 "axum", 3980 "bytes", ··· 3996 [[package]] 3997 name = "jacquard-common" 3998 version = "0.8.0" 3999 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4000 dependencies = [ 4001 "base64 0.22.1", 4002 "bon", ··· 4038 [[package]] 4039 name = "jacquard-derive" 4040 version = "0.8.0" 4041 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4042 dependencies = [ 4043 "heck 0.5.0", 4044 "jacquard-lexicon", ··· 4050 [[package]] 4051 name = "jacquard-identity" 4052 version = "0.8.0" 4053 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4054 dependencies = [ 4055 "bon", 4056 "bytes", ··· 4060 "jacquard-common", 4061 "jacquard-lexicon", 4062 "miette 7.6.0", 4063 + "mini-moka", 4064 "n0-future", 4065 "percent-encoding", 4066 "reqwest", ··· 4077 [[package]] 4078 name = "jacquard-lexicon" 4079 version = "0.8.0" 4080 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4081 dependencies = [ 4082 "cid", 4083 + "dashmap", 4084 "heck 0.5.0", 4085 "inventory", 4086 "jacquard-common", ··· 4103 [[package]] 4104 name = "jacquard-oauth" 4105 version = "0.8.0" 4106 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4107 dependencies = [ 4108 "base64 0.22.1", 4109 "bytes", 4110 "chrono", 4111 + "dashmap", 4112 "elliptic-curve", 4113 "http", 4114 "jacquard-common", ··· 4323 "static-regular-grammar", 4324 "thiserror 1.0.69", 4325 ] 4326 4327 [[package]] 4328 name = "lazy-js-bundle" ··· 4836 4837 [[package]] 4838 name = "mini-moka" 4839 + version = "0.11.0" 4840 + source = "git+https://github.com/moka-rs/mini-moka?rev=da864e849f5d034f32e02197fee9bb5d5af36d3d#da864e849f5d034f32e02197fee9bb5d5af36d3d" 4841 dependencies = [ 4842 "crossbeam-channel", 4843 "crossbeam-utils", 4844 + "dashmap", 4845 "smallvec", 4846 "tagptr", 4847 "triomphe", 4848 + "web-time", 4849 ] 4850 4851 [[package]] ··· 4892 "libc", 4893 "wasi 0.11.1+wasi-snapshot-preview1", 4894 "windows-sys 0.61.2", 4895 ] 4896 4897 [[package]] ··· 5087 5088 [[package]] 5089 name = "num-bigint-dig" 5090 + version = "0.8.5" 5091 source = "registry+https://github.com/rust-lang/crates.io-index" 5092 + checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" 5093 dependencies = [ 5094 "lazy_static", 5095 "libm", 5096 "num-integer", ··· 5759 checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 5760 5761 [[package]] 5762 name = "postgres-protocol" 5763 version = "0.6.9" 5764 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5944 ] 5945 5946 [[package]] 5947 name = "quick-error" 5948 version = "1.2.3" 5949 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6520 6521 [[package]] 6522 name = "schemars" 6523 + version = "1.0.5" 6524 source = "registry+https://github.com/rust-lang/crates.io-index" 6525 + checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" 6526 dependencies = [ 6527 "dyn-clone", 6528 "ref-cast", ··· 6635 version = "1.0.27" 6636 source = "registry+https://github.com/rust-lang/crates.io-index" 6637 checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 6638 6639 [[package]] 6640 name = "send_wrapper" ··· 6820 "indexmap 1.9.3", 6821 "indexmap 2.12.0", 6822 "schemars 0.9.0", 6823 + "schemars 1.0.5", 6824 "serde_core", 6825 "serde_json", 6826 "serde_with_macros", ··· 6956 checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 6957 6958 [[package]] 6959 name = "slab" 6960 version = "0.4.11" 6961 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7485 dependencies = [ 7486 "deranged", 7487 "itoa", 7488 + "js-sys", 7489 "libc", 7490 "num-conv", 7491 "num_threads", ··· 7696 7697 [[package]] 7698 name = "tokio-util" 7699 + version = "0.7.17" 7700 source = "registry+https://github.com/rust-lang/crates.io-index" 7701 + checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 7702 dependencies = [ 7703 "bytes", 7704 "futures-core", ··· 8501 "axum", 8502 "chrono", 8503 "clap", 8504 + "dashmap", 8505 "diesel", 8506 "diesel-async", 8507 "diesel_migrations", ··· 8584 dependencies = [ 8585 "bitflags 2.10.0", 8586 "compact_string", 8587 + "dashmap", 8588 "dynosaur", 8589 "http", 8590 "ignore", ··· 8616 version = "0.1.0" 8617 dependencies = [ 8618 "axum", 8619 + "chrono", 8620 + "console_error_panic_hook", 8621 + "dashmap", 8622 "dioxus", 8623 "jacquard", 8624 "jacquard-axum", 8625 "markdown-weaver", 8626 "mini-moka", 8627 + "time", 8628 "weaver-api", 8629 "weaver-common", 8630 "weaver-renderer", ··· 8741 8742 [[package]] 8743 name = "webpki-roots" 8744 + version = "1.0.4" 8745 source = "registry+https://github.com/rust-lang/crates.io-index" 8746 + checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" 8747 dependencies = [ 8748 "rustls-pki-types", 8749 ]
+1
Cargo.toml
··· 36 lexicon_cid = { package = "cid", version = "0.10.1", features = [ 37 "serde-codec", 38 ] } 39 markdown-weaver = { git = "https://github.com/rsform/markdown-weaver" } 40 markdown-weaver-escape = { git = "https://github.com/rsform/markdown-weaver" } 41
··· 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
+26 -21
crates/weaver-cli/src/main.rs
··· 1 use jacquard::client::{Agent, FileAuthStore}; 2 use jacquard::identity::JacquardResolver; 3 use jacquard::oauth::client::{OAuthClient, OAuthSession}; 4 use jacquard::oauth::loopback::LoopbackConfig; 5 use jacquard::prelude::*; 6 use jacquard::types::string::Handle; 7 - use jacquard::IntoStatic; 8 use miette::{IntoDiagnostic, Result}; 9 use std::io::BufRead; 10 use std::path::PathBuf; 11 use std::sync::Arc; 12 - use weaver_renderer::static_site::StaticSiteWriter; 13 - use weaver_renderer::walker::{WalkOptions, vault_contents}; 14 use weaver_renderer::atproto::AtProtoPreprocessContext; 15 use weaver_renderer::utils::VaultBrokenLinkCallback; 16 17 use clap::{Parser, Subcommand}; 18 ··· 51 source: PathBuf, 52 53 /// Notebook title 54 - #[arg(long)] 55 title: String, 56 57 /// Path to auth store file ··· 71 let store_path = store.unwrap_or_else(default_auth_store_path); 72 authenticate(handle, store_path).await?; 73 } 74 - Some(Commands::Publish { source, title, store }) => { 75 let store_path = store.unwrap_or_else(default_auth_store_path); 76 publish_notebook(source, title, store_path).await?; 77 } ··· 227 authenticate(handle, store_path.clone()).await?; 228 229 // Load the session we just created 230 - try_load_session(&store_path).await 231 .ok_or_else(|| miette::miette!("Failed to load session after authentication"))? 232 } 233 }; ··· 236 237 // Create agent and resolve DID document to get handle 238 let agent = Agent::new(session); 239 - let (did, _session_id) = agent.info().await 240 .ok_or_else(|| miette::miette!("No session info available"))?; 241 let did_doc_response = agent.resolve_did_doc(&did).await?; 242 let did_doc = did_doc_response.parse()?; ··· 276 println!("Found {} markdown files", md_files.len()); 277 278 // Create preprocessing context 279 - let context = AtProtoPreprocessContext::new( 280 - vault_arc.clone(), 281 - title.clone(), 282 - agent.clone(), 283 - ).with_creator(did.clone().into_static(), handle.clone().into_static()); 284 285 // Process each file 286 for file_path in &md_files { ··· 299 }); 300 301 // Parse markdown 302 - use weaver_renderer::default_md_options; 303 use markdown_weaver::Parser; 304 - let parser = Parser::new_with_broken_link_callback(&contents, default_md_options(), callback); 305 let iterator = weaver_renderer::ContextIterator::default(parser); 306 307 // Process through NotebookProcessor 308 - use weaver_renderer::{NotebookProcessor, NotebookContext}; 309 use n0_future::StreamExt; 310 let mut processor = NotebookProcessor::new(file_context.clone(), iterator); 311 312 // Write canonical markdown with MarkdownWriter ··· 317 318 // Process all events 319 while let Some(event) = processor.next().await { 320 - md_writer.write_event(event).map_err(|e| { 321 - miette::miette!("Failed to write markdown: {:?}", e) 322 - })?; 323 } 324 325 // Extract blobs and entry metadata ··· 327 let entry_title = file_context.entry_title(); 328 329 // Build Entry record with blobs 330 - use weaver_api::sh_weaver::notebook::entry::{Entry, EntryEmbeds}; 331 - use weaver_api::sh_weaver::embed::images::{Images, Image}; 332 - use jacquard::types::string::Datetime; 333 use jacquard::types::blob::BlobRef; 334 335 let embeds = if !blobs.is_empty() { 336 // Build images from blobs
··· 1 + use jacquard::IntoStatic; 2 use jacquard::client::{Agent, FileAuthStore}; 3 use jacquard::identity::JacquardResolver; 4 use jacquard::oauth::client::{OAuthClient, OAuthSession}; 5 use jacquard::oauth::loopback::LoopbackConfig; 6 use jacquard::prelude::*; 7 use jacquard::types::string::Handle; 8 use miette::{IntoDiagnostic, Result}; 9 use std::io::BufRead; 10 use std::path::PathBuf; 11 use std::sync::Arc; 12 use weaver_renderer::atproto::AtProtoPreprocessContext; 13 + use weaver_renderer::static_site::StaticSiteWriter; 14 use weaver_renderer::utils::VaultBrokenLinkCallback; 15 + use weaver_renderer::walker::{WalkOptions, vault_contents}; 16 17 use clap::{Parser, Subcommand}; 18 ··· 51 source: PathBuf, 52 53 /// Notebook title 54 + //#[arg(long)] 55 title: String, 56 57 /// Path to auth store file ··· 71 let store_path = store.unwrap_or_else(default_auth_store_path); 72 authenticate(handle, store_path).await?; 73 } 74 + Some(Commands::Publish { 75 + source, 76 + title, 77 + store, 78 + }) => { 79 let store_path = store.unwrap_or_else(default_auth_store_path); 80 publish_notebook(source, title, store_path).await?; 81 } ··· 231 authenticate(handle, store_path.clone()).await?; 232 233 // Load the session we just created 234 + try_load_session(&store_path) 235 + .await 236 .ok_or_else(|| miette::miette!("Failed to load session after authentication"))? 237 } 238 }; ··· 241 242 // Create agent and resolve DID document to get handle 243 let agent = Agent::new(session); 244 + let (did, _session_id) = agent 245 + .info() 246 + .await 247 .ok_or_else(|| miette::miette!("No session info available"))?; 248 let did_doc_response = agent.resolve_did_doc(&did).await?; 249 let did_doc = did_doc_response.parse()?; ··· 283 println!("Found {} markdown files", md_files.len()); 284 285 // Create preprocessing context 286 + let context = AtProtoPreprocessContext::new(vault_arc.clone(), title.clone(), agent.clone()) 287 + .with_creator(did.clone().into_static(), handle.clone().into_static()); 288 289 // Process each file 290 for file_path in &md_files { ··· 303 }); 304 305 // Parse markdown 306 use markdown_weaver::Parser; 307 + use weaver_renderer::default_md_options; 308 + let parser = 309 + Parser::new_with_broken_link_callback(&contents, default_md_options(), callback); 310 let iterator = weaver_renderer::ContextIterator::default(parser); 311 312 // Process through NotebookProcessor 313 use n0_future::StreamExt; 314 + use weaver_renderer::{NotebookContext, NotebookProcessor}; 315 let mut processor = NotebookProcessor::new(file_context.clone(), iterator); 316 317 // Write canonical markdown with MarkdownWriter ··· 322 323 // Process all events 324 while let Some(event) = processor.next().await { 325 + md_writer 326 + .write_event(event) 327 + .map_err(|e| miette::miette!("Failed to write markdown: {:?}", e))?; 328 } 329 330 // Extract blobs and entry metadata ··· 332 let entry_title = file_context.entry_title(); 333 334 // Build Entry record with blobs 335 use jacquard::types::blob::BlobRef; 336 + use jacquard::types::string::Datetime; 337 + use weaver_api::sh_weaver::embed::images::{Image, Images}; 338 + use weaver_api::sh_weaver::notebook::entry::{Entry, EntryEmbeds}; 339 340 let embeds = if !blobs.is_empty() { 341 // Build images from blobs
+11 -7
crates/weaver-renderer/src/atproto.rs
··· 3 //! Two-stage pipeline: markdown→markdown preprocessing (CLI), 4 //! then client-side markdown→HTML rendering (WASM). 5 6 mod error; 7 - mod types; 8 mod markdown_writer; 9 mod preprocess; 10 - mod client; 11 - mod embed_renderer; 12 mod writer; 13 14 pub use error::{AtProtoPreprocessError, ClientRenderError}; 15 - pub use types::{BlobName, BlobInfo}; 16 pub use preprocess::AtProtoPreprocessContext; 17 - pub use client::{ClientContext, EmbedResolver, DefaultEmbedResolver}; 18 - pub use markdown_writer::MarkdownWriter; 19 - pub use embed_renderer::{fetch_and_render_profile, fetch_and_render_post, fetch_and_render_generic}; 20 pub use writer::{ClientWriter, EmbedContentProvider};
··· 3 //! Two-stage pipeline: markdown→markdown preprocessing (CLI), 4 //! then client-side markdown→HTML rendering (WASM). 5 6 + mod client; 7 + mod embed_renderer; 8 mod error; 9 mod markdown_writer; 10 + #[cfg(not(target_family = "wasm"))] 11 mod preprocess; 12 + mod types; 13 mod writer; 14 15 + pub use client::{ClientContext, DefaultEmbedResolver, EmbedResolver}; 16 + pub use embed_renderer::{ 17 + fetch_and_render_generic, fetch_and_render_post, fetch_and_render_profile, 18 + }; 19 pub use error::{AtProtoPreprocessError, ClientRenderError}; 20 + pub use markdown_writer::MarkdownWriter; 21 + #[cfg(not(target_family = "wasm"))] 22 pub use preprocess::AtProtoPreprocessContext; 23 + pub use types::{BlobInfo, BlobName}; 24 pub use writer::{ClientWriter, EmbedContentProvider};
+30 -30
crates/weaver-renderer/src/atproto/client.rs
··· 1 use crate::{Frontmatter, NotebookContext}; 2 - use super::{types::BlobName, error::ClientRenderError}; 3 use jacquard::{ 4 client::{Agent, AgentSession}, 5 prelude::IdentityResolver, 6 types::string::{AtUri, Cid, Did}, 7 }; 8 - use markdown_weaver::{Tag, CowStr as MdCowStr, WeaverAttributes}; 9 use std::collections::HashMap; 10 use std::sync::Arc; 11 use weaver_api::sh_weaver::notebook::entry::Entry; ··· 13 /// Trait for resolving embed content on the client side 14 /// 15 /// Implementations can fetch from cache, make HTTP requests, or use other sources. 16 - pub trait EmbedResolver: Send + Sync { 17 /// Resolve a profile embed by AT URI 18 fn resolve_profile( 19 &self, 20 uri: &AtUri<'_>, 21 - ) -> impl std::future::Future<Output = Result<String, ClientRenderError>> + Send; 22 23 /// Resolve a post/record embed by AT URI 24 fn resolve_post( 25 &self, 26 uri: &AtUri<'_>, 27 - ) -> impl std::future::Future<Output = Result<String, ClientRenderError>> + Send; 28 29 /// Resolve a markdown embed from URL 30 /// ··· 33 &self, 34 url: &str, 35 depth: usize, 36 - ) -> impl std::future::Future<Output = Result<String, ClientRenderError>> + Send; 37 } 38 39 /// Default embed resolver that fetches records from PDSs ··· 74 } 75 76 async fn resolve_post(&self, uri: &AtUri<'_>) -> Result<String, ClientRenderError> { 77 - use crate::atproto::{fetch_and_render_post, fetch_and_render_generic}; 78 79 // Check if it's a known type 80 if let Some(collection) = uri.collection() { 81 match collection.as_ref() { 82 "app.bsky.feed.post" => { 83 - fetch_and_render_post(uri, &*self.agent) 84 - .await 85 - .map_err(|e| ClientRenderError::EntryFetch { 86 uri: uri.as_ref().to_string(), 87 source: Box::new(e), 88 - }) 89 - } 90 - _ => { 91 - fetch_and_render_generic(uri, &*self.agent) 92 - .await 93 - .map_err(|e| ClientRenderError::EntryFetch { 94 - uri: uri.as_ref().to_string(), 95 - source: Box::new(e), 96 - }) 97 } 98 } 99 } else { 100 Err(ClientRenderError::EntryFetch { ··· 153 } 154 155 /// Add an embed resolver for fetching embed content 156 - pub fn with_embed_resolver<R: EmbedResolver>( 157 - self, 158 - resolver: Arc<R>, 159 - ) -> ClientContext<'a, R> { 160 ClientContext { 161 entry: self.entry, 162 creator_did: self.creator_did, ··· 247 } 248 // Weaver records and unknown collections go to weaver.sh 249 _ => { 250 - format!("https://weaver.sh/{}/{}/{}", authority, collection_str, rkey_str) 251 } 252 } 253 } else { ··· 279 280 async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> { 281 match &link { 282 - Tag::Link { link_type, dest_url, title, id } => { 283 let url = dest_url.as_ref(); 284 285 // Try to parse as AT URI ··· 418 #[cfg(test)] 419 mod tests { 420 use super::*; 421 use weaver_api::sh_weaver::notebook::entry::Entry; 422 - use jacquard::types::string::{Did, Datetime}; 423 424 #[test] 425 fn test_client_context_creation() { ··· 436 #[test] 437 fn test_at_uri_to_web_url_profile() { 438 let uri = AtUri::new("at://did:plc:xyz123").unwrap(); 439 - assert_eq!( 440 - at_uri_to_web_url(&uri), 441 - "https://weaver.sh/did:plc:xyz123" 442 - ); 443 } 444 445 #[test]
··· 1 + use super::{error::ClientRenderError, types::BlobName}; 2 use crate::{Frontmatter, NotebookContext}; 3 use jacquard::{ 4 client::{Agent, AgentSession}, 5 prelude::IdentityResolver, 6 types::string::{AtUri, Cid, Did}, 7 }; 8 + use markdown_weaver::{CowStr as MdCowStr, Tag, WeaverAttributes}; 9 use std::collections::HashMap; 10 use std::sync::Arc; 11 use weaver_api::sh_weaver::notebook::entry::Entry; ··· 13 /// Trait for resolving embed content on the client side 14 /// 15 /// Implementations can fetch from cache, make HTTP requests, or use other sources. 16 + pub trait EmbedResolver { 17 /// Resolve a profile embed by AT URI 18 fn resolve_profile( 19 &self, 20 uri: &AtUri<'_>, 21 + ) -> impl std::future::Future<Output = Result<String, ClientRenderError>>; 22 23 /// Resolve a post/record embed by AT URI 24 fn resolve_post( 25 &self, 26 uri: &AtUri<'_>, 27 + ) -> impl std::future::Future<Output = Result<String, ClientRenderError>>; 28 29 /// Resolve a markdown embed from URL 30 /// ··· 33 &self, 34 url: &str, 35 depth: usize, 36 + ) -> impl std::future::Future<Output = Result<String, ClientRenderError>>; 37 } 38 39 /// Default embed resolver that fetches records from PDSs ··· 74 } 75 76 async fn resolve_post(&self, uri: &AtUri<'_>) -> Result<String, ClientRenderError> { 77 + use crate::atproto::{fetch_and_render_generic, fetch_and_render_post}; 78 79 // Check if it's a known type 80 if let Some(collection) = uri.collection() { 81 match collection.as_ref() { 82 "app.bsky.feed.post" => { 83 + fetch_and_render_post(uri, &*self.agent).await.map_err(|e| { 84 + ClientRenderError::EntryFetch { 85 uri: uri.as_ref().to_string(), 86 source: Box::new(e), 87 + } 88 + }) 89 } 90 + _ => fetch_and_render_generic(uri, &*self.agent) 91 + .await 92 + .map_err(|e| ClientRenderError::EntryFetch { 93 + uri: uri.as_ref().to_string(), 94 + source: Box::new(e), 95 + }), 96 } 97 } else { 98 Err(ClientRenderError::EntryFetch { ··· 151 } 152 153 /// Add an embed resolver for fetching embed content 154 + pub fn with_embed_resolver<R: EmbedResolver>(self, resolver: Arc<R>) -> ClientContext<'a, R> { 155 ClientContext { 156 entry: self.entry, 157 creator_did: self.creator_did, ··· 242 } 243 // Weaver records and unknown collections go to weaver.sh 244 _ => { 245 + format!( 246 + "https://weaver.sh/{}/{}/{}", 247 + authority, collection_str, rkey_str 248 + ) 249 } 250 } 251 } else { ··· 277 278 async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> { 279 match &link { 280 + Tag::Link { 281 + link_type, 282 + dest_url, 283 + title, 284 + id, 285 + } => { 286 let url = dest_url.as_ref(); 287 288 // Try to parse as AT URI ··· 421 #[cfg(test)] 422 mod tests { 423 use super::*; 424 + use jacquard::types::string::{Datetime, Did}; 425 use weaver_api::sh_weaver::notebook::entry::Entry; 426 427 #[test] 428 fn test_client_context_creation() { ··· 439 #[test] 440 fn test_at_uri_to_web_url_profile() { 441 let uri = AtUri::new("at://did:plc:xyz123").unwrap(); 442 + assert_eq!(at_uri_to_web_url(&uri), "https://weaver.sh/did:plc:xyz123"); 443 } 444 445 #[test]
+156 -114
crates/weaver-renderer/src/atproto/preprocess.rs
··· 1 use crate::{Frontmatter, NotebookContext}; 2 - use super::types::{BlobName, BlobInfo}; 3 use dashmap::DashMap; 4 use jacquard::{ 5 client::{Agent, AgentSession, AgentSessionExt}, 6 prelude::IdentityResolver, 7 types::string::{CowStr, Did, Handle}, 8 }; 9 - use markdown_weaver::{Tag, CowStr as MdCowStr, WeaverAttributes}; 10 - use std::{ 11 - path::PathBuf, 12 - sync::Arc, 13 - }; 14 15 pub struct AtProtoPreprocessContext<A: AgentSession + IdentityResolver> { 16 // Vault information ··· 113 // Stub NotebookContext implementation 114 impl<A: AgentSession + IdentityResolver> NotebookContext for AtProtoPreprocessContext<A> { 115 fn set_entry_title(&self, title: MdCowStr<'_>) { 116 - self.titles.insert(self.current_path.clone(), title.into_static()); 117 } 118 119 fn entry_title(&self) -> MdCowStr<'_> { 120 self.titles 121 .get(&self.current_path) 122 .map(|t| t.value().clone()) 123 - .unwrap_or(MdCowStr::Borrowed("")) 124 } 125 126 fn frontmatter(&self) -> Frontmatter { ··· 131 } 132 133 fn set_frontmatter(&self, frontmatter: Frontmatter) { 134 - self.frontmatter.insert(self.current_path.clone(), frontmatter); 135 } 136 137 async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> { ··· 151 match resolved { 152 LinkUri::Path(path) => { 153 // Local wikilink - look up in vault 154 - if let Some(file_path) = lookup_filename_in_vault(path.as_ref(), &self.vault_contents) { 155 let entry_title = file_path 156 .file_stem() 157 .and_then(|s| s.to_str()) ··· 166 normalized_title 167 ) 168 } else { 169 - format!( 170 - "/{}/{}", 171 - self.notebook_title.as_ref(), 172 - normalized_title 173 - ) 174 }; 175 176 return Tag::Link { ··· 213 214 async fn handle_image<'s>(&self, image: Tag<'s>) -> Tag<'s> { 215 use crate::utils::is_local_path; 216 - use tokio::fs; 217 use jacquard::bytes::Bytes; 218 use jacquard::types::blob::MimeType; 219 use mime_sniffer::MimeTypeSniffer; 220 221 match &image { 222 Tag::Image { ··· 248 // Sniff mime type from data 249 let bytes = Bytes::from(image_data.clone()); 250 let mime = MimeType::new_owned( 251 - bytes.sniff_mime_type().unwrap_or("application/octet-stream") 252 ); 253 254 // Upload blob (dereference Arc) ··· 309 match resolved { 310 LinkUri::Path(path) => { 311 // Entry embed - look up in vault 312 - if let Some(file_path) = lookup_filename_in_vault(path.as_ref(), &self.vault_contents) { 313 let entry_title = file_path 314 .file_stem() 315 .and_then(|s| s.to_str()) ··· 324 normalized_title 325 ) 326 } else { 327 - format!( 328 - "/{}/{}", 329 - self.notebook_title.as_ref(), 330 - normalized_title 331 - ) 332 }; 333 334 return Tag::Embed { 335 embed_type: *embed_type, ··· 363 }); 364 365 if let Some(content_html) = content { 366 - new_attrs.attrs.push(("content".into(), content_html.into())); 367 } 368 369 return Tag::Embed { ··· 376 } 377 LinkUri::AtRecord(uri) => { 378 // AT URI embed - fetch and render 379 - use crate::atproto::{fetch_and_render_post, fetch_and_render_generic}; 380 use markdown_weaver::WeaverAttributes; 381 382 // Determine if this is a known type ··· 387 match fetch_and_render_post(&uri, &*self.agent).await { 388 Ok(html) => Some(html), 389 Err(e) => { 390 - eprintln!("Failed to fetch post {}: {}", uri.as_ref(), e); 391 None 392 } 393 } ··· 397 match fetch_and_render_generic(&uri, &*self.agent).await { 398 Ok(html) => Some(html), 399 Err(e) => { 400 - eprintln!("Failed to fetch record {}: {}", uri.as_ref(), e); 401 None 402 } 403 } ··· 414 }); 415 416 if let Some(content_html) = content { 417 - new_attrs.attrs.push(("content".into(), content_html.into())); 418 } 419 420 return Tag::Embed { ··· 425 attrs: Some(new_attrs), 426 }; 427 } 428 - LinkUri::Path(path) => { 429 - // Markdown embed - look up in vault and render 430 - use crate::utils::lookup_filename_in_vault; 431 - use tokio::fs; 432 433 - // Check depth limit 434 - const MAX_DEPTH: usize = 1; 435 - if self.embed_depth >= MAX_DEPTH { 436 - eprintln!("Max embed depth reached for {}", path.as_ref()); 437 - return embed.clone(); 438 - } 439 - 440 - if let Some(file_path) = lookup_filename_in_vault(path.as_ref(), &self.vault_contents) { 441 - // Read the markdown file 442 - match fs::read_to_string(&file_path).await { 443 - Ok(markdown_content) => { 444 - // Create a child context with incremented depth 445 - let mut child_ctx = self.with_depth(self.embed_depth + 1); 446 - child_ctx.current_path = file_path.clone(); 447 - 448 - // Render the markdown through the processor 449 - // We'll use markdown_weaver to parse and render to HTML 450 - use markdown_weaver::{Parser, Options}; 451 - use markdown_weaver_escape::StrWrite; 452 - 453 - let parser = Parser::new_ext(&markdown_content, Options::all()); 454 - let mut html_output = String::new(); 455 - 456 - // Process events through context callbacks 457 - for event in parser { 458 - match event { 459 - markdown_weaver::Event::Start(tag) => { 460 - let processed = match tag { 461 - Tag::Link { .. } => child_ctx.handle_link(tag).await, 462 - Tag::Image { .. } => child_ctx.handle_image(tag).await, 463 - Tag::Embed { .. } => child_ctx.handle_embed(tag).await, 464 - _ => tag, 465 - }; 466 - // Simple HTML writing (reuse escape logic) 467 - match processed { 468 - Tag::Paragraph => html_output.write_str("<p>").ok(), 469 - _ => None, 470 - }; 471 - } 472 - markdown_weaver::Event::End(tag_end) => { 473 - match tag_end { 474 - markdown_weaver::TagEnd::Paragraph => html_output.write_str("</p>\n").ok(), 475 - _ => None, 476 - }; 477 - } 478 - markdown_weaver::Event::Text(text) => { 479 - use markdown_weaver_escape::escape_html_body_text; 480 - escape_html_body_text(&mut html_output, &text).ok(); 481 - } 482 - _ => {} 483 - } 484 - } 485 - 486 - let mut new_attrs = attrs.clone().unwrap_or_else(|| WeaverAttributes { 487 - classes: vec![], 488 - attrs: vec![], 489 - }); 490 - 491 - new_attrs.attrs.push(("content".into(), html_output.into())); 492 - 493 - return Tag::Embed { 494 - embed_type: *embed_type, 495 - dest_url: dest_url.clone(), 496 - title: title.clone(), 497 - id: id.clone(), 498 - attrs: Some(new_attrs), 499 - }; 500 - } 501 - Err(e) => { 502 - eprintln!("Failed to read file {:?}: {}", file_path, e); 503 - } 504 - } 505 - } 506 - } 507 _ => {} 508 } 509 510 // Pass through other embed types 511 embed.clone() 512 } 513 - Tag::Image { 514 - link_type, 515 - dest_url, 516 - title, 517 - id, 518 - attrs, 519 - } => { 520 // Some embeds come through as explicit Tag::Image 521 // Delegate to handle_image for image-specific processing 522 self.handle_image(embed).await ··· 530 } 531 532 fn add_reference(&self, reference: MdCowStr<'_>) { 533 - self.reference_map.insert(reference.into_static(), self.current_path.clone()); 534 } 535 } 536
··· 1 + use super::types::{BlobInfo, BlobName}; 2 use crate::{Frontmatter, NotebookContext}; 3 use dashmap::DashMap; 4 use jacquard::{ 5 client::{Agent, AgentSession, AgentSessionExt}, 6 prelude::IdentityResolver, 7 types::string::{CowStr, Did, Handle}, 8 }; 9 + use markdown_weaver::{CowStr as MdCowStr, Tag, WeaverAttributes}; 10 + use std::{path::PathBuf, sync::Arc}; 11 + use yaml_rust2::Yaml; 12 13 pub struct AtProtoPreprocessContext<A: AgentSession + IdentityResolver> { 14 // Vault information ··· 111 // Stub NotebookContext implementation 112 impl<A: AgentSession + IdentityResolver> NotebookContext for AtProtoPreprocessContext<A> { 113 fn set_entry_title(&self, title: MdCowStr<'_>) { 114 + let path = self.current_path.clone(); 115 + self.titles 116 + .insert(path.clone(), title.clone().into_static()); 117 + self.frontmatter.get_mut(&path).map(|frontmatter| { 118 + if let Ok(mut yaml) = frontmatter.yaml.write() { 119 + if yaml.get(0).is_some_and(|y| y.is_hash()) { 120 + let map = yaml.get_mut(0).unwrap().as_mut_hash().unwrap(); 121 + map.insert( 122 + Yaml::String("title".into()), 123 + Yaml::String(title.into_static().into()), 124 + ); 125 + } 126 + } 127 + }); 128 } 129 130 fn entry_title(&self) -> MdCowStr<'_> { 131 self.titles 132 .get(&self.current_path) 133 .map(|t| t.value().clone()) 134 + .unwrap_or_else(|| { 135 + // Fall back to file stem if no explicit title set 136 + let title = self 137 + .current_path 138 + .file_stem() 139 + .and_then(|s| s.to_str()) 140 + .map(|s| MdCowStr::Borrowed(s)) 141 + .unwrap_or(MdCowStr::Borrowed("Untitled")); 142 + 143 + // Cache the derived title 144 + self.titles 145 + .insert(self.current_path.clone(), title.clone().into_static()); 146 + title 147 + }) 148 } 149 150 fn frontmatter(&self) -> Frontmatter { ··· 155 } 156 157 fn set_frontmatter(&self, frontmatter: Frontmatter) { 158 + self.frontmatter 159 + .insert(self.current_path.clone(), frontmatter); 160 } 161 162 async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> { ··· 176 match resolved { 177 LinkUri::Path(path) => { 178 // Local wikilink - look up in vault 179 + if let Some(file_path) = 180 + lookup_filename_in_vault(path.as_ref(), &self.vault_contents) 181 + { 182 let entry_title = file_path 183 .file_stem() 184 .and_then(|s| s.to_str()) ··· 193 normalized_title 194 ) 195 } else { 196 + format!("/{}/{}", self.notebook_title.as_ref(), normalized_title) 197 }; 198 199 return Tag::Link { ··· 236 237 async fn handle_image<'s>(&self, image: Tag<'s>) -> Tag<'s> { 238 use crate::utils::is_local_path; 239 use jacquard::bytes::Bytes; 240 use jacquard::types::blob::MimeType; 241 use mime_sniffer::MimeTypeSniffer; 242 + use tokio::fs; 243 244 match &image { 245 Tag::Image { ··· 271 // Sniff mime type from data 272 let bytes = Bytes::from(image_data.clone()); 273 let mime = MimeType::new_owned( 274 + bytes 275 + .sniff_mime_type() 276 + .unwrap_or("application/octet-stream"), 277 ); 278 279 // Upload blob (dereference Arc) ··· 334 match resolved { 335 LinkUri::Path(path) => { 336 // Entry embed - look up in vault 337 + if let Some(file_path) = 338 + lookup_filename_in_vault(path.as_ref(), &self.vault_contents) 339 + { 340 let entry_title = file_path 341 .file_stem() 342 .and_then(|s| s.to_str()) ··· 351 normalized_title 352 ) 353 } else { 354 + format!("/{}/{}", self.notebook_title.as_ref(), normalized_title) 355 }; 356 + // Markdown embed - look up in vault and render 357 + //use tokio::fs; 358 + 359 + // Check depth limit 360 + const MAX_DEPTH: usize = 1; 361 + if self.embed_depth >= MAX_DEPTH { 362 + eprintln!("Max embed depth reached for {}", path.as_ref()); 363 + return Tag::Embed { 364 + embed_type: *embed_type, 365 + dest_url: MdCowStr::Boxed(canonical_url.into_boxed_str()), 366 + title: title.clone(), 367 + id: id.clone(), 368 + attrs: attrs.clone(), 369 + }; 370 + } 371 + // // Read the markdown file 372 + // match fs::read_to_string(&file_path).await { 373 + // Ok(markdown_content) => { 374 + // // Create a child context with incremented depth 375 + // let mut child_ctx = self.with_depth(self.embed_depth + 1); 376 + // child_ctx.current_path = file_path.clone(); 377 + 378 + // // Render the markdown through the processor 379 + // // We'll use markdown_weaver to parse and render to HTML 380 + // use markdown_weaver::{Options, Parser}; 381 + // use markdown_weaver_escape::StrWrite; 382 + 383 + // let parser = Parser::new_ext(&markdown_content, Options::all()); 384 + // let mut html_output = String::new(); 385 + 386 + // // Process events through context callbacks 387 + // for event in parser { 388 + // match event { 389 + // markdown_weaver::Event::Start(tag) => { 390 + // let processed = match tag { 391 + // Tag::Link { .. } => { 392 + // child_ctx.handle_link(tag).await 393 + // } 394 + // Tag::Image { .. } => { 395 + // child_ctx.handle_image(tag).await 396 + // } 397 + // Tag::Embed { .. } => { 398 + // child_ctx.handle_embed(tag).await 399 + // } 400 + // _ => tag, 401 + // }; 402 + // // Simple HTML writing (reuse escape logic) 403 + // match processed { 404 + // Tag::Paragraph => { 405 + // html_output.write_str("<p>").ok() 406 + // } 407 + // _ => None, 408 + // }; 409 + // } 410 + // markdown_weaver::Event::End(tag_end) => { 411 + // match tag_end { 412 + // markdown_weaver::TagEnd::Paragraph => { 413 + // html_output.write_str("</p>\n").ok() 414 + // } 415 + // _ => None, 416 + // }; 417 + // } 418 + // markdown_weaver::Event::Text(text) => { 419 + // use markdown_weaver_escape::escape_html_body_text; 420 + // escape_html_body_text(&mut html_output, &text).ok(); 421 + // } 422 + // _ => {} 423 + // } 424 + // } 425 + 426 + // let mut new_attrs = 427 + // attrs.clone().unwrap_or_else(|| WeaverAttributes { 428 + // classes: vec![], 429 + // attrs: vec![], 430 + // }); 431 + 432 + // new_attrs.attrs.push(("content".into(), html_output.into())); 433 + 434 + // return Tag::Embed { 435 + // embed_type: *embed_type, 436 + // dest_url: MdCowStr::Boxed(canonical_url.into_boxed_str()), 437 + // title: title.clone(), 438 + // id: id.clone(), 439 + // attrs: Some(new_attrs), 440 + // }; 441 + // } 442 + // Err(e) => { 443 + // eprintln!("Failed to read file {:?}: {}", file_path, e); 444 + // } 445 + // } 446 447 return Tag::Embed { 448 embed_type: *embed_type, ··· 476 }); 477 478 if let Some(content_html) = content { 479 + new_attrs 480 + .attrs 481 + .push(("content".into(), content_html.into())); 482 } 483 484 return Tag::Embed { ··· 491 } 492 LinkUri::AtRecord(uri) => { 493 // AT URI embed - fetch and render 494 + use crate::atproto::{fetch_and_render_generic, fetch_and_render_post}; 495 use markdown_weaver::WeaverAttributes; 496 497 // Determine if this is a known type ··· 502 match fetch_and_render_post(&uri, &*self.agent).await { 503 Ok(html) => Some(html), 504 Err(e) => { 505 + eprintln!( 506 + "Failed to fetch post {}: {}", 507 + uri.as_ref(), 508 + e 509 + ); 510 None 511 } 512 } ··· 516 match fetch_and_render_generic(&uri, &*self.agent).await { 517 Ok(html) => Some(html), 518 Err(e) => { 519 + eprintln!( 520 + "Failed to fetch record {}: {}", 521 + uri.as_ref(), 522 + e 523 + ); 524 None 525 } 526 } ··· 537 }); 538 539 if let Some(content_html) = content { 540 + new_attrs 541 + .attrs 542 + .push(("content".into(), content_html.into())); 543 } 544 545 return Tag::Embed { ··· 550 attrs: Some(new_attrs), 551 }; 552 } 553 554 _ => {} 555 } 556 557 // Pass through other embed types 558 embed.clone() 559 } 560 + Tag::Image { .. } => { 561 // Some embeds come through as explicit Tag::Image 562 // Delegate to handle_image for image-specific processing 563 self.handle_image(embed).await ··· 571 } 572 573 fn add_reference(&self, reference: MdCowStr<'_>) { 574 + self.reference_map 575 + .insert(reference.into_static(), self.current_path.clone()); 576 } 577 } 578
+1 -2
crates/weaver-renderer/src/css.rs
··· 1 use crate::theme::Theme; 2 use miette::IntoDiagnostic; 3 use std::io::Cursor; 4 - use syntect::highlighting::{Theme as SyntectTheme, ThemeSet}; 5 use syntect::html::{ClassStyle, css_for_theme_with_class_style}; 6 - use syntect::parsing::SyntaxSet; 7 use weaver_api::com_atproto::sync::get_blob::GetBlob; 8 use weaver_api::sh_weaver::notebook::theme::ThemeCodeTheme; 9 use weaver_common::jacquard::client::BasicClient;
··· 1 use crate::theme::Theme; 2 use miette::IntoDiagnostic; 3 use std::io::Cursor; 4 + use syntect::highlighting::ThemeSet; 5 use syntect::html::{ClassStyle, css_for_theme_with_class_style}; 6 use weaver_api::com_atproto::sync::get_blob::GetBlob; 7 use weaver_api::sh_weaver::notebook::theme::ThemeCodeTheme; 8 use weaver_common::jacquard::client::BasicClient;
+3 -6
crates/weaver-renderer/src/static_site.rs
··· 9 pub mod document; 10 pub mod writer; 11 12 use crate::{ 13 ContextIterator, NotebookProcessor, 14 static_site::{ ··· 16 document::{CssMode, write_document_footer, write_document_head}, 17 writer::StaticPageWriter, 18 }, 19 - theme::defaultTheme, 20 utils::flatten_dir_to_just_one_parent, 21 walker::{WalkOptions, vault_contents}, 22 }; ··· 30 path::{Path, PathBuf}, 31 sync::Arc, 32 }; 33 - use crate::utils::VaultBrokenLinkCallback; 34 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 35 use tokio::io::AsyncWriteExt; 36 use unicode_normalization::UnicodeNormalization; ··· 66 | Self::NORMALIZE_DIR_NAMES 67 } 68 } 69 - 70 71 pub struct StaticSiteWriter<A> 72 where ··· 240 .await 241 .into_diagnostic()?; 242 243 - let default_theme = defaultTheme(); 244 let theme = self.context.theme.as_deref().unwrap_or(&default_theme); 245 246 // Write base.css ··· 408 409 Ok(()) 410 } 411 - 412 - 413 414 #[cfg(test)] 415 mod tests;
··· 9 pub mod document; 10 pub mod writer; 11 12 + use crate::utils::VaultBrokenLinkCallback; 13 use crate::{ 14 ContextIterator, NotebookProcessor, 15 static_site::{ ··· 17 document::{CssMode, write_document_footer, write_document_head}, 18 writer::StaticPageWriter, 19 }, 20 + theme::default_theme, 21 utils::flatten_dir_to_just_one_parent, 22 walker::{WalkOptions, vault_contents}, 23 }; ··· 31 path::{Path, PathBuf}, 32 sync::Arc, 33 }; 34 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 35 use tokio::io::AsyncWriteExt; 36 use unicode_normalization::UnicodeNormalization; ··· 66 | Self::NORMALIZE_DIR_NAMES 67 } 68 } 69 70 pub struct StaticSiteWriter<A> 71 where ··· 239 .await 240 .into_diagnostic()?; 241 242 + let default_theme = default_theme(); 243 let theme = self.context.theme.as_deref().unwrap_or(&default_theme); 244 245 // Write base.css ··· 407 408 Ok(()) 409 } 410 411 #[cfg(test)] 412 mod tests;
+2 -2
crates/weaver-renderer/src/static_site/document.rs
··· 1 use crate::css::{generate_base_css, generate_syntax_css}; 2 use crate::static_site::context::{KaTeXSource, StaticSiteContext}; 3 - use crate::theme::{Theme, defaultTheme}; 4 use miette::IntoDiagnostic; 5 use weaver_common::jacquard::client::AgentSession; 6 ··· 98 .into_diagnostic()?; 99 } 100 CssMode::Inline => { 101 - let default_theme = defaultTheme(); 102 let theme = context.theme.as_deref().unwrap_or(&default_theme); 103 104 writer.write_all(b" <style>\n").await.into_diagnostic()?;
··· 1 use crate::css::{generate_base_css, generate_syntax_css}; 2 use crate::static_site::context::{KaTeXSource, StaticSiteContext}; 3 + use crate::theme::{Theme, default_theme}; 4 use miette::IntoDiagnostic; 5 use weaver_common::jacquard::client::AgentSession; 6 ··· 98 .into_diagnostic()?; 99 } 100 CssMode::Inline => { 101 + let default_theme = default_theme(); 102 let theme = context.theme.as_deref().unwrap_or(&default_theme); 103 104 writer.write_all(b" <style>\n").await.into_diagnostic()?;
+5 -5
crates/weaver-renderer/src/theme.rs
··· 4 use weaver_common::jacquard::CowStr; 5 use weaver_common::jacquard::cowstr::ToCowStr; 6 7 - pub fn defaultTheme() -> Theme<'static> { 8 Theme::new() 9 .code_theme(ThemeCodeTheme::CodeThemeName(Box::new( 10 "rose-pine".to_cowstr(), 11 ))) 12 .colours(ThemeColours { 13 - background: CowStr::new_static("#faf4ed"), 14 - foreground: CowStr::new_static("#2b303b"), 15 - link: CowStr::new_static("#286983"), 16 - link_hover: CowStr::new_static("#56949f"), 17 primary: CowStr::new_static("#c4a7e7"), 18 secondary: CowStr::new_static("#3e8fb0"), 19
··· 4 use weaver_common::jacquard::CowStr; 5 use weaver_common::jacquard::cowstr::ToCowStr; 6 7 + pub fn default_theme() -> Theme<'static> { 8 Theme::new() 9 .code_theme(ThemeCodeTheme::CodeThemeName(Box::new( 10 "rose-pine".to_cowstr(), 11 ))) 12 .colours(ThemeColours { 13 + background: CowStr::new_static("#191724"), 14 + foreground: CowStr::new_static("#e0def4"), 15 + link: CowStr::new_static("#31748f"), 16 + link_hover: CowStr::new_static("#9ccfd8"), 17 primary: CowStr::new_static("#c4a7e7"), 18 secondary: CowStr::new_static("#3e8fb0"), 19
+18 -14
crates/weaver-server/Cargo.toml
··· 4 authors = ["Orual <orual@nonbinary.computer>"] 5 edition = "2021" 6 7 - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 9 [dependencies] 10 dashmap = "6.1.0" ··· 15 weaver-api = { path = "../weaver-api", features = ["streaming"] } 16 markdown-weaver = { workspace = true } 17 weaver-renderer = { path = "../weaver-renderer" } 18 - moka = { version = "0.12", features = ["future"], optional = true } 19 - mini-moka = { version = "0.10" } 20 - dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false } 21 axum = {version = "0.8.6", optional = true} 22 23 - [features] 24 - default = ["web"] 25 - # The feature that are only required for the web = ["dioxus/web"] build target should be optional and only enabled in the web = ["dioxus/web"] feature 26 - web = ["dioxus/web"] 27 - # The feature that are only required for the desktop = ["dioxus/desktop"] build target should be optional and only enabled in the desktop = ["dioxus/desktop"] feature 28 - desktop = ["dioxus/desktop"] 29 - # The feature that are only required for the mobile = ["dioxus/mobile"] build target should be optional and only enabled in the mobile = ["dioxus/mobile"] feature 30 - mobile = ["dioxus/mobile"] 31 - # The feature that are only required for the server = ["dioxus/server"] build target should be optional and only enabled in the server = ["dioxus/server"] feature 32 - server = ["dioxus/server", "dep:jacquard-axum", "dep:moka", "dep:axum"]
··· 4 authors = ["Orual <orual@nonbinary.computer>"] 5 edition = "2021" 6 7 + [features] 8 + default = ["web"] 9 + # The feature that are only required for the web = ["dioxus/web"] build target should be optional and only enabled in the web = ["dioxus/web"] feature 10 + web = ["dioxus/web", "chrono/wasmbind"] 11 + # The feature that are only required for the desktop = ["dioxus/desktop"] build target should be optional and only enabled in the desktop = ["dioxus/desktop"] feature 12 + desktop = ["dioxus/desktop"] 13 + # The feature that are only required for the mobile = ["dioxus/mobile"] build target should be optional and only enabled in the mobile = ["dioxus/mobile"] feature 14 + mobile = ["dioxus/mobile"] 15 + # The feature that are only required for the server = ["dioxus/server"] build target should be optional and only enabled in the server = ["dioxus/server"] feature 16 + server = ["dioxus/server", "dep:jacquard-axum", "dep:axum"] 17 18 [dependencies] 19 dashmap = "6.1.0" ··· 24 weaver-api = { path = "../weaver-api", features = ["streaming"] } 25 markdown-weaver = { workspace = true } 26 weaver-renderer = { path = "../weaver-renderer" } 27 + mini-moka = { git = "https://github.com/moka-rs/mini-moka", rev = "da864e849f5d034f32e02197fee9bb5d5af36d3d" } 28 + #dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false } 29 axum = {version = "0.8.6", optional = true} 30 31 + chrono = { version = "0.4" } 32 + 33 + [target.'cfg(target_arch = "wasm32")'.dependencies] 34 + time = { version = "0.3", features = ["wasm-bindgen"] } 35 + console_error_panic_hook = "0.1" 36 + mini-moka = { git = "https://github.com/moka-rs/mini-moka", rev = "da864e849f5d034f32e02197fee9bb5d5af36d3d", features = ["js"] }
+96
crates/weaver-server/src/cache_impl.rs
···
··· 1 + //! Platform-specific cache implementations 2 + //! Native uses sync cache (thread-safe, no mutex needed) 3 + //! WASM uses unsync cache wrapped in Arc<Mutex<>> (no threads, but need interior mutability) 4 + 5 + #[cfg(not(target_arch = "wasm32"))] 6 + mod native { 7 + use std::time::Duration; 8 + 9 + pub type Cache<K, V> = mini_moka::sync::Cache<K, V>; 10 + 11 + pub fn new_cache<K, V>(max_capacity: u64, ttl: Duration) -> Cache<K, V> 12 + where 13 + K: std::hash::Hash + Eq + Send + Sync + 'static, 14 + V: Clone + Send + Sync + 'static, 15 + { 16 + mini_moka::sync::Cache::builder() 17 + .max_capacity(max_capacity) 18 + .time_to_live(ttl) 19 + .build() 20 + } 21 + 22 + pub fn get<K, V>(cache: &Cache<K, V>, key: &K) -> Option<V> 23 + where 24 + K: std::hash::Hash + Eq + Send + Sync + 'static, 25 + V: Clone + Send + Sync + 'static, 26 + { 27 + cache.get(key) 28 + } 29 + 30 + pub fn insert<K, V>(cache: &Cache<K, V>, key: K, value: V) 31 + where 32 + K: std::hash::Hash + Eq + Send + Sync + 'static, 33 + V: Clone + Send + Sync + 'static, 34 + { 35 + cache.insert(key, value); 36 + } 37 + 38 + pub fn iter<K, V>(cache: &Cache<K, V>) -> Vec<V> 39 + where 40 + K: std::hash::Hash + Eq + Send + Sync + 'static, 41 + V: Clone + Send + Sync + 'static, 42 + { 43 + cache.iter().map(|entry| entry.value().clone()).collect() 44 + } 45 + } 46 + 47 + #[cfg(target_arch = "wasm32")] 48 + mod wasm { 49 + use std::sync::{Arc, Mutex}; 50 + use std::time::Duration; 51 + 52 + pub type Cache<K, V> = Arc<Mutex<mini_moka::unsync::Cache<K, V>>>; 53 + 54 + pub fn new_cache<K, V>(max_capacity: u64, ttl: Duration) -> Cache<K, V> 55 + where 56 + K: std::hash::Hash + Eq + 'static, 57 + V: Clone + 'static, 58 + { 59 + Arc::new(Mutex::new( 60 + mini_moka::unsync::Cache::builder() 61 + .max_capacity(max_capacity) 62 + .time_to_live(ttl) 63 + .build(), 64 + )) 65 + } 66 + 67 + pub fn get<K, V>(cache: &Cache<K, V>, key: &K) -> Option<V> 68 + where 69 + K: std::hash::Hash + Eq + 'static, 70 + V: Clone + 'static, 71 + { 72 + cache.lock().unwrap().get(key).cloned() 73 + } 74 + 75 + pub fn insert<K, V>(cache: &Cache<K, V>, key: K, value: V) 76 + where 77 + K: std::hash::Hash + Eq + 'static, 78 + V: Clone + 'static, 79 + { 80 + cache.lock().unwrap().insert(key, value); 81 + } 82 + 83 + pub fn iter<K, V>(cache: &Cache<K, V>) -> Vec<V> 84 + where 85 + K: std::hash::Hash + Eq + 'static, 86 + V: Clone + 'static, 87 + { 88 + cache.lock().unwrap().iter().map(|(_, v)| v.clone()).collect() 89 + } 90 + } 91 + 92 + #[cfg(not(target_arch = "wasm32"))] 93 + pub use native::*; 94 + 95 + #[cfg(target_arch = "wasm32")] 96 + pub use wasm::*;
+20 -9
crates/weaver-server/src/components/css.rs
··· 1 #[allow(unused_imports)] 2 use crate::fetch; 3 - use dioxus::prelude::*; 4 #[allow(unused_imports)] 5 - use dioxus::{fullstack::extract::Extension, fullstack::get_server_url, CapturedError}; 6 use jacquard::smol_str::SmolStr; 7 #[allow(unused_imports)] 8 use std::sync::Arc; 9 #[allow(unused_imports)] 10 use weaver_renderer::theme::Theme; 11 12 #[component] 13 pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { ··· 19 } 20 21 #[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::CachedFetcher>>)] 22 - pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<String> { 23 use jacquard::client::AgentSessionExt; 24 use jacquard::types::ident::AtIdentifier; 25 use jacquard::{from_data, CowStr}; 26 27 use weaver_api::sh_weaver::notebook::book::Book; 28 use weaver_renderer::css::{generate_base_css, generate_syntax_css}; 29 - use weaver_renderer::theme::defaultTheme; 30 31 let ident = AtIdentifier::new_owned(ident)?; 32 let theme = if let Some(notebook) = fetcher.get_notebook(ident, notebook).await? { ··· 36 theme 37 .into_output() 38 .map(|t| t.value) 39 - .unwrap_or(defaultTheme()) 40 } else { 41 - defaultTheme() 42 } 43 } else { 44 - defaultTheme() 45 } 46 } else { 47 - defaultTheme() 48 }; 49 let mut css = generate_base_css(&theme); 50 css.push_str( ··· 53 .map_err(|e| CapturedError::from_display(e)) 54 .unwrap_or_default(), 55 ); 56 - Ok(css) 57 }
··· 1 #[allow(unused_imports)] 2 use crate::fetch; 3 #[allow(unused_imports)] 4 + use dioxus::{fullstack::get_server_url, CapturedError}; 5 + use dioxus::{ 6 + fullstack::{ 7 + headers::ContentType, 8 + http::header::CONTENT_TYPE, 9 + response::{self, Response}, 10 + }, 11 + prelude::*, 12 + }; 13 use jacquard::smol_str::SmolStr; 14 #[allow(unused_imports)] 15 use std::sync::Arc; 16 #[allow(unused_imports)] 17 use weaver_renderer::theme::Theme; 18 + 19 + #[cfg(feature = "server")] 20 + use axum::{extract::Extension, response::IntoResponse}; 21 22 #[component] 23 pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { ··· 29 } 30 31 #[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::CachedFetcher>>)] 32 + pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<Response> { 33 use jacquard::client::AgentSessionExt; 34 use jacquard::types::ident::AtIdentifier; 35 use jacquard::{from_data, CowStr}; 36 37 use weaver_api::sh_weaver::notebook::book::Book; 38 use weaver_renderer::css::{generate_base_css, generate_syntax_css}; 39 + use weaver_renderer::theme::default_theme; 40 41 let ident = AtIdentifier::new_owned(ident)?; 42 let theme = if let Some(notebook) = fetcher.get_notebook(ident, notebook).await? { ··· 46 theme 47 .into_output() 48 .map(|t| t.value) 49 + .unwrap_or(default_theme()) 50 } else { 51 + default_theme() 52 } 53 } else { 54 + default_theme() 55 } 56 } else { 57 + default_theme() 58 }; 59 let mut css = generate_base_css(&theme); 60 css.push_str( ··· 63 .map_err(|e| CapturedError::from_display(e)) 64 .unwrap_or_default(), 65 ); 66 + 67 + Ok(([(CONTENT_TYPE, "text/css")], css).into_response()) 68 }
+30 -12
crates/weaver-server/src/components/entry.rs
··· 47 entry 48 })); 49 50 - rsx! { 51 - match &*entry.read_unchecked() { 52 - Some(Some(entry)) => { 53 - let class = use_signal(|| String::from("entry")); 54 - let content = use_signal(||entry.1.clone()); 55 - rsx! { EntryMarkdown { 56 - class, 57 - content 58 - } } 59 - }, 60 - Some(None) => rsx! { p { "Loading entry failed" } }, 61 - None => rsx! { p { "Loading..." } } 62 } 63 } 64 } 65 ··· 90 div { 91 id: "{&*props.id.read()}", 92 class: "{&*props.class.read()}", 93 dangerous_inner_html: "{html_buf}" 94 } 95 }
··· 47 entry 48 })); 49 50 + match &*entry.read_unchecked() { 51 + Some(Some(entry_data)) => { 52 + rsx! { EntryMarkdownDirect { 53 + content: entry_data.1.clone() 54 + } } 55 + }, 56 + Some(None) => { 57 + rsx! { div { class: "error", "Entry not found" } } 58 } 59 + None => rsx! { p { "Loading..." } } 60 } 61 } 62 ··· 87 div { 88 id: "{&*props.id.read()}", 89 class: "{&*props.class.read()}", 90 + dangerous_inner_html: "{html_buf}" 91 + } 92 + } 93 + } 94 + 95 + /// Render entry content directly without signals 96 + #[component] 97 + fn EntryMarkdownDirect( 98 + #[props(default)] id: String, 99 + #[props(default = "entry".to_string())] class: String, 100 + content: entry::Entry<'static>, 101 + ) -> Element { 102 + let parser = markdown_weaver::Parser::new(&content.content); 103 + 104 + let mut html_buf = String::new(); 105 + markdown_weaver::html::push_html(&mut html_buf, parser); 106 + 107 + rsx! { 108 + div { 109 + id: "{id}", 110 + class: "{class}", 111 dangerous_inner_html: "{html_buf}" 112 } 113 }
+2 -2
crates/weaver-server/src/components/mod.rs
··· 2 //! They can be used to defined common UI elements like buttons, forms, and modals. In this template, we define a Hero 3 //! component and an Echo component for fullstack apps to be used in our app. 4 5 - mod css; 6 pub use css::NotebookCss; 7 8 mod entry; ··· 10 11 mod identity; 12 pub use identity::{Repository, RepositoryIndex}; 13 - pub mod avatar;
··· 2 //! They can be used to defined common UI elements like buttons, forms, and modals. In this template, we define a Hero 3 //! component and an Echo component for fullstack apps to be used in our app. 4 5 + pub mod css; 6 pub use css::NotebookCss; 7 8 mod entry; ··· 10 11 mod identity; 12 pub use identity::{Repository, RepositoryIndex}; 13 + //pub mod avatar;
+40 -152
crates/weaver-server/src/fetch.rs
··· 1 use dioxus::Result; 2 use jacquard::{client::BasicClient, smol_str::SmolStr, types::ident::AtIdentifier}; 3 - use std::{ 4 - sync::{Arc, Mutex}, 5 - time::Duration, 6 - }; 7 use weaver_api::{ 8 com_atproto::repo::strong_ref::StrongRef, 9 sh_weaver::notebook::{entry::Entry, BookEntryView, NotebookView}, ··· 13 #[derive(Clone)] 14 pub struct CachedFetcher { 15 pub client: Arc<BasicClient>, 16 - #[cfg(not(feature = "server"))] 17 - book_cache: Arc< 18 - Mutex< 19 - mini_moka::unsync::Cache< 20 - (AtIdentifier<'static>, SmolStr), 21 - Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 22 - >, 23 - >, 24 - >, 25 - #[cfg(not(feature = "server"))] 26 - entry_cache: Arc< 27 - Mutex< 28 - mini_moka::unsync::Cache< 29 - (AtIdentifier<'static>, SmolStr), 30 - Arc<(BookEntryView<'static>, Entry<'static>)>, 31 - >, 32 - >, 33 - >, 34 - #[cfg(feature = "server")] 35 - book_cache: Arc< 36 - Mutex< 37 - mini_moka::sync::Cache< 38 - (AtIdentifier<'static>, SmolStr), 39 - Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 40 - >, 41 - >, 42 >, 43 - #[cfg(feature = "server")] 44 - entry_cache: Arc< 45 - Mutex< 46 - mini_moka::sync::Cache< 47 - (AtIdentifier<'static>, SmolStr), 48 - Arc<(BookEntryView<'static>, Entry<'static>)>, 49 - >, 50 - >, 51 >, 52 } 53 54 impl CachedFetcher { 55 - #[cfg(not(feature = "server"))] 56 pub fn new(client: Arc<BasicClient>) -> Self { 57 - let book_cache = mini_moka::unsync::Cache::builder() 58 - .max_capacity(100) 59 - .time_to_idle(Duration::from_secs(1200)) 60 - .build(); 61 - let entry_cache = mini_moka::unsync::Cache::builder() 62 - .max_capacity(100) 63 - .time_to_idle(Duration::from_secs(600)) 64 - .build(); 65 - 66 Self { 67 client, 68 - book_cache: Arc::new(Mutex::new(book_cache)), 69 - entry_cache: Arc::new(Mutex::new(entry_cache)), 70 - } 71 - } 72 - 73 - #[cfg(feature = "server")] 74 - pub fn new(client: Arc<BasicClient>) -> Self { 75 - let book_cache = mini_moka::sync::Cache::builder() 76 - .max_capacity(100) 77 - .time_to_idle(Duration::from_secs(1200)) 78 - .build(); 79 - let entry_cache = mini_moka::sync::Cache::builder() 80 - .max_capacity(100) 81 - .time_to_idle(Duration::from_secs(600)) 82 - .build(); 83 - 84 - Self { 85 - client, 86 - book_cache: Arc::new(Mutex::new(book_cache)), 87 - entry_cache: Arc::new(Mutex::new(entry_cache)), 88 } 89 } 90 ··· 93 ident: AtIdentifier<'static>, 94 title: SmolStr, 95 ) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 96 - if let Ok(mut book_cache) = self.book_cache.lock() { 97 - if let Some(entry) = book_cache.get(&(ident.clone(), title.clone())) { 98 - Ok(Some(entry.clone())) 99 } else { 100 - if let Some((notebook, entries)) = 101 - notebook_by_title(self.client.clone(), &ident, &title) 102 - .await 103 - .map_err(|e| dioxus::CapturedError::from_display(e))? 104 - { 105 - let stored = Arc::new((notebook, entries)); 106 - book_cache.insert((ident.clone(), title.into()), stored.clone()); 107 - Ok(Some(stored)) 108 - } else { 109 - Ok(None) 110 - } 111 } 112 - } else { 113 - Ok(None) 114 } 115 } 116 ··· 122 ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> { 123 if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 124 let (notebook, entries) = result.as_ref(); 125 - if let Ok(mut entry_cache) = self.entry_cache.lock() { 126 - if let Some(entry) = entry_cache.get(&(ident.clone(), entry_title.clone())) { 127 - Ok(Some(entry.clone())) 128 } else { 129 - if let Some(entry) = entry_by_title( 130 - self.client.clone(), 131 - notebook, 132 - entries.as_ref(), 133 - &entry_title, 134 - ) 135 - .await 136 - .map_err(|e| dioxus::CapturedError::from_display(e))? 137 - { 138 - let stored = Arc::new(entry); 139 - entry_cache.insert((ident.clone(), entry_title.into()), stored.clone()); 140 - Ok(Some(stored)) 141 - } else { 142 - Ok(None) 143 - } 144 } 145 - } else { 146 - Ok(None) 147 } 148 } else { 149 Ok(None) 150 } 151 } 152 153 - #[cfg(not(feature = "server"))] 154 pub fn list_recent_entries(&self) -> Vec<Arc<(BookEntryView<'static>, Entry<'static>)>> { 155 - if let Ok(entry_cache) = self.entry_cache.lock() { 156 - let mut entries = Vec::new(); 157 - for (_, entry) in entry_cache.iter() { 158 - entries.push(entry.clone()); 159 - } 160 - entries 161 - } else { 162 - Vec::new() 163 - } 164 - } 165 - 166 - #[cfg(not(feature = "server"))] 167 - pub fn list_recent_notebooks( 168 - &self, 169 - ) -> Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>> { 170 - if let Ok(book_cache) = self.book_cache.lock() { 171 - let mut entries = Vec::new(); 172 - for (_, entry) in book_cache.iter() { 173 - entries.push(entry.clone()); 174 - } 175 - entries 176 - } else { 177 - Vec::new() 178 - } 179 - } 180 - 181 - #[cfg(feature = "server")] 182 - pub fn list_recent_entries(&self) -> Vec<Arc<(BookEntryView<'static>, Entry<'static>)>> { 183 - if let Ok(entry_cache) = self.entry_cache.lock() { 184 - let mut entries = Vec::new(); 185 - for entry in entry_cache.iter() { 186 - entries.push(entry.clone()); 187 - } 188 - entries 189 - } else { 190 - Vec::new() 191 - } 192 } 193 194 - #[cfg(feature = "server")] 195 pub fn list_recent_notebooks( 196 &self, 197 ) -> Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>> { 198 - if let Ok(book_cache) = self.book_cache.lock() { 199 - let mut entries = Vec::new(); 200 - for entry in book_cache.iter() { 201 - entries.push(entry.clone()); 202 - } 203 - entries 204 - } else { 205 - Vec::new() 206 - } 207 } 208 }
··· 1 + use crate::cache_impl; 2 use dioxus::Result; 3 use jacquard::{client::BasicClient, smol_str::SmolStr, types::ident::AtIdentifier}; 4 + use std::{sync::Arc, time::Duration}; 5 use weaver_api::{ 6 com_atproto::repo::strong_ref::StrongRef, 7 sh_weaver::notebook::{entry::Entry, BookEntryView, NotebookView}, ··· 11 #[derive(Clone)] 12 pub struct CachedFetcher { 13 pub client: Arc<BasicClient>, 14 + book_cache: cache_impl::Cache< 15 + (AtIdentifier<'static>, SmolStr), 16 + Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 17 >, 18 + entry_cache: cache_impl::Cache< 19 + (AtIdentifier<'static>, SmolStr), 20 + Arc<(BookEntryView<'static>, Entry<'static>)>, 21 >, 22 } 23 24 impl CachedFetcher { 25 pub fn new(client: Arc<BasicClient>) -> Self { 26 Self { 27 client, 28 + book_cache: cache_impl::new_cache(100, Duration::from_secs(1200)), 29 + entry_cache: cache_impl::new_cache(100, Duration::from_secs(600)), 30 } 31 } 32 ··· 35 ident: AtIdentifier<'static>, 36 title: SmolStr, 37 ) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> { 38 + if let Some(entry) = cache_impl::get(&self.book_cache, &(ident.clone(), title.clone())) { 39 + Ok(Some(entry)) 40 + } else { 41 + if let Some((notebook, entries)) = 42 + notebook_by_title(self.client.clone(), &ident, &title) 43 + .await 44 + .map_err(|e| dioxus::CapturedError::from_display(e))? 45 + { 46 + let stored = Arc::new((notebook, entries)); 47 + cache_impl::insert(&self.book_cache, (ident, title), stored.clone()); 48 + Ok(Some(stored)) 49 } else { 50 + Ok(None) 51 } 52 } 53 } 54 ··· 60 ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> { 61 if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 62 let (notebook, entries) = result.as_ref(); 63 + if let Some(entry) = cache_impl::get(&self.entry_cache, &(ident.clone(), entry_title.clone())) { 64 + Ok(Some(entry)) 65 + } else { 66 + if let Some(entry) = entry_by_title( 67 + self.client.clone(), 68 + notebook, 69 + entries.as_ref(), 70 + &entry_title, 71 + ) 72 + .await 73 + .map_err(|e| dioxus::CapturedError::from_display(e))? 74 + { 75 + let stored = Arc::new(entry); 76 + cache_impl::insert(&self.entry_cache, (ident, entry_title), stored.clone()); 77 + Ok(Some(stored)) 78 } else { 79 + Ok(None) 80 } 81 } 82 } else { 83 Ok(None) 84 } 85 } 86 87 pub fn list_recent_entries(&self) -> Vec<Arc<(BookEntryView<'static>, Entry<'static>)>> { 88 + cache_impl::iter(&self.entry_cache) 89 } 90 91 pub fn list_recent_notebooks( 92 &self, 93 ) -> Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>> { 94 + cache_impl::iter(&self.book_cache) 95 } 96 }
+33 -23
crates/weaver-server/src/main.rs
··· 7 use views::{Home, Navbar, Notebook, NotebookIndex, NotebookPage}; 8 9 mod blobcache; 10 /// Define a components module that contains all shared components for our app. 11 mod components; 12 mod fetch; ··· 49 const MAIN_CSS: Asset = asset!("/assets/styling/main.css"); 50 51 fn main() { 52 // Run `serve()` on the server only 53 #[cfg(feature = "server")] 54 dioxus::serve(|| async move { 55 - use axum::{extract::Request, middleware, middleware::Next}; 56 - // Create a new router for our app using the `router` function 57 - let mut router = dioxus::server::router(App).layer(middleware::from_fn( 58 - |mut req: Request, next: Next| async move { 59 - // Attach some extra state to the request 60 61 - use crate::blobcache::BlobCache; 62 - use crate::fetch::CachedFetcher; 63 - use std::convert::Infallible; 64 - use std::sync::Arc; 65 - req.extensions_mut() 66 - .insert(Arc::new(CachedFetcher::new(Arc::new( 67 - BasicClient::unauthenticated(), 68 - )))); 69 - req.extensions_mut() 70 - .insert(Arc::new(BlobCache::new(Arc::new( 71 - BasicClient::unauthenticated(), 72 - )))); 73 74 - // And then return the response with `next.run() 75 - Ok::<_, Infallible>(next.run(req).await) 76 - }, 77 - )); 78 79 - // .. customize the router, adding layers and new routes 80 - 81 // And then return the router 82 Ok(router) 83 });
··· 7 use views::{Home, Navbar, Notebook, NotebookIndex, NotebookPage}; 8 9 mod blobcache; 10 + mod cache_impl; 11 /// Define a components module that contains all shared components for our app. 12 mod components; 13 mod fetch; ··· 50 const MAIN_CSS: Asset = asset!("/assets/styling/main.css"); 51 52 fn main() { 53 + // Set up better panic messages for wasm 54 + #[cfg(target_arch = "wasm32")] 55 + console_error_panic_hook::set_once(); 56 + 57 // Run `serve()` on the server only 58 #[cfg(feature = "server")] 59 dioxus::serve(|| async move { 60 + use crate::blobcache::BlobCache; 61 + use crate::fetch::CachedFetcher; 62 + use axum::{ 63 + extract::{Extension, Request}, 64 + middleware, 65 + middleware::Next, 66 + }; 67 + use std::convert::Infallible; 68 + use std::sync::Arc; 69 70 + // Create shared state 71 + let fetcher = Arc::new(CachedFetcher::new(Arc::new(BasicClient::unauthenticated()))); 72 + let blob_cache = Arc::new(BlobCache::new(Arc::new(BasicClient::unauthenticated()))); 73 74 + // Create a new router for our app using the `router` function 75 + let router = dioxus::server::router(App).layer(middleware::from_fn({ 76 + let fetcher = fetcher.clone(); 77 + let blob_cache = blob_cache.clone(); 78 + move |mut req: Request, next: Next| { 79 + let fetcher = fetcher.clone(); 80 + let blob_cache = blob_cache.clone(); 81 + async move { 82 + // Attach extensions for dioxus server functions 83 + req.extensions_mut().insert(fetcher); 84 + req.extensions_mut().insert(blob_cache); 85 86 + // And then return the response with `next.run() 87 + Ok::<_, Infallible>(next.run(req).await) 88 + } 89 + } 90 + })); 91 // And then return the router 92 Ok(router) 93 });