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

Orual 8e78c389 0e3b5d2b

+523 -601
+49 -202
Cargo.lock
··· 208 208 ] 209 209 210 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 211 name = "async-recursion" 223 212 version = "1.1.1" 224 213 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 564 553 565 554 [[package]] 566 555 name = "borrow-or-share" 567 - version = "0.2.3" 556 + version = "0.2.4" 568 557 source = "registry+https://github.com/rust-lang/crates.io-index" 569 - checksum = "8fa326467c5d528c03e479661320269e7716d6b7d5d49bafd30890ce0c725696" 558 + checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" 570 559 571 560 [[package]] 572 561 name = "borsh" ··· 649 638 checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 650 639 651 640 [[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 641 name = "byteorder" 659 642 version = "1.5.0" 660 643 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 695 678 ] 696 679 697 680 [[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 681 name = "cbor4ii" 730 682 version = "0.2.14" 731 683 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 998 950 "libc", 999 951 "once_cell", 1000 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", 1001 963 ] 1002 964 1003 965 [[package]] ··· 1387 1349 1388 1350 [[package]] 1389 1351 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 1352 version = "6.1.0" 1404 1353 source = "registry+https://github.com/rust-lang/crates.io-index" 1405 1354 checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" ··· 1775 1724 "global-hotkey", 1776 1725 "infer", 1777 1726 "jni", 1778 - "lazy-js-bundle 0.7.0", 1727 + "lazy-js-bundle", 1779 1728 "libc", 1780 1729 "muda", 1781 1730 "ndk", ··· 1847 1796 "futures-channel", 1848 1797 "futures-util", 1849 1798 "generational-box", 1850 - "lazy-js-bundle 0.7.0", 1799 + "lazy-js-bundle", 1851 1800 "serde", 1852 1801 "serde_json", 1853 1802 "tracing", ··· 2006 1955 "futures-util", 2007 1956 "generational-box", 2008 1957 "keyboard-types", 2009 - "lazy-js-bundle 0.7.0", 1958 + "lazy-js-bundle", 2010 1959 "rustversion", 2011 1960 "serde", 2012 1961 "serde_json", ··· 2036 1985 "dioxus-core-types", 2037 1986 "dioxus-html", 2038 1987 "js-sys", 2039 - "lazy-js-bundle 0.7.0", 1988 + "lazy-js-bundle", 2040 1989 "rustc-hash 2.1.1", 2041 1990 "serde", 2042 1991 "sledgehammer_bindgen", ··· 2087 2036 ] 2088 2037 2089 2038 [[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 2039 name = "dioxus-router" 2104 2040 version = "0.7.0" 2105 2041 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2160 2096 "bytes", 2161 2097 "chrono", 2162 2098 "ciborium", 2163 - "dashmap 6.1.0", 2099 + "dashmap", 2164 2100 "dioxus-cli-config", 2165 2101 "dioxus-core", 2166 2102 "dioxus-core-macro", ··· 2257 2193 ] 2258 2194 2259 2195 [[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 2196 name = "dioxus-web" 2272 2197 version = "0.7.0" 2273 2198 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2288 2213 "generational-box", 2289 2214 "gloo-timers", 2290 2215 "js-sys", 2291 - "lazy-js-bundle 0.7.0", 2216 + "lazy-js-bundle", 2292 2217 "rustc-hash 2.1.1", 2293 2218 "send_wrapper", 2294 2219 "serde", ··· 2627 2552 dependencies = [ 2628 2553 "libc", 2629 2554 "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 2555 ] 2640 2556 2641 2557 [[package]] ··· 3230 3146 "libc", 3231 3147 "system-deps", 3232 3148 ] 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 3149 3240 3150 [[package]] 3241 3151 name = "global-hotkey" ··· 4017 3927 [[package]] 4018 3928 name = "jacquard" 4019 3929 version = "0.8.0" 4020 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 3930 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4021 3931 dependencies = [ 4022 3932 "bytes", 4023 3933 "getrandom 0.2.16", ··· 4046 3956 [[package]] 4047 3957 name = "jacquard-api" 4048 3958 version = "0.8.0" 4049 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 3959 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4050 3960 dependencies = [ 4051 3961 "bon", 4052 3962 "bytes", ··· 4064 3974 [[package]] 4065 3975 name = "jacquard-axum" 4066 3976 version = "0.8.0" 4067 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 3977 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4068 3978 dependencies = [ 4069 3979 "axum", 4070 3980 "bytes", ··· 4086 3996 [[package]] 4087 3997 name = "jacquard-common" 4088 3998 version = "0.8.0" 4089 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 3999 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4090 4000 dependencies = [ 4091 4001 "base64 0.22.1", 4092 4002 "bon", ··· 4128 4038 [[package]] 4129 4039 name = "jacquard-derive" 4130 4040 version = "0.8.0" 4131 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4041 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4132 4042 dependencies = [ 4133 4043 "heck 0.5.0", 4134 4044 "jacquard-lexicon", ··· 4140 4050 [[package]] 4141 4051 name = "jacquard-identity" 4142 4052 version = "0.8.0" 4143 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4053 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4144 4054 dependencies = [ 4145 4055 "bon", 4146 4056 "bytes", ··· 4150 4060 "jacquard-common", 4151 4061 "jacquard-lexicon", 4152 4062 "miette 7.6.0", 4153 - "moka", 4063 + "mini-moka", 4154 4064 "n0-future", 4155 4065 "percent-encoding", 4156 4066 "reqwest", ··· 4167 4077 [[package]] 4168 4078 name = "jacquard-lexicon" 4169 4079 version = "0.8.0" 4170 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4080 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4171 4081 dependencies = [ 4172 4082 "cid", 4173 - "dashmap 6.1.0", 4083 + "dashmap", 4174 4084 "heck 0.5.0", 4175 4085 "inventory", 4176 4086 "jacquard-common", ··· 4193 4103 [[package]] 4194 4104 name = "jacquard-oauth" 4195 4105 version = "0.8.0" 4196 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#10d719d2a2c459ab562b2be6fecdfe11f52b521c" 4106 + source = "git+https://tangled.org/@nonbinary.computer/jacquard#71aae4e8c9a174927d3404fdcf9031547ca89691" 4197 4107 dependencies = [ 4198 4108 "base64 0.22.1", 4199 4109 "bytes", 4200 4110 "chrono", 4201 - "dashmap 6.1.0", 4111 + "dashmap", 4202 4112 "elliptic-curve", 4203 4113 "http", 4204 4114 "jacquard-common", ··· 4413 4323 "static-regular-grammar", 4414 4324 "thiserror 1.0.69", 4415 4325 ] 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 4326 4423 4327 [[package]] 4424 4328 name = "lazy-js-bundle" ··· 4932 4836 4933 4837 [[package]] 4934 4838 name = "mini-moka" 4935 - version = "0.10.3" 4936 - source = "registry+https://github.com/rust-lang/crates.io-index" 4937 - checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" 4839 + version = "0.11.0" 4840 + source = "git+https://github.com/moka-rs/mini-moka?rev=da864e849f5d034f32e02197fee9bb5d5af36d3d#da864e849f5d034f32e02197fee9bb5d5af36d3d" 4938 4841 dependencies = [ 4939 4842 "crossbeam-channel", 4940 4843 "crossbeam-utils", 4941 - "dashmap 5.5.3", 4942 - "skeptic", 4844 + "dashmap", 4943 4845 "smallvec", 4944 4846 "tagptr", 4945 4847 "triomphe", 4848 + "web-time", 4946 4849 ] 4947 4850 4948 4851 [[package]] ··· 4989 4892 "libc", 4990 4893 "wasi 0.11.1+wasi-snapshot-preview1", 4991 4894 "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 4895 ] 5014 4896 5015 4897 [[package]] ··· 5205 5087 5206 5088 [[package]] 5207 5089 name = "num-bigint-dig" 5208 - version = "0.8.4" 5090 + version = "0.8.5" 5209 5091 source = "registry+https://github.com/rust-lang/crates.io-index" 5210 - checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 5092 + checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" 5211 5093 dependencies = [ 5212 - "byteorder", 5213 5094 "lazy_static", 5214 5095 "libm", 5215 5096 "num-integer", ··· 5878 5759 checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 5879 5760 5880 5761 [[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 5762 name = "postgres-protocol" 5888 5763 version = "0.6.9" 5889 5764 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6069 5944 ] 6070 5945 6071 5946 [[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 5947 name = "quick-error" 6084 5948 version = "1.2.3" 6085 5949 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6656 6520 6657 6521 [[package]] 6658 6522 name = "schemars" 6659 - version = "1.0.4" 6523 + version = "1.0.5" 6660 6524 source = "registry+https://github.com/rust-lang/crates.io-index" 6661 - checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" 6525 + checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" 6662 6526 dependencies = [ 6663 6527 "dyn-clone", 6664 6528 "ref-cast", ··· 6771 6635 version = "1.0.27" 6772 6636 source = "registry+https://github.com/rust-lang/crates.io-index" 6773 6637 checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 6774 - dependencies = [ 6775 - "serde", 6776 - "serde_core", 6777 - ] 6778 6638 6779 6639 [[package]] 6780 6640 name = "send_wrapper" ··· 6960 6820 "indexmap 1.9.3", 6961 6821 "indexmap 2.12.0", 6962 6822 "schemars 0.9.0", 6963 - "schemars 1.0.4", 6823 + "schemars 1.0.5", 6964 6824 "serde_core", 6965 6825 "serde_json", 6966 6826 "serde_with_macros", ··· 7096 6956 checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 7097 6957 7098 6958 [[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 6959 name = "slab" 7115 6960 version = "0.4.11" 7116 6961 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7640 7485 dependencies = [ 7641 7486 "deranged", 7642 7487 "itoa", 7488 + "js-sys", 7643 7489 "libc", 7644 7490 "num-conv", 7645 7491 "num_threads", ··· 7850 7696 7851 7697 [[package]] 7852 7698 name = "tokio-util" 7853 - version = "0.7.16" 7699 + version = "0.7.17" 7854 7700 source = "registry+https://github.com/rust-lang/crates.io-index" 7855 - checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 7701 + checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 7856 7702 dependencies = [ 7857 7703 "bytes", 7858 7704 "futures-core", ··· 8655 8501 "axum", 8656 8502 "chrono", 8657 8503 "clap", 8658 - "dashmap 6.1.0", 8504 + "dashmap", 8659 8505 "diesel", 8660 8506 "diesel-async", 8661 8507 "diesel_migrations", ··· 8738 8584 dependencies = [ 8739 8585 "bitflags 2.10.0", 8740 8586 "compact_string", 8741 - "dashmap 6.1.0", 8587 + "dashmap", 8742 8588 "dynosaur", 8743 8589 "http", 8744 8590 "ignore", ··· 8770 8616 version = "0.1.0" 8771 8617 dependencies = [ 8772 8618 "axum", 8773 - "dashmap 6.1.0", 8619 + "chrono", 8620 + "console_error_panic_hook", 8621 + "dashmap", 8774 8622 "dioxus", 8775 - "dioxus-primitives", 8776 8623 "jacquard", 8777 8624 "jacquard-axum", 8778 8625 "markdown-weaver", 8779 8626 "mini-moka", 8780 - "moka", 8627 + "time", 8781 8628 "weaver-api", 8782 8629 "weaver-common", 8783 8630 "weaver-renderer", ··· 8894 8741 8895 8742 [[package]] 8896 8743 name = "webpki-roots" 8897 - version = "1.0.3" 8744 + version = "1.0.4" 8898 8745 source = "registry+https://github.com/rust-lang/crates.io-index" 8899 - checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" 8746 + checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" 8900 8747 dependencies = [ 8901 8748 "rustls-pki-types", 8902 8749 ]
+1
Cargo.toml
··· 36 36 lexicon_cid = { package = "cid", version = "0.10.1", features = [ 37 37 "serde-codec", 38 38 ] } 39 + 39 40 markdown-weaver = { git = "https://github.com/rsform/markdown-weaver" } 40 41 markdown-weaver-escape = { git = "https://github.com/rsform/markdown-weaver" } 41 42
+26 -21
crates/weaver-cli/src/main.rs
··· 1 + use jacquard::IntoStatic; 1 2 use jacquard::client::{Agent, FileAuthStore}; 2 3 use jacquard::identity::JacquardResolver; 3 4 use jacquard::oauth::client::{OAuthClient, OAuthSession}; 4 5 use jacquard::oauth::loopback::LoopbackConfig; 5 6 use jacquard::prelude::*; 6 7 use jacquard::types::string::Handle; 7 - use jacquard::IntoStatic; 8 8 use miette::{IntoDiagnostic, Result}; 9 9 use std::io::BufRead; 10 10 use std::path::PathBuf; 11 11 use std::sync::Arc; 12 - use weaver_renderer::static_site::StaticSiteWriter; 13 - use weaver_renderer::walker::{WalkOptions, vault_contents}; 14 12 use weaver_renderer::atproto::AtProtoPreprocessContext; 13 + use weaver_renderer::static_site::StaticSiteWriter; 15 14 use weaver_renderer::utils::VaultBrokenLinkCallback; 15 + use weaver_renderer::walker::{WalkOptions, vault_contents}; 16 16 17 17 use clap::{Parser, Subcommand}; 18 18 ··· 51 51 source: PathBuf, 52 52 53 53 /// Notebook title 54 - #[arg(long)] 54 + //#[arg(long)] 55 55 title: String, 56 56 57 57 /// Path to auth store file ··· 71 71 let store_path = store.unwrap_or_else(default_auth_store_path); 72 72 authenticate(handle, store_path).await?; 73 73 } 74 - Some(Commands::Publish { source, title, store }) => { 74 + Some(Commands::Publish { 75 + source, 76 + title, 77 + store, 78 + }) => { 75 79 let store_path = store.unwrap_or_else(default_auth_store_path); 76 80 publish_notebook(source, title, store_path).await?; 77 81 } ··· 227 231 authenticate(handle, store_path.clone()).await?; 228 232 229 233 // Load the session we just created 230 - try_load_session(&store_path).await 234 + try_load_session(&store_path) 235 + .await 231 236 .ok_or_else(|| miette::miette!("Failed to load session after authentication"))? 232 237 } 233 238 }; ··· 236 241 237 242 // Create agent and resolve DID document to get handle 238 243 let agent = Agent::new(session); 239 - let (did, _session_id) = agent.info().await 244 + let (did, _session_id) = agent 245 + .info() 246 + .await 240 247 .ok_or_else(|| miette::miette!("No session info available"))?; 241 248 let did_doc_response = agent.resolve_did_doc(&did).await?; 242 249 let did_doc = did_doc_response.parse()?; ··· 276 283 println!("Found {} markdown files", md_files.len()); 277 284 278 285 // 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()); 286 + let context = AtProtoPreprocessContext::new(vault_arc.clone(), title.clone(), agent.clone()) 287 + .with_creator(did.clone().into_static(), handle.clone().into_static()); 284 288 285 289 // Process each file 286 290 for file_path in &md_files { ··· 299 303 }); 300 304 301 305 // Parse markdown 302 - use weaver_renderer::default_md_options; 303 306 use markdown_weaver::Parser; 304 - let parser = Parser::new_with_broken_link_callback(&contents, default_md_options(), callback); 307 + use weaver_renderer::default_md_options; 308 + let parser = 309 + Parser::new_with_broken_link_callback(&contents, default_md_options(), callback); 305 310 let iterator = weaver_renderer::ContextIterator::default(parser); 306 311 307 312 // Process through NotebookProcessor 308 - use weaver_renderer::{NotebookProcessor, NotebookContext}; 309 313 use n0_future::StreamExt; 314 + use weaver_renderer::{NotebookContext, NotebookProcessor}; 310 315 let mut processor = NotebookProcessor::new(file_context.clone(), iterator); 311 316 312 317 // Write canonical markdown with MarkdownWriter ··· 317 322 318 323 // Process all events 319 324 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 - })?; 325 + md_writer 326 + .write_event(event) 327 + .map_err(|e| miette::miette!("Failed to write markdown: {:?}", e))?; 323 328 } 324 329 325 330 // Extract blobs and entry metadata ··· 327 332 let entry_title = file_context.entry_title(); 328 333 329 334 // 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 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}; 334 339 335 340 let embeds = if !blobs.is_empty() { 336 341 // Build images from blobs
+11 -7
crates/weaver-renderer/src/atproto.rs
··· 3 3 //! Two-stage pipeline: markdown→markdown preprocessing (CLI), 4 4 //! then client-side markdown→HTML rendering (WASM). 5 5 6 + mod client; 7 + mod embed_renderer; 6 8 mod error; 7 - mod types; 8 9 mod markdown_writer; 10 + #[cfg(not(target_family = "wasm"))] 9 11 mod preprocess; 10 - mod client; 11 - mod embed_renderer; 12 + mod types; 12 13 mod writer; 13 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 + }; 14 19 pub use error::{AtProtoPreprocessError, ClientRenderError}; 15 - pub use types::{BlobName, BlobInfo}; 20 + pub use markdown_writer::MarkdownWriter; 21 + #[cfg(not(target_family = "wasm"))] 16 22 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}; 23 + pub use types::{BlobInfo, BlobName}; 20 24 pub use writer::{ClientWriter, EmbedContentProvider};
+30 -30
crates/weaver-renderer/src/atproto/client.rs
··· 1 + use super::{error::ClientRenderError, types::BlobName}; 1 2 use crate::{Frontmatter, NotebookContext}; 2 - use super::{types::BlobName, error::ClientRenderError}; 3 3 use jacquard::{ 4 4 client::{Agent, AgentSession}, 5 5 prelude::IdentityResolver, 6 6 types::string::{AtUri, Cid, Did}, 7 7 }; 8 - use markdown_weaver::{Tag, CowStr as MdCowStr, WeaverAttributes}; 8 + use markdown_weaver::{CowStr as MdCowStr, Tag, WeaverAttributes}; 9 9 use std::collections::HashMap; 10 10 use std::sync::Arc; 11 11 use weaver_api::sh_weaver::notebook::entry::Entry; ··· 13 13 /// Trait for resolving embed content on the client side 14 14 /// 15 15 /// Implementations can fetch from cache, make HTTP requests, or use other sources. 16 - pub trait EmbedResolver: Send + Sync { 16 + pub trait EmbedResolver { 17 17 /// Resolve a profile embed by AT URI 18 18 fn resolve_profile( 19 19 &self, 20 20 uri: &AtUri<'_>, 21 - ) -> impl std::future::Future<Output = Result<String, ClientRenderError>> + Send; 21 + ) -> impl std::future::Future<Output = Result<String, ClientRenderError>>; 22 22 23 23 /// Resolve a post/record embed by AT URI 24 24 fn resolve_post( 25 25 &self, 26 26 uri: &AtUri<'_>, 27 - ) -> impl std::future::Future<Output = Result<String, ClientRenderError>> + Send; 27 + ) -> impl std::future::Future<Output = Result<String, ClientRenderError>>; 28 28 29 29 /// Resolve a markdown embed from URL 30 30 /// ··· 33 33 &self, 34 34 url: &str, 35 35 depth: usize, 36 - ) -> impl std::future::Future<Output = Result<String, ClientRenderError>> + Send; 36 + ) -> impl std::future::Future<Output = Result<String, ClientRenderError>>; 37 37 } 38 38 39 39 /// Default embed resolver that fetches records from PDSs ··· 74 74 } 75 75 76 76 async fn resolve_post(&self, uri: &AtUri<'_>) -> Result<String, ClientRenderError> { 77 - use crate::atproto::{fetch_and_render_post, fetch_and_render_generic}; 77 + use crate::atproto::{fetch_and_render_generic, fetch_and_render_post}; 78 78 79 79 // Check if it's a known type 80 80 if let Some(collection) = uri.collection() { 81 81 match collection.as_ref() { 82 82 "app.bsky.feed.post" => { 83 - fetch_and_render_post(uri, &*self.agent) 84 - .await 85 - .map_err(|e| ClientRenderError::EntryFetch { 83 + fetch_and_render_post(uri, &*self.agent).await.map_err(|e| { 84 + ClientRenderError::EntryFetch { 86 85 uri: uri.as_ref().to_string(), 87 86 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 - }) 87 + } 88 + }) 97 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 + }), 98 96 } 99 97 } else { 100 98 Err(ClientRenderError::EntryFetch { ··· 153 151 } 154 152 155 153 /// 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> { 154 + pub fn with_embed_resolver<R: EmbedResolver>(self, resolver: Arc<R>) -> ClientContext<'a, R> { 160 155 ClientContext { 161 156 entry: self.entry, 162 157 creator_did: self.creator_did, ··· 247 242 } 248 243 // Weaver records and unknown collections go to weaver.sh 249 244 _ => { 250 - format!("https://weaver.sh/{}/{}/{}", authority, collection_str, rkey_str) 245 + format!( 246 + "https://weaver.sh/{}/{}/{}", 247 + authority, collection_str, rkey_str 248 + ) 251 249 } 252 250 } 253 251 } else { ··· 279 277 280 278 async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> { 281 279 match &link { 282 - Tag::Link { link_type, dest_url, title, id } => { 280 + Tag::Link { 281 + link_type, 282 + dest_url, 283 + title, 284 + id, 285 + } => { 283 286 let url = dest_url.as_ref(); 284 287 285 288 // Try to parse as AT URI ··· 418 421 #[cfg(test)] 419 422 mod tests { 420 423 use super::*; 424 + use jacquard::types::string::{Datetime, Did}; 421 425 use weaver_api::sh_weaver::notebook::entry::Entry; 422 - use jacquard::types::string::{Did, Datetime}; 423 426 424 427 #[test] 425 428 fn test_client_context_creation() { ··· 436 439 #[test] 437 440 fn test_at_uri_to_web_url_profile() { 438 441 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 - ); 442 + assert_eq!(at_uri_to_web_url(&uri), "https://weaver.sh/did:plc:xyz123"); 443 443 } 444 444 445 445 #[test]
+156 -114
crates/weaver-renderer/src/atproto/preprocess.rs
··· 1 + use super::types::{BlobInfo, BlobName}; 1 2 use crate::{Frontmatter, NotebookContext}; 2 - use super::types::{BlobName, BlobInfo}; 3 3 use dashmap::DashMap; 4 4 use jacquard::{ 5 5 client::{Agent, AgentSession, AgentSessionExt}, 6 6 prelude::IdentityResolver, 7 7 types::string::{CowStr, Did, Handle}, 8 8 }; 9 - use markdown_weaver::{Tag, CowStr as MdCowStr, WeaverAttributes}; 10 - use std::{ 11 - path::PathBuf, 12 - sync::Arc, 13 - }; 9 + use markdown_weaver::{CowStr as MdCowStr, Tag, WeaverAttributes}; 10 + use std::{path::PathBuf, sync::Arc}; 11 + use yaml_rust2::Yaml; 14 12 15 13 pub struct AtProtoPreprocessContext<A: AgentSession + IdentityResolver> { 16 14 // Vault information ··· 113 111 // Stub NotebookContext implementation 114 112 impl<A: AgentSession + IdentityResolver> NotebookContext for AtProtoPreprocessContext<A> { 115 113 fn set_entry_title(&self, title: MdCowStr<'_>) { 116 - self.titles.insert(self.current_path.clone(), title.into_static()); 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 + }); 117 128 } 118 129 119 130 fn entry_title(&self) -> MdCowStr<'_> { 120 131 self.titles 121 132 .get(&self.current_path) 122 133 .map(|t| t.value().clone()) 123 - .unwrap_or(MdCowStr::Borrowed("")) 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 + }) 124 148 } 125 149 126 150 fn frontmatter(&self) -> Frontmatter { ··· 131 155 } 132 156 133 157 fn set_frontmatter(&self, frontmatter: Frontmatter) { 134 - self.frontmatter.insert(self.current_path.clone(), frontmatter); 158 + self.frontmatter 159 + .insert(self.current_path.clone(), frontmatter); 135 160 } 136 161 137 162 async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> { ··· 151 176 match resolved { 152 177 LinkUri::Path(path) => { 153 178 // Local wikilink - look up in vault 154 - if let Some(file_path) = lookup_filename_in_vault(path.as_ref(), &self.vault_contents) { 179 + if let Some(file_path) = 180 + lookup_filename_in_vault(path.as_ref(), &self.vault_contents) 181 + { 155 182 let entry_title = file_path 156 183 .file_stem() 157 184 .and_then(|s| s.to_str()) ··· 166 193 normalized_title 167 194 ) 168 195 } else { 169 - format!( 170 - "/{}/{}", 171 - self.notebook_title.as_ref(), 172 - normalized_title 173 - ) 196 + format!("/{}/{}", self.notebook_title.as_ref(), normalized_title) 174 197 }; 175 198 176 199 return Tag::Link { ··· 213 236 214 237 async fn handle_image<'s>(&self, image: Tag<'s>) -> Tag<'s> { 215 238 use crate::utils::is_local_path; 216 - use tokio::fs; 217 239 use jacquard::bytes::Bytes; 218 240 use jacquard::types::blob::MimeType; 219 241 use mime_sniffer::MimeTypeSniffer; 242 + use tokio::fs; 220 243 221 244 match &image { 222 245 Tag::Image { ··· 248 271 // Sniff mime type from data 249 272 let bytes = Bytes::from(image_data.clone()); 250 273 let mime = MimeType::new_owned( 251 - bytes.sniff_mime_type().unwrap_or("application/octet-stream") 274 + bytes 275 + .sniff_mime_type() 276 + .unwrap_or("application/octet-stream"), 252 277 ); 253 278 254 279 // Upload blob (dereference Arc) ··· 309 334 match resolved { 310 335 LinkUri::Path(path) => { 311 336 // Entry embed - look up in vault 312 - if let Some(file_path) = lookup_filename_in_vault(path.as_ref(), &self.vault_contents) { 337 + if let Some(file_path) = 338 + lookup_filename_in_vault(path.as_ref(), &self.vault_contents) 339 + { 313 340 let entry_title = file_path 314 341 .file_stem() 315 342 .and_then(|s| s.to_str()) ··· 324 351 normalized_title 325 352 ) 326 353 } else { 327 - format!( 328 - "/{}/{}", 329 - self.notebook_title.as_ref(), 330 - normalized_title 331 - ) 354 + format!("/{}/{}", self.notebook_title.as_ref(), normalized_title) 332 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 + // } 333 446 334 447 return Tag::Embed { 335 448 embed_type: *embed_type, ··· 363 476 }); 364 477 365 478 if let Some(content_html) = content { 366 - new_attrs.attrs.push(("content".into(), content_html.into())); 479 + new_attrs 480 + .attrs 481 + .push(("content".into(), content_html.into())); 367 482 } 368 483 369 484 return Tag::Embed { ··· 376 491 } 377 492 LinkUri::AtRecord(uri) => { 378 493 // AT URI embed - fetch and render 379 - use crate::atproto::{fetch_and_render_post, fetch_and_render_generic}; 494 + use crate::atproto::{fetch_and_render_generic, fetch_and_render_post}; 380 495 use markdown_weaver::WeaverAttributes; 381 496 382 497 // Determine if this is a known type ··· 387 502 match fetch_and_render_post(&uri, &*self.agent).await { 388 503 Ok(html) => Some(html), 389 504 Err(e) => { 390 - eprintln!("Failed to fetch post {}: {}", uri.as_ref(), e); 505 + eprintln!( 506 + "Failed to fetch post {}: {}", 507 + uri.as_ref(), 508 + e 509 + ); 391 510 None 392 511 } 393 512 } ··· 397 516 match fetch_and_render_generic(&uri, &*self.agent).await { 398 517 Ok(html) => Some(html), 399 518 Err(e) => { 400 - eprintln!("Failed to fetch record {}: {}", uri.as_ref(), e); 519 + eprintln!( 520 + "Failed to fetch record {}: {}", 521 + uri.as_ref(), 522 + e 523 + ); 401 524 None 402 525 } 403 526 } ··· 414 537 }); 415 538 416 539 if let Some(content_html) = content { 417 - new_attrs.attrs.push(("content".into(), content_html.into())); 540 + new_attrs 541 + .attrs 542 + .push(("content".into(), content_html.into())); 418 543 } 419 544 420 545 return Tag::Embed { ··· 425 550 attrs: Some(new_attrs), 426 551 }; 427 552 } 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 553 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 554 _ => {} 508 555 } 509 556 510 557 // Pass through other embed types 511 558 embed.clone() 512 559 } 513 - Tag::Image { 514 - link_type, 515 - dest_url, 516 - title, 517 - id, 518 - attrs, 519 - } => { 560 + Tag::Image { .. } => { 520 561 // Some embeds come through as explicit Tag::Image 521 562 // Delegate to handle_image for image-specific processing 522 563 self.handle_image(embed).await ··· 530 571 } 531 572 532 573 fn add_reference(&self, reference: MdCowStr<'_>) { 533 - self.reference_map.insert(reference.into_static(), self.current_path.clone()); 574 + self.reference_map 575 + .insert(reference.into_static(), self.current_path.clone()); 534 576 } 535 577 } 536 578
+1 -2
crates/weaver-renderer/src/css.rs
··· 1 1 use crate::theme::Theme; 2 2 use miette::IntoDiagnostic; 3 3 use std::io::Cursor; 4 - use syntect::highlighting::{Theme as SyntectTheme, ThemeSet}; 4 + use syntect::highlighting::ThemeSet; 5 5 use syntect::html::{ClassStyle, css_for_theme_with_class_style}; 6 - use syntect::parsing::SyntaxSet; 7 6 use weaver_api::com_atproto::sync::get_blob::GetBlob; 8 7 use weaver_api::sh_weaver::notebook::theme::ThemeCodeTheme; 9 8 use weaver_common::jacquard::client::BasicClient;
+3 -6
crates/weaver-renderer/src/static_site.rs
··· 9 9 pub mod document; 10 10 pub mod writer; 11 11 12 + use crate::utils::VaultBrokenLinkCallback; 12 13 use crate::{ 13 14 ContextIterator, NotebookProcessor, 14 15 static_site::{ ··· 16 17 document::{CssMode, write_document_footer, write_document_head}, 17 18 writer::StaticPageWriter, 18 19 }, 19 - theme::defaultTheme, 20 + theme::default_theme, 20 21 utils::flatten_dir_to_just_one_parent, 21 22 walker::{WalkOptions, vault_contents}, 22 23 }; ··· 30 31 path::{Path, PathBuf}, 31 32 sync::Arc, 32 33 }; 33 - use crate::utils::VaultBrokenLinkCallback; 34 34 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 35 35 use tokio::io::AsyncWriteExt; 36 36 use unicode_normalization::UnicodeNormalization; ··· 66 66 | Self::NORMALIZE_DIR_NAMES 67 67 } 68 68 } 69 - 70 69 71 70 pub struct StaticSiteWriter<A> 72 71 where ··· 240 239 .await 241 240 .into_diagnostic()?; 242 241 243 - let default_theme = defaultTheme(); 242 + let default_theme = default_theme(); 244 243 let theme = self.context.theme.as_deref().unwrap_or(&default_theme); 245 244 246 245 // Write base.css ··· 408 407 409 408 Ok(()) 410 409 } 411 - 412 - 413 410 414 411 #[cfg(test)] 415 412 mod tests;
+2 -2
crates/weaver-renderer/src/static_site/document.rs
··· 1 1 use crate::css::{generate_base_css, generate_syntax_css}; 2 2 use crate::static_site::context::{KaTeXSource, StaticSiteContext}; 3 - use crate::theme::{Theme, defaultTheme}; 3 + use crate::theme::{Theme, default_theme}; 4 4 use miette::IntoDiagnostic; 5 5 use weaver_common::jacquard::client::AgentSession; 6 6 ··· 98 98 .into_diagnostic()?; 99 99 } 100 100 CssMode::Inline => { 101 - let default_theme = defaultTheme(); 101 + let default_theme = default_theme(); 102 102 let theme = context.theme.as_deref().unwrap_or(&default_theme); 103 103 104 104 writer.write_all(b" <style>\n").await.into_diagnostic()?;
+5 -5
crates/weaver-renderer/src/theme.rs
··· 4 4 use weaver_common::jacquard::CowStr; 5 5 use weaver_common::jacquard::cowstr::ToCowStr; 6 6 7 - pub fn defaultTheme() -> Theme<'static> { 7 + pub fn default_theme() -> Theme<'static> { 8 8 Theme::new() 9 9 .code_theme(ThemeCodeTheme::CodeThemeName(Box::new( 10 10 "rose-pine".to_cowstr(), 11 11 ))) 12 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"), 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 17 primary: CowStr::new_static("#c4a7e7"), 18 18 secondary: CowStr::new_static("#3e8fb0"), 19 19
+18 -14
crates/weaver-server/Cargo.toml
··· 4 4 authors = ["Orual <orual@nonbinary.computer>"] 5 5 edition = "2021" 6 6 7 - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 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"] 8 17 9 18 [dependencies] 10 19 dashmap = "6.1.0" ··· 15 24 weaver-api = { path = "../weaver-api", features = ["streaming"] } 16 25 markdown-weaver = { workspace = true } 17 26 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 } 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 } 21 29 axum = {version = "0.8.6", optional = true} 22 30 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"] 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 1 #[allow(unused_imports)] 2 2 use crate::fetch; 3 - use dioxus::prelude::*; 4 3 #[allow(unused_imports)] 5 - use dioxus::{fullstack::extract::Extension, fullstack::get_server_url, CapturedError}; 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 + }; 6 13 use jacquard::smol_str::SmolStr; 7 14 #[allow(unused_imports)] 8 15 use std::sync::Arc; 9 16 #[allow(unused_imports)] 10 17 use weaver_renderer::theme::Theme; 18 + 19 + #[cfg(feature = "server")] 20 + use axum::{extract::Extension, response::IntoResponse}; 11 21 12 22 #[component] 13 23 pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { ··· 19 29 } 20 30 21 31 #[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::CachedFetcher>>)] 22 - pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<String> { 32 + pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<Response> { 23 33 use jacquard::client::AgentSessionExt; 24 34 use jacquard::types::ident::AtIdentifier; 25 35 use jacquard::{from_data, CowStr}; 26 36 27 37 use weaver_api::sh_weaver::notebook::book::Book; 28 38 use weaver_renderer::css::{generate_base_css, generate_syntax_css}; 29 - use weaver_renderer::theme::defaultTheme; 39 + use weaver_renderer::theme::default_theme; 30 40 31 41 let ident = AtIdentifier::new_owned(ident)?; 32 42 let theme = if let Some(notebook) = fetcher.get_notebook(ident, notebook).await? { ··· 36 46 theme 37 47 .into_output() 38 48 .map(|t| t.value) 39 - .unwrap_or(defaultTheme()) 49 + .unwrap_or(default_theme()) 40 50 } else { 41 - defaultTheme() 51 + default_theme() 42 52 } 43 53 } else { 44 - defaultTheme() 54 + default_theme() 45 55 } 46 56 } else { 47 - defaultTheme() 57 + default_theme() 48 58 }; 49 59 let mut css = generate_base_css(&theme); 50 60 css.push_str( ··· 53 63 .map_err(|e| CapturedError::from_display(e)) 54 64 .unwrap_or_default(), 55 65 ); 56 - Ok(css) 66 + 67 + Ok(([(CONTENT_TYPE, "text/css")], css).into_response()) 57 68 }
+30 -12
crates/weaver-server/src/components/entry.rs
··· 47 47 entry 48 48 })); 49 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..." } } 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" } } 62 58 } 59 + None => rsx! { p { "Loading..." } } 63 60 } 64 61 } 65 62 ··· 90 87 div { 91 88 id: "{&*props.id.read()}", 92 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}", 93 111 dangerous_inner_html: "{html_buf}" 94 112 } 95 113 }
+2 -2
crates/weaver-server/src/components/mod.rs
··· 2 2 //! They can be used to defined common UI elements like buttons, forms, and modals. In this template, we define a Hero 3 3 //! component and an Echo component for fullstack apps to be used in our app. 4 4 5 - mod css; 5 + pub mod css; 6 6 pub use css::NotebookCss; 7 7 8 8 mod entry; ··· 10 10 11 11 mod identity; 12 12 pub use identity::{Repository, RepositoryIndex}; 13 - pub mod avatar; 13 + //pub mod avatar;
+40 -152
crates/weaver-server/src/fetch.rs
··· 1 + use crate::cache_impl; 1 2 use dioxus::Result; 2 3 use jacquard::{client::BasicClient, smol_str::SmolStr, types::ident::AtIdentifier}; 3 - use std::{ 4 - sync::{Arc, Mutex}, 5 - time::Duration, 6 - }; 4 + use std::{sync::Arc, time::Duration}; 7 5 use weaver_api::{ 8 6 com_atproto::repo::strong_ref::StrongRef, 9 7 sh_weaver::notebook::{entry::Entry, BookEntryView, NotebookView}, ··· 13 11 #[derive(Clone)] 14 12 pub struct CachedFetcher { 15 13 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 - >, 14 + book_cache: cache_impl::Cache< 15 + (AtIdentifier<'static>, SmolStr), 16 + Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 42 17 >, 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 - >, 18 + entry_cache: cache_impl::Cache< 19 + (AtIdentifier<'static>, SmolStr), 20 + Arc<(BookEntryView<'static>, Entry<'static>)>, 51 21 >, 52 22 } 53 23 54 24 impl CachedFetcher { 55 - #[cfg(not(feature = "server"))] 56 25 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 26 Self { 67 27 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)), 28 + book_cache: cache_impl::new_cache(100, Duration::from_secs(1200)), 29 + entry_cache: cache_impl::new_cache(100, Duration::from_secs(600)), 88 30 } 89 31 } 90 32 ··· 93 35 ident: AtIdentifier<'static>, 94 36 title: SmolStr, 95 37 ) -> 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())) 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)) 99 49 } 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 - } 50 + Ok(None) 111 51 } 112 - } else { 113 - Ok(None) 114 52 } 115 53 } 116 54 ··· 122 60 ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> { 123 61 if let Some(result) = self.get_notebook(ident.clone(), book_title).await? { 124 62 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())) 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)) 128 78 } 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 - } 79 + Ok(None) 144 80 } 145 - } else { 146 - Ok(None) 147 81 } 148 82 } else { 149 83 Ok(None) 150 84 } 151 85 } 152 86 153 - #[cfg(not(feature = "server"))] 154 87 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 - } 88 + cache_impl::iter(&self.entry_cache) 192 89 } 193 90 194 - #[cfg(feature = "server")] 195 91 pub fn list_recent_notebooks( 196 92 &self, 197 93 ) -> 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 - } 94 + cache_impl::iter(&self.book_cache) 207 95 } 208 96 }
+33 -23
crates/weaver-server/src/main.rs
··· 7 7 use views::{Home, Navbar, Notebook, NotebookIndex, NotebookPage}; 8 8 9 9 mod blobcache; 10 + mod cache_impl; 10 11 /// Define a components module that contains all shared components for our app. 11 12 mod components; 12 13 mod fetch; ··· 49 50 const MAIN_CSS: Asset = asset!("/assets/styling/main.css"); 50 51 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 + 52 57 // Run `serve()` on the server only 53 58 #[cfg(feature = "server")] 54 59 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 + 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; 60 69 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 - )))); 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 73 74 - // And then return the response with `next.run() 75 - Ok::<_, Infallible>(next.run(req).await) 76 - }, 77 - )); 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); 78 85 79 - // .. customize the router, adding layers and new routes 80 - 86 + // And then return the response with `next.run() 87 + Ok::<_, Infallible>(next.run(req).await) 88 + } 89 + } 90 + })); 81 91 // And then return the router 82 92 Ok(router) 83 93 });