From 9f507e488a3ebe8d7b3f04a1c6b1f1610b018069 Mon Sep 17 00:00:00 2001 From: Orual Date: Mon, 22 Dec 2025 19:06:09 -0500 Subject: [PATCH] minified/fixed css --- Cargo.lock | 353 ++++- crates/weaver-app/Cargo.toml | 5 +- crates/weaver-app/assets/styling/entry.css | 4 +- .../assets/styling/notebook-defaults.css | 1252 +++++++++++++++++ crates/weaver-app/src/components/css.rs | 54 +- crates/weaver-app/src/data.rs | 103 +- crates/weaver-app/src/fetch.rs | 4 + crates/weaver-app/src/views/entry.rs | 6 +- crates/weaver-app/src/views/external.rs | 144 +- crates/weaver-renderer/src/css.rs | 3 +- test-sidenotes.html | 156 +- 11 files changed, 1859 insertions(+), 225 deletions(-) create mode 100644 crates/weaver-app/assets/styling/notebook-defaults.css diff --git a/Cargo.lock b/Cargo.lock index f420a61..6926ac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,17 @@ dependencies = [ "inout", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -674,6 +685,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +dependencies = [ + "simd-abstraction", +] + [[package]] name = "base64ct" version = "1.8.1" @@ -746,6 +766,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.8.2" @@ -907,6 +939,28 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.24.0" @@ -1403,6 +1457,15 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "const-str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21077772762a1002bb421c3af42ac1725fa56066bfc53d9a55bb79905df2aaf3" +dependencies = [ + "const-str-proc-macro", +] + [[package]] name = "const-str" version = "0.4.3" @@ -1415,6 +1478,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0664d2867b4a32697dfe655557f5c3b187e9b605b38612a748e5ec99811d160" +[[package]] +name = "const-str-proc-macro" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1e0fdd2e5d3041e530e1b21158aeeef8b5d0e306bc5c1e3d6cf0930d10e25a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "const_format" version = "0.2.35" @@ -1744,6 +1818,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cssparser" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser-color" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556c099a61d85989d7af52b692e35a8d68a57e7df8c6d07563dc0778b3960c9f" +dependencies = [ + "cssparser 0.33.0", +] + [[package]] name = "cssparser-macros" version = "0.6.1" @@ -1922,6 +2018,15 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "data-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + [[package]] name = "data-url" version = "0.3.2" @@ -3736,6 +3841,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -4440,6 +4551,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -4447,7 +4561,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.12", "allocator-api2", ] @@ -5533,6 +5647,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.11.0" @@ -5952,7 +6075,7 @@ version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ - "cssparser", + "cssparser 0.29.6", "html5ever 0.29.1", "indexmap 2.12.1", "selectors", @@ -6104,6 +6227,46 @@ dependencies = [ "x11", ] +[[package]] +name = "lightningcss" +version = "1.0.0-alpha.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b407ca668368d1d5a86cea58ac82d9f9f9ca4bac1e9dce6f16f875f0f081a911" +dependencies = [ + "ahash 0.8.12", + "bitflags 2.10.0", + "const-str 0.3.2", + "cssparser 0.33.0", + "cssparser-color", + "dashmap 5.5.3", + "data-encoding", + "getrandom 0.3.4", + "indexmap 2.12.1", + "itertools 0.10.5", + "lazy_static", + "lightningcss-derive", + "parcel_selectors", + "parcel_sourcemap", + "pastey", + "pathdiff", + "rayon", + "serde", + "serde-content", + "smallvec", +] + +[[package]] +name = "lightningcss-derive" +version = "1.0.0-alpha.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -6621,7 +6784,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" dependencies = [ - "ahash", + "ahash 0.8.12", "portable-atomic", ] @@ -7556,6 +7719,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "outref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" + [[package]] name = "owo-colors" version = "4.2.3" @@ -7609,6 +7778,36 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parcel_selectors" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fd03f1ad26cb6b3ec1b7414fa78a3bd639e7dbb421b1a60513c96ce886a196" +dependencies = [ + "bitflags 2.10.0", + "cssparser 0.33.0", + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "precomputed-hash", + "rustc-hash 2.1.1", + "smallvec", +] + +[[package]] +name = "parcel_sourcemap" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" +dependencies = [ + "base64-simd", + "data-url 0.1.1", + "rkyv", + "serde", + "serde_json", + "vlq", +] + [[package]] name = "parking" version = "2.2.1" @@ -7644,6 +7843,18 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -8273,6 +8484,26 @@ dependencies = [ "cc", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "publicsuffix" version = "2.3.0" @@ -8343,7 +8574,7 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" dependencies = [ - "ahash", + "ahash 0.8.12", "equivalent", "hashbrown 0.16.1", "parking_lot", @@ -8419,6 +8650,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -8574,6 +8811,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -8658,6 +8915,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.12.26" @@ -8788,6 +9054,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rouille" version = "3.6.2" @@ -9080,6 +9375,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.7.3" @@ -9137,7 +9438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", - "cssparser", + "cssparser 0.29.6", "derive_more 0.99.20", "fxhash", "log", @@ -9185,6 +9486,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.4.5" @@ -9591,6 +9901,15 @@ version = "3.0.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0251c9d6468f4ba853b6352b190fb7c1e405087779917c238445eb03993826" +[[package]] +name = "simd-abstraction" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" +dependencies = [ + "outref", +] + [[package]] name = "simd-adler32" version = "0.3.8" @@ -10341,6 +10660,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -11303,7 +11628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6" dependencies = [ "base64 0.22.1", - "data-url", + "data-url 0.3.2", "flate2", "fontdb", "imagesize", @@ -11377,6 +11702,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vlq" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff" + [[package]] name = "walkdir" version = "2.5.0" @@ -11670,6 +12001,7 @@ dependencies = [ "jacquard-identity", "jacquard-lexicon", "js-sys", + "lightningcss", "lol_alloc", "loro", "markdown-weaver", @@ -12685,6 +13017,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" diff --git a/crates/weaver-app/Cargo.toml b/crates/weaver-app/Cargo.toml index 911a852..61e2dff 100644 --- a/crates/weaver-app/Cargo.toml +++ b/crates/weaver-app/Cargo.toml @@ -35,7 +35,7 @@ use-index = ["weaver-common/use-index"] web = ["dioxus/web", "dioxus-primitives/web"] desktop = ["dioxus/desktop"] mobile = ["dioxus/mobile"] -server = [ "dioxus/server", "dep:jacquard-axum", "dep:axum", "dep:resvg", "dep:usvg", "dep:tiny-skia", "dep:textwrap", "dep:askama", "dep:fontdb"] +server = [ "dioxus/server", "dep:jacquard-axum", "dep:axum", "dep:resvg", "dep:usvg", "dep:tiny-skia", "dep:textwrap", "dep:askama", "dep:fontdb", "dep:lightningcss"] collab-worker = ["weaver-common/iroh"] @@ -88,6 +88,9 @@ textwrap = { version = "0.16", optional = true } askama = { version = "0.12", optional = true } fontdb = { version = "0.22", optional = true } +# CSS minification (server-only) +lightningcss = { version = "1.0.0-alpha.68", optional = true } + [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] webbrowser = "1.0.6" reqwest = { version = "0.12", default-features = false, features = ["json"] } diff --git a/crates/weaver-app/assets/styling/entry.css b/crates/weaver-app/assets/styling/entry.css index 3a549de..d406355 100644 --- a/crates/weaver-app/assets/styling/entry.css +++ b/crates/weaver-app/assets/styling/entry.css @@ -321,7 +321,9 @@ border: 1px solid var(--color-border); border-radius: 4px; text-decoration: none; - transition: color 0.2s ease, border-color 0.2s ease; + transition: + color 0.2s ease, + border-color 0.2s ease; } .source-badge:hover { diff --git a/crates/weaver-app/assets/styling/notebook-defaults.css b/crates/weaver-app/assets/styling/notebook-defaults.css new file mode 100644 index 0000000..4988777 --- /dev/null +++ b/crates/weaver-app/assets/styling/notebook-defaults.css @@ -0,0 +1,1252 @@ +/* CSS Reset */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +/* Base Styles */ +html { + font-size: var(--spacing-base); + line-height: var(--spacing-line-height); +} + +/* Scoped to notebook-content container */ +.notebook-content { + font-family: var(--font-body); + color: var(--color-text); + background-color: var(--color-base); + margin: 0 auto; + padding: 1rem 0rem; + word-wrap: break-word; + overflow-wrap: break-word; + counter-reset: sidenote-counter; + max-width: 95ch; +} + +/* When sidenotes exist, body padding creates the gutter */ +/* Left padding shrinks first as viewport narrows, right stays for sidenotes */ +body:has(.sidenote) { + padding-inline-start: clamp(1rem, calc((100vw - 95ch - 15.5rem - 2rem) / 2), 15.5rem); + padding-inline-end: 15.5rem; +} + +/* Typography */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-heading); + margin-top: calc(1rem * var(--spacing-scale)); + margin-bottom: 0.5rem; + line-height: 1.2; +} + +h1 { + font-size: 2rem; + color: var(--color-secondary); +} +h2 { + font-size: 1.5rem; + color: var(--color-primary); +} +h3 { + font-size: 1.25rem; + color: var(--color-secondary); +} +h4 { + font-size: 1.2rem; + color: var(--color-tertiary); +} +h5 { + font-size: 1.125rem; + color: var(--color-secondary); +} +h6 { + font-size: 1rem; +} + +p { + margin-bottom: 1rem; + word-wrap: break-word; + overflow-wrap: break-word; +} + +a { + color: var(--color-link); + text-decoration: none; +} + +.notebook-content a:hover { + color: var(--color-emphasis); + text-decoration: underline; +} + +/* Wikilink validation (editor) */ +.link-valid { + color: var(--color-link); +} + +.link-broken { + color: var(--color-error); + text-decoration: underline wavy; + text-decoration-color: var(--color-error); + opacity: 0.8; +} + +/* Selection */ +::selection { + background: var(--color-highlight); + color: var(--color-text); +} + +/* Lists */ +ul, +ol { + margin-inline-start: 1rem; + margin-bottom: 1rem; +} + +li { + margin-bottom: 0.25rem; +} + +/* Code */ +code { + font-family: var(--font-mono); + background: var(--color-surface); + padding: 0.125rem 0.25rem; + border-radius: 4px; + font-size: 0.9em; +} + +pre { + overflow-x: auto; + margin-bottom: 1rem; + border-radius: 5px; + border: 1px solid var(--color-border); + box-sizing: border-box; +} + +/* Code blocks inside pre are handled by syntax theme */ +pre code { + display: block; + width: fit-content; + min-width: 100%; + padding: 1rem; + background: var(--color-surface); +} + +/* Math */ +.math { + font-family: var(--font-mono); +} + +.math-display { + display: block; + margin: 1rem 0; + text-align: center; +} + +/* Blockquotes */ +blockquote { + border-inline-start: 2px solid var(--color-secondary); + background: var(--color-surface); + padding-inline-start: 1rem; + padding-inline-end: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.04rem; + margin: 1rem 0; + font-size: 0.95em; + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; +} + +/* Tables */ +table { + border-collapse: collapse; + width: 100%; + margin-bottom: 1rem; + display: block; + overflow-x: auto; + max-width: 100%; +} + +th, +td { + border: 1px solid var(--color-border); + padding: 0.5rem; + text-align: start; +} + +th { + background: var(--color-surface); + font-weight: 600; +} + +tr:hover { + background: var(--color-surface); +} + +/* Footnotes */ +.footnote-reference { + font-size: 0.8em; + color: var(--color-subtle); +} + +.footnote-definition { + order: 9999; + margin: 0; + padding: 0.5rem 0; + font-size: 0.9em; +} + +.footnote-definition:first-of-type { + margin-top: 2rem; + padding-top: 1rem; + border-top: 2px solid var(--color-border); +} + +.footnote-definition:first-of-type::before { + content: "Footnotes"; + display: block; + font-weight: 600; + font-size: 1.1em; + color: var(--color-subtle); + margin-bottom: 0.75rem; +} + +.footnote-definition-label { + font-weight: 600; + margin-inline-end: 0.5rem; + color: var(--color-primary); +} + +/* Aside blocks (via WeaverBlock prefix) - scoped to notebook content */ +.notebook-content aside, +.notebook-content .aside { + float: inline-start; + width: 40%; + margin: 0 1.5rem 1rem 0; + padding: 1rem; + background: var(--color-surface); + border-inline-end: 3px solid var(--color-primary); + font-size: 0.9em; + clear: inline-start; +} + +.notebook-content aside > *:first-child, +.notebook-content .aside > *:first-child { + margin-top: 0; +} + +.notebook-content aside > *:last-child, +.notebook-content .aside > *:last-child { + margin-bottom: 0; +} + +/* Reset blockquote styling inside asides */ +.notebook-content aside > blockquote, +.notebook-content .aside > blockquote { + border-inline-start: none; + background: transparent; + padding: 0; + margin: 0; + font-size: inherit; +} + +/* Indent utilities */ +.indent-1 { + margin-inline-start: 1em; +} +.indent-2 { + margin-inline-start: 2em; +} +.indent-3 { + margin-inline-start: 3em; +} + +/* Tufte-style Sidenotes */ +/* Hide checkbox for sidenote toggle */ +.margin-toggle { + display: none; +} + +/* Sidenote number marker (inline superscript) */ +.sidenote-number { + counter-increment: sidenote-counter; +} + +.sidenote-number::after { + content: counter(sidenote-counter); + font-size: 0.7em; + position: relative; + top: -0.5em; + color: var(--color-primary); + padding-inline-start: 0.1em; +} + +/* Sidenote content (margin notes on wide screens) */ +.sidenote { + float: inline-end; + clear: inline-end; + margin-inline-end: -15.5rem; + width: 14rem; + margin-top: 0.3rem; + margin-bottom: 1rem; + font-size: 0.85em; + line-height: 1.4; + color: var(--color-subtle); +} + +.sidenote::before { + content: counter(sidenote-counter) ". "; + color: var(--color-primary); +} + +/* Mobile sidenotes: toggle behavior */ +@media (max-width: 900px) { + /* Reset sidenote gutter on mobile */ + body:has(.sidenote) { + padding-inline-end: 0; + } + + aside, + .aside { + float: none; + width: 100%; + margin: 1rem 0; + } + + .sidenote { + display: none; + } + + .margin-toggle:checked + .sidenote { + display: block; + float: none; + width: 95%; + margin: 0.5rem 2.5%; + padding: 0.5rem; + background: var(--color-surface); + border-inline-start: 2px solid var(--color-primary); + } + + label.sidenote-number { + cursor: pointer; + } + + label.sidenote-number::after { + text-decoration: underline; + } +} + +/* Images */ +img { + max-width: 100%; + height: auto; + display: block; + margin: 1rem 0; + border-radius: 4px; +} + +/* Hygiene for iframes */ +.html-embed-block { + max-width: 100%; + height: auto; + display: block; + margin: 1rem 0; +} + +/* AT Protocol Embeds - Container */ +/* Light mode: paper with shadow, dark mode: blueprint with borders */ +.atproto-embed { + display: block; + position: relative; + max-width: 550px; + margin: 1rem 0; + padding: 1rem; + background: var(--color-surface); + border-inline-start: 2px solid var(--color-secondary); + box-shadow: 0 1px 2px color-mix(in srgb, var(--color-text) 8%, transparent); +} + +.atproto-embed:hover { + border-inline-start-color: var(--color-primary); +} + +@media (prefers-color-scheme: dark) { + .atproto-embed { + box-shadow: none; + border: 1px solid var(--color-border); + border-inline-start: 2px solid var(--color-secondary); + } +} + +.atproto-embed-placeholder { + color: var(--color-muted); + font-style: italic; +} + +.embed-loading { + display: block; + padding: 0.5rem 0; + color: var(--color-subtle); + font-family: var(--font-mono); + font-size: 0.85rem; +} + +/* Embed Author Block */ +.embed-author { + display: flex; + align-items: center; + gap: 0.75rem; + padding-bottom: 0.5rem; +} + +.embed-avatar { + width: 36px; + height: 36px; + max-width: 36px; + max-height: 36px; + aspect-ratio: 1; + margin: 0; + object-fit: cover; +} + +.embed-author-info { + display: flex; + flex-direction: column; + gap: 0; + min-width: 0; +} + +.embed-avatar-link { + display: block; + flex-shrink: 0; +} + +.embed-author-name { + font-weight: 600; + color: var(--color-text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-decoration: none; + line-height: 1.2; +} + +a.embed-author-name:hover { + color: var(--color-link); +} + +.embed-author-handle { + font-size: 0.85em; + font-family: var(--font-mono); + color: var(--color-subtle); + text-decoration: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.2; +} + +.embed-author-handle:hover { + color: var(--color-link); +} + +/* Card-wide clickable link (sits behind content) */ +.embed-card-link { + position: absolute; + inset: 0; + z-index: 0; +} + +.embed-card-link:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +/* Interactive elements sit above the card link */ +.embed-author, +.embed-external, +.embed-quote, +.embed-images, +.embed-meta { + position: relative; + z-index: 1; +} + +/* Embed Content Block */ +.embed-content { + display: block; + color: var(--color-text); + line-height: 1.5; + margin-bottom: 0.75rem; + white-space: pre-wrap; +} + +.embed-description { + display: block; + color: var(--color-text); + font-size: 0.95em; + line-height: 1.4; +} + +/* Embed Metadata Block */ +.embed-meta { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.85em; + color: var(--color-muted); + margin-top: 0.75rem; +} + +.embed-stats { + display: flex; + gap: 1rem; + font-family: var(--font-mono); +} + +.embed-stat { + color: var(--color-subtle); + font-size: 0.9em; +} + +.embed-time { + color: var(--color-subtle); + text-decoration: none; + font-family: var(--font-mono); + font-size: 0.9em; +} + +.embed-time:hover { + color: var(--color-link); +} + +.embed-type { + font-size: 0.8em; + color: var(--color-subtle); + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Embed URL link (shown with syntax in editor) */ +.embed-url { + color: var(--color-link); + font-family: var(--font-mono); + font-size: 0.9em; + word-break: break-all; +} + +/* External link cards */ +.embed-external { + display: flex; + gap: 0.75rem; + padding: 0.75rem; + background: var(--color-surface); + border: 1px dashed var(--color-border); + text-decoration: none; + color: inherit; + margin-top: 0.5rem; +} + +.embed-external:hover { + border-inline-start: 2px solid var(--color-primary); + margin-inline-start: -1px; +} + +@media (prefers-color-scheme: dark) { + .embed-external { + border: 1px solid var(--color-border); + } + + .embed-external:hover { + border-inline-start: 2px solid var(--color-primary); + margin-inline-start: -1px; + } +} + +.embed-external-thumb { + width: 120px; + height: 80px; + object-fit: cover; + flex-shrink: 0; +} + +.embed-external-info { + display: flex; + flex-direction: column; + gap: 0.25rem; + min-width: 0; +} + +.embed-external-title { + font-weight: 600; + color: var(--color-text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.embed-external-description { + font-size: 0.9em; + color: var(--color-muted); + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.embed-external-url { + font-size: 0.8em; + font-family: var(--font-mono); + color: var(--color-subtle); +} + +/* Image embeds */ +.embed-images { + display: grid; + gap: 4px; + margin-top: 0.5rem; + overflow: hidden; +} + +.embed-images-1 { + grid-template-columns: 1fr; +} + +.embed-images-2 { + grid-template-columns: 1fr 1fr; +} + +.embed-images-3 { + grid-template-columns: 1fr 1fr; +} + +.embed-images-4 { + grid-template-columns: 1fr 1fr; +} + +.embed-image-link { + display: block; + line-height: 0; +} + +.embed-image { + width: 100%; + height: auto; + max-height: 500px; + object-fit: cover; + object-position: center; + margin: 0; +} + +/* Quoted records */ +.embed-quote { + display: block; + margin-top: 0.5rem; + padding: 0.75rem; + background: var(--color-overlay); + border-inline-start: 2px solid var(--color-tertiary); +} + +@media (prefers-color-scheme: dark) { + .embed-quote { + border: 1px solid var(--color-border); + border-inline-start: 2px solid var(--color-tertiary); + } +} + +.embed-quote .embed-author { + margin-bottom: 0.5rem; +} + +.embed-quote .embed-avatar { + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + max-width: 24px; + max-height: 24px; +} + +.embed-quote .embed-content { + font-size: 0.95em; + margin-bottom: 0; +} + +/* Placeholder states */ +.embed-video-placeholder, +.embed-not-found, +.embed-blocked, +.embed-detached, +.embed-unknown { + display: block; + padding: 1rem; + background: var(--color-overlay); + border-inline-start: 2px solid var(--color-border); + color: var(--color-muted); + font-style: italic; + margin-top: 0.5rem; + font-family: var(--font-mono); + font-size: 0.9em; +} + +@media (prefers-color-scheme: dark) { + .embed-video-placeholder, + .embed-not-found, + .embed-blocked, + .embed-detached, + .embed-unknown { + border: 1px dashed var(--color-border); + } +} + +/* Record card embeds (feeds, lists, labelers, starter packs) */ +.embed-record-card { + display: block; + margin-top: 0.5rem; + padding: 0.75rem; + background: var(--color-overlay); + border-inline-start: 2px solid var(--color-tertiary); +} + +.embed-record-card > .embed-author-name { + display: block; + font-size: 1.1em; +} + +.embed-subtitle { + display: block; + font-size: 0.85em; + color: var(--color-muted); + margin-bottom: 0.5rem; +} + +.embed-record-card .embed-description { + display: block; + margin: 0.5rem 0; +} + +.embed-record-card .embed-stats { + display: block; + margin-top: 0.25rem; +} + +/* Generic record fields */ +.embed-fields { + display: block; + margin-top: 0.5rem; + font-family: var(--font-ui); + font-size: 0.85rem; + color: var(--color-muted); +} + +.embed-field { + display: block; + margin-top: 0.25rem; +} + +/* Nested fields get indentation */ +.embed-fields .embed-fields { + display: block; + margin-top: 0.5rem; + margin-inline-start: 1rem; + padding-inline-start: 0.5rem; + border-inline-start: 1px solid var(--color-border); +} + +/* Type label inside fields should be block with spacing */ +.embed-fields > .embed-author-handle { + display: block; + margin-bottom: 0.25rem; +} + +.embed-field-name { + color: var(--color-subtle); +} + +.embed-field-number { + color: var(--color-tertiary); +} + +.embed-field-date { + color: var(--color-muted); +} + +.embed-field-count { + color: var(--color-muted); + font-style: italic; +} + +.embed-field-bool-true { + color: var(--color-success); +} + +.embed-field-bool-false { + color: var(--color-muted); +} + +.embed-field-link, +.embed-field-aturi { + color: var(--color-link); + text-decoration: none; +} + +.embed-field-link:hover, +.embed-field-aturi:hover { + text-decoration: underline; +} + +.embed-field-did { + font-family: var(--font-mono); + font-size: 0.9em; +} + +.embed-field-did .did-scheme, +.embed-field-did .did-separator { + color: var(--color-muted); +} + +.embed-field-did .did-method { + color: var(--color-tertiary); +} + +.embed-field-did .did-identifier { + color: var(--color-text); +} + +.embed-field-nsid { + color: var(--color-secondary); +} + +.embed-field-handle { + color: var(--color-link); +} + +/* AT URI highlighting */ +.aturi-scheme { + color: var(--color-muted); +} + +.aturi-slash { + color: var(--color-muted); +} + +.aturi-authority { + color: var(--color-link); +} + +.aturi-collection { + color: var(--color-secondary); +} + +.aturi-rkey { + color: var(--color-tertiary); +} + +/* Generic AT Protocol record embed */ +.atproto-record > .embed-author-handle { + display: block; + margin-bottom: 0.25rem; +} + +.atproto-record > .embed-author-name { + display: block; + margin-bottom: 0.5rem; +} + +.atproto-record > .embed-content { + margin-bottom: 0.5rem; +} + +/* Notebook entry embed - full width, expandable */ +.atproto-entry { + max-width: none; + width: 100%; + margin: 1.5rem 0; + padding: 0; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-inline-start: 1px solid var(--color-border); + box-shadow: none; + overflow: hidden; +} + +.atproto-entry:hover { + border-inline-start-color: var(--color-border); +} + +@media (prefers-color-scheme: dark) { + .atproto-entry { + border: 1px solid var(--color-border); + border-inline-start: 1px solid var(--color-border); + } +} + +.embed-entry-header { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.5rem 1rem; + padding: 0.75rem 1rem; + background: var(--color-overlay); + border-bottom: 1px solid var(--color-border); +} + +.embed-entry-title { + font-size: 1.1em; + font-weight: 600; + color: var(--color-text); +} + +.embed-entry-author { + font-size: 0.85em; + color: var(--color-muted); +} + +/* Hidden checkbox for expand/collapse */ +.embed-entry-toggle { + display: none; +} + +/* Content wrapper - scrollable when collapsed */ +.embed-entry-content { + max-height: 30rem; + overflow-y: auto; + padding: 1rem; + transition: max-height 0.3s ease; +} + +/* When checkbox is checked, expand fully */ +.embed-entry-toggle:checked ~ .embed-entry-content { + max-height: none; +} + +/* Expand/collapse button */ +.embed-entry-expand { + display: block; + width: 100%; + padding: 0.5rem; + text-align: center; + font-size: 0.85em; + font-family: var(--font-ui); + color: var(--color-muted); + background: var(--color-overlay); + border-top: 1px solid var(--color-border); + cursor: pointer; + user-select: none; +} + +.embed-entry-expand:hover { + color: var(--color-text); + background: var(--color-surface); +} + +/* Toggle button text */ +.embed-entry-expand::before { + content: "Expand ↓"; +} + +.embed-entry-toggle:checked ~ .embed-entry-expand::before { + content: "Collapse ↑"; +} + +/* Hide expand button if content doesn't overflow (via JS class) */ +.atproto-entry.no-overflow .embed-entry-expand { + display: none; +} + +/* Horizontal Rule */ +hr { + border: none; + border-top: 2px solid var(--color-border); + margin: 2rem 0; +} + +/* Tablet and mobile responsiveness */ +@media (max-width: 900px) { + .notebook-content { + padding: 1.5rem 1rem; + max-width: 100%; + } + + h1 { + font-size: 1.85rem; + } + h2 { + font-size: 1.4rem; + } + h3 { + font-size: 1.2rem; + } + + blockquote { + margin-inline-start: 0; + margin-inline-end: 0; + } +} + +/* Small mobile phones */ +@media (max-width: 480px) { + .notebook-content { + padding: 1rem 0.75rem; + } + + h1 { + font-size: 1.65rem; + } + h2 { + font-size: 1.3rem; + } + h3 { + font-size: 1.1rem; + } + + blockquote { + padding-inline-start: 0.75rem; + padding-inline-end: 0.75rem; + } +} + +/* Leaflet document embeds */ +.atproto-leaflet { + max-width: none; + width: 100%; + margin: 1rem 0; +} + +.leaflet-document { + display: block; +} + +.leaflet-text { + margin: 0.5rem 0; +} + +.leaflet-button { + display: inline-block; + padding: 0.5rem 1rem; + background: var(--color-primary); + color: var(--color-base); + text-decoration: none; + border-radius: 4px; + margin: 0.5rem 0; +} + +.leaflet-button:hover { + opacity: 0.9; +} + +/* Alignment utilities */ +.align-center { + text-align: center; +} +.align-right { + text-align: right; +} +.align-justify { + text-align: justify; +} + +/* ========================================================================== + SYNTAX HIGHLIGHTING + ========================================================================== */ + +/* Syntax highlighting - Light Mode (default) */ +/* + * theme "Rosé Pine Dawn" generated by syntect + */ + +.wvc-code { + color: #575279; + background-color: #faf4ed; +} + +.wvc-comment { + color: #797593; + font-style: italic; +} +.wvc-string, +.wvc-punctuation.wvc-definition.wvc-string { + color: #ea9d34; +} +.wvc-constant.wvc-numeric { + color: #ea9d34; +} +.wvc-constant.wvc-language { + color: #ea9d34; + font-weight: bold; +} +.wvc-constant.wvc-character, +.wvc-constant.wvc-other { + color: #ea9d34; +} +.wvc-variable { + color: #575279; + font-style: italic; +} +.wvc-keyword { + color: #286983; +} +.wvc-storage { + color: #56949f; +} +.wvc-storage.wvc-type { + color: #56949f; +} +.wvc-entity.wvc-name.wvc-class { + color: #286983; + font-weight: bold; +} +.wvc-entity.wvc-other.wvc-inherited-class { + color: #286983; + font-style: italic; +} +.wvc-entity.wvc-name.wvc-function { + color: #d7827e; + font-style: italic; +} +.wvc-variable.wvc-parameter { + color: #907aa9; +} +.wvc-entity.wvc-name.wvc-tag { + color: #286983; + font-weight: bold; +} +.wvc-entity.wvc-other.wvc-attribute-name { + color: #907aa9; +} +.wvc-support.wvc-function { + color: #d7827e; + font-weight: bold; +} +.wvc-support.wvc-constant { + color: #ea9d34; + font-weight: bold; +} +.wvc-support.wvc-type, +.wvc-support.wvc-class { + color: #56949f; + font-weight: bold; +} +.wvc-support.wvc-other.wvc-variable { + color: #b4637a; + font-weight: bold; +} +.wvc-invalid { + color: #575279; + background-color: #b4637a; +} +.wvc-invalid.wvc-deprecated { + color: #575279; + background-color: #907aa9; +} +.wvc-punctuation, +.wvc-keyword.wvc-operator { + color: #797593; +} + +/* Syntax highlighting - Dark Mode */ +@media (prefers-color-scheme: dark) { + /* + * theme "Rosé Pine" generated by syntect + */ + + .wvc-code { + color: #e0def4; + background-color: #191724; + } + + .wvc-comment { + color: #908caa; + font-style: italic; + } + .wvc-string, + .wvc-punctuation.wvc-definition.wvc-string { + color: #f6c177; + } + .wvc-constant.wvc-numeric { + color: #f6c177; + } + .wvc-constant.wvc-language { + color: #f6c177; + font-weight: bold; + } + .wvc-constant.wvc-character, + .wvc-constant.wvc-other { + color: #f6c177; + } + .wvc-variable { + color: #e0def4; + font-style: italic; + } + .wvc-keyword { + color: #31748f; + } + .wvc-storage { + color: #9ccfd8; + } + .wvc-storage.wvc-type { + color: #9ccfd8; + } + .wvc-entity.wvc-name.wvc-class { + color: #31748f; + font-weight: bold; + } + .wvc-entity.wvc-other.wvc-inherited-class { + color: #31748f; + font-style: italic; + } + .wvc-entity.wvc-name.wvc-function { + color: #ebbcba; + font-style: italic; + } + .wvc-variable.wvc-parameter { + color: #c4a7e7; + } + .wvc-entity.wvc-name.wvc-tag { + color: #31748f; + font-weight: bold; + } + .wvc-entity.wvc-other.wvc-attribute-name { + color: #c4a7e7; + } + .wvc-support.wvc-function { + color: #ebbcba; + font-weight: bold; + } + .wvc-support.wvc-constant { + color: #f6c177; + font-weight: bold; + } + .wvc-support.wvc-type, + .wvc-support.wvc-class { + color: #9ccfd8; + font-weight: bold; + } + .wvc-support.wvc-other.wvc-variable { + color: #eb6f92; + font-weight: bold; + } + .wvc-invalid { + color: #e0def4; + background-color: #eb6f92; + } + .wvc-invalid.wvc-deprecated { + color: #e0def4; + background-color: #c4a7e7; + } + .wvc-punctuation, + .wvc-keyword.wvc-operator { + color: #908caa; + } +} diff --git a/crates/weaver-app/src/components/css.rs b/crates/weaver-app/src/components/css.rs index 525a18c..4049b0f 100644 --- a/crates/weaver-app/src/components/css.rs +++ b/crates/weaver-app/src/components/css.rs @@ -88,25 +88,9 @@ pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { #[component] pub fn DefaultNotebookCss() -> Element { - use weaver_renderer::css::{generate_base_css, generate_syntax_css}; - use weaver_renderer::theme::default_resolved_theme; - - let css_content = use_resource(move || async move { - let resolved_theme = default_resolved_theme(); - - let mut css = generate_base_css(&resolved_theme); - css.push_str( - &generate_syntax_css(&resolved_theme) - .await - .unwrap_or_default(), - ); - - Some(css) - }); - - match css_content() { - Some(Some(css)) => rsx! { document::Style { {css} } }, - _ => rsx! {}, + rsx! { + document::Stylesheet { href: asset!("/assets/styling/theme-defaults.css") } + document::Stylesheet { href: asset!("/assets/styling/notebook-defaults.css") } } } @@ -155,5 +139,37 @@ pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result { .unwrap_or_default(), ); + let css = minify_css(&css).unwrap_or(css); + Ok(([(CONTENT_TYPE, "text/css")], css).into_response()) } + +#[cfg(feature = "server")] +fn minify_css(css: &str) -> Option { + use lightningcss::printer::PrinterOptions; + use lightningcss::stylesheet::{MinifyOptions, ParserOptions, StyleSheet}; + + let stylesheet = match StyleSheet::parse(css, ParserOptions::default()) { + Ok(s) => s, + Err(e) => { + tracing::warn!("CSS parse error: {:?}", e); + return None; + } + }; + let mut stylesheet = stylesheet; + if let Err(e) = stylesheet.minify(MinifyOptions::default()) { + tracing::warn!("CSS minify error: {:?}", e); + return None; + } + let result = match stylesheet.to_css(PrinterOptions { + minify: true, + ..Default::default() + }) { + Ok(r) => r, + Err(e) => { + tracing::warn!("CSS print error: {:?}", e); + return None; + } + }; + Some(result.code) +} diff --git a/crates/weaver-app/src/data.rs b/crates/weaver-app/src/data.rs index 2466535..01248ca 100644 --- a/crates/weaver-app/src/data.rs +++ b/crates/weaver-app/src/data.rs @@ -1585,7 +1585,7 @@ pub fn use_leaflet_document_data( ident: ReadSignal>, rkey: ReadSignal, ) -> ( - Result)>>, RenderError>, + Result, Option)>>, RenderError>, Memo>, ) { use weaver_api::pub_leaflet::document::Document; @@ -1595,8 +1595,12 @@ pub fn use_leaflet_document_data( let res = use_server_future(move || { let fetcher = fetcher.clone(); async move { + use jacquard::IntoStatic; use jacquard::client::AgentSessionExt; + use jacquard::prelude::IdentityResolver; + use weaver_api::pub_leaflet::document::DocumentPagesItem; use weaver_api::pub_leaflet::publication::Publication; + use weaver_renderer::leaflet::{LeafletRenderContext, render_linear_document}; let ident = ident(); let rkey = rkey(); @@ -1646,12 +1650,37 @@ pub fn use_leaflet_document_data( None }; + // Render HTML + let rendered_html = { + let author_did = match &record.value.author { + AtIdentifier::Did(d) => d.clone().into_static(), + AtIdentifier::Handle(h) => fetcher.resolve_handle(h).await.ok()?.into_static(), + }; + let ctx = LeafletRenderContext::new(author_did); + let mut html = String::new(); + for page in &record.value.pages { + match page { + DocumentPagesItem::LinearDocument(linear_doc) => { + html.push_str(&render_linear_document(linear_doc, &ctx, &fetcher).await); + } + DocumentPagesItem::Canvas(_) => { + html.push_str("
[Canvas layout not yet supported]
"); + } + DocumentPagesItem::Unknown(_) => { + html.push_str("
[Unknown page type]
"); + } + } + } + Some(html) + }; + let profile = fetcher.fetch_profile(&ident).await.ok()?; Some(( serde_json::to_value(&record.value).ok()?, serde_json::to_value(&*profile).ok()?, publication_base_path, + rendered_html, )) } }); @@ -1661,7 +1690,7 @@ pub fn use_leaflet_document_data( use weaver_api::sh_weaver::actor::ProfileDataView; let res = res.as_ref().ok()?; - if let Some(Some((doc_json, profile_json, base_path))) = &*res.read() { + if let Some(Some((doc_json, profile_json, base_path, rendered_html))) = &*res.read() { let document = jacquard::from_json_value::(doc_json.clone()).ok()?; let profile = jacquard::from_json_value::(profile_json.clone()).ok()?; @@ -1669,6 +1698,7 @@ pub fn use_leaflet_document_data( document, profile, publication_base_path: base_path.clone(), + rendered_html: rendered_html.clone(), }) } else { None @@ -1687,8 +1717,10 @@ pub fn use_leaflet_document_data( Memo>, ) { use jacquard::IntoStatic; - use weaver_api::pub_leaflet::document::Document; + use jacquard::prelude::IdentityResolver; + use weaver_api::pub_leaflet::document::{Document, DocumentPagesItem}; use weaver_api::pub_leaflet::publication::Publication; + use weaver_renderer::leaflet::{LeafletRenderContext, render_linear_document}; let fetcher = use_context::(); @@ -1730,12 +1762,37 @@ pub fn use_leaflet_document_data( None }; + // Render HTML + let rendered_html = { + let author_did = match &record.value.author { + AtIdentifier::Did(d) => d.clone().into_static(), + AtIdentifier::Handle(h) => fetcher.resolve_handle(h).await.ok()?.into_static(), + }; + let ctx = LeafletRenderContext::new(author_did); + let mut html = String::new(); + for page in &record.value.pages { + match page { + DocumentPagesItem::LinearDocument(linear_doc) => { + html.push_str(&render_linear_document(linear_doc, &ctx, &fetcher).await); + } + DocumentPagesItem::Canvas(_) => { + html.push_str("
[Canvas layout not yet supported]
"); + } + DocumentPagesItem::Unknown(_) => { + html.push_str("
[Unknown page type]
"); + } + } + } + Some(html) + }; + let profile = fetcher.fetch_profile(&ident).await.ok()?; Some(crate::fetch::LeafletDocumentData { document: record.value.into_static(), profile: (*profile).clone(), publication_base_path, + rendered_html, }) } }); @@ -1753,7 +1810,7 @@ pub fn use_pckt_document_data( ident: ReadSignal>, rkey: ReadSignal, ) -> ( - Result)>>, RenderError>, + Result, Option)>>, RenderError>, Memo>, ) { let fetcher = use_context::(); @@ -1761,9 +1818,12 @@ pub fn use_pckt_document_data( let res = use_server_future(move || { let fetcher = fetcher.clone(); async move { + use jacquard::IntoStatic; use jacquard::client::AgentSessionExt; + use jacquard::prelude::IdentityResolver; use weaver_api::site_standard::document::Document as SiteStandardDocument; use weaver_api::site_standard::publication::Publication; + use weaver_renderer::pckt::{PcktRenderContext, render_content_blocks}; let ident = ident(); let rkey = rkey(); @@ -1807,12 +1867,27 @@ pub fn use_pckt_document_data( None }; + // Render HTML + let rendered_html = { + let author_did = match &ident { + AtIdentifier::Did(d) => d.clone().into_static(), + AtIdentifier::Handle(h) => fetcher.resolve_handle(h).await.ok()?.into_static(), + }; + let ctx = PcktRenderContext::new(author_did); + if let Some(blocks) = &doc.content { + Some(render_content_blocks(blocks, &ctx, &fetcher).await) + } else { + Some(String::from("

No content

")) + } + }; + let profile = fetcher.fetch_profile(&ident).await.ok()?; Some(( serde_json::to_value(&doc).ok()?, serde_json::to_value(&*profile).ok()?, publication_url, + rendered_html, )) } }); @@ -1822,7 +1897,7 @@ pub fn use_pckt_document_data( use weaver_api::site_standard::document::Document as SiteStandardDocument; let res = res.as_ref().ok()?; - if let Some(Some((doc_json, profile_json, publication_url))) = &*res.read() { + if let Some(Some((doc_json, profile_json, publication_url, rendered_html))) = &*res.read() { let document = jacquard::from_json_value::(doc_json.clone()).ok()?; let profile = @@ -1831,6 +1906,7 @@ pub fn use_pckt_document_data( document, profile, publication_url: publication_url.clone(), + rendered_html: rendered_html.clone(), }) } else { None @@ -1849,8 +1925,10 @@ pub fn use_pckt_document_data( Memo>, ) { use jacquard::IntoStatic; + use jacquard::prelude::IdentityResolver; use weaver_api::site_standard::document::Document as SiteStandardDocument; use weaver_api::site_standard::publication::Publication; + use weaver_renderer::pckt::{PcktRenderContext, render_content_blocks}; let fetcher = use_context::(); @@ -1899,12 +1977,27 @@ pub fn use_pckt_document_data( None }; + // Render HTML + let rendered_html = { + let author_did = match &ident { + AtIdentifier::Did(d) => d.clone().into_static(), + AtIdentifier::Handle(h) => fetcher.resolve_handle(h).await.ok()?.into_static(), + }; + let ctx = PcktRenderContext::new(author_did); + if let Some(blocks) = &doc.content { + Some(render_content_blocks(blocks, &ctx, &fetcher).await) + } else { + Some(String::from("

No content

")) + } + }; + let profile = fetcher.fetch_profile(&ident).await.ok()?; Some(crate::fetch::PcktDocumentData { document: doc, profile: (*profile).clone(), publication_url, + rendered_html, }) } }); diff --git a/crates/weaver-app/src/fetch.rs b/crates/weaver-app/src/fetch.rs index 4edc427..887baf8 100644 --- a/crates/weaver-app/src/fetch.rs +++ b/crates/weaver-app/src/fetch.rs @@ -90,6 +90,8 @@ pub struct LeafletDocumentData { pub profile: ProfileDataView<'static>, /// Publication base_path for constructing external URL (e.g., "connectedplaces.leaflet.pub") pub publication_base_path: Option, + /// Pre-rendered HTML content + pub rendered_html: Option, } /// Data for a site.standard / blog.pckt document @@ -100,6 +102,8 @@ pub struct PcktDocumentData { pub profile: ProfileDataView<'static>, /// Publication URL for constructing external URL (e.g., "https://crypto.pckt.blog") pub publication_url: Option, + /// Pre-rendered HTML content + pub rendered_html: Option, } pub struct Client { diff --git a/crates/weaver-app/src/views/entry.rs b/crates/weaver-app/src/views/entry.rs index 5805a88..a92b614 100644 --- a/crates/weaver-app/src/views/entry.rs +++ b/crates/weaver-app/src/views/entry.rs @@ -77,7 +77,7 @@ pub fn StandaloneEntry( document::Link { rel: "stylesheet", href: ENTRY_CSS } NotebookCss { ident: ident().to_smolstr(), notebook: book_title.clone() } - div { class: "entry-page-layout", + div { class: "entry-page", if let Some(ref prev) = book_entry_view.prev { div { class: "nav-gutter nav-prev", NavButton { @@ -133,7 +133,7 @@ pub fn StandaloneEntry( DefaultNotebookCss {} - div { class: "entry-page-layout", + div { class: "entry-page", div { class: "entry-content-main notebook-content", { let (word_count, reading_time_mins) = calculate_reading_stats(&entry_record.content); @@ -234,7 +234,7 @@ pub fn NotebookEntryByRkey( document::Link { rel: "stylesheet", href: ENTRY_CSS } NotebookCss { ident: ident().to_smolstr(), notebook: book_title() } - div { class: "entry-page-layout", + div { class: "entry-page", if let Some(ref prev) = book_entry_view.prev { div { class: "nav-gutter nav-prev", NavButton { diff --git a/crates/weaver-app/src/views/external.rs b/crates/weaver-app/src/views/external.rs index 5c5f7a1..ff44265 100644 --- a/crates/weaver-app/src/views/external.rs +++ b/crates/weaver-app/src/views/external.rs @@ -7,7 +7,6 @@ use weaver_api::sh_weaver::notebook::AuthorListView; use crate::components::css::DefaultNotebookCss; use crate::components::{AuthorList, extract_author_info}; -use crate::fetch::Fetcher; #[component] pub fn WhiteWindEntry( @@ -71,7 +70,7 @@ pub fn WhiteWindEntry( document::Link { rel: "stylesheet", href: ENTRY_CSS } DefaultNotebookCss {} - div { class: "entry-page-layout", + div { class: "entry-page", div { class: "entry-content-main notebook-content", header { class: "entry-metadata", div { class: "entry-header-row", @@ -141,7 +140,6 @@ pub fn LeafletEntry( rkey: ReadSignal, ) -> Element { use crate::components::{ENTRY_CSS, EntryOgMeta}; - use weaver_api::pub_leaflet::document::DocumentPagesItem; let (entry_res, entry_data) = crate::data::use_leaflet_document_data(ident, rkey); @@ -170,9 +168,6 @@ pub fn LeafletEntry( .record(data.profile.clone()) .build(); - let pages = data.document.pages.clone(); - let author_did = data.document.author.clone(); - rsx! { EntryOgMeta { title: title.to_string(), @@ -184,7 +179,7 @@ pub fn LeafletEntry( document::Link { rel: "stylesheet", href: ENTRY_CSS } DefaultNotebookCss {} - div { class: "entry-page-layout", + div { class: "entry-page", div { class: "entry-content-main notebook-content", header { class: "entry-metadata", div { class: "entry-header-row", @@ -206,9 +201,13 @@ pub fn LeafletEntry( } } } - LeafletContent { - pages: pages, - author_did: author_did, + if let Some(ref html) = data.rendered_html { + div { + class: "entry leaflet-document", + dangerous_inner_html: "{html}" + } + } else { + p { "Rendering..." } } } } @@ -218,69 +217,6 @@ pub fn LeafletEntry( } } -#[component] -fn LeafletContent( - pages: Vec>, - author_did: jacquard::types::string::AtIdentifier<'static>, -) -> Element { - use jacquard::IntoStatic; - use jacquard::prelude::*; - use weaver_api::pub_leaflet::document::DocumentPagesItem; - use weaver_renderer::leaflet::{LeafletRenderContext, render_linear_document}; - - let fetcher = use_context::(); - - let html = use_resource(move || { - let pages = pages.clone(); - let author_did = author_did.clone(); - let fetcher = fetcher.clone(); - async move { - let mut html = String::new(); - - // Resolve author DID - let did = match &author_did { - jacquard::types::string::AtIdentifier::Did(d) => d.clone().into_static(), - jacquard::types::string::AtIdentifier::Handle(h) => { - match fetcher.resolve_handle(h).await { - Ok(d) => d.into_static(), - Err(_) => return String::from("

Failed to resolve author

"), - } - } - }; - - let ctx = LeafletRenderContext::new(did); - - for page in &pages { - match page { - DocumentPagesItem::LinearDocument(linear_doc) => { - html.push_str(&render_linear_document(linear_doc, &ctx, &fetcher).await); - } - DocumentPagesItem::Canvas(_) => { - html.push_str("
[Canvas layout not yet supported]
"); - } - DocumentPagesItem::Unknown(_) => { - html.push_str( - "
[Unknown page type]
", - ); - } - } - } - - html - } - }); - - match &*html.read() { - Some(content) => rsx! { - div { - class: "entry leaflet-document", - dangerous_inner_html: "{content}" - } - }, - None => rsx! { p { "Rendering..." } }, - } -} - #[cfg(feature = "pckt")] #[component] pub fn PcktEntry(ident: ReadSignal>, rkey: ReadSignal) -> Element { @@ -327,9 +263,6 @@ pub fn PcktEntry(ident: ReadSignal>, rkey: ReadSignal>, rkey: ReadSignal>, rkey: ReadSignal>, rkey: ReadSignal>>, - author_did: AtIdentifier<'static>, -) -> Element { - use jacquard::IntoStatic; - use jacquard::prelude::*; - use weaver_renderer::pckt::{PcktRenderContext, render_content_blocks}; - - let fetcher = use_context::(); - - let html = use_resource(move || { - let content = content.clone(); - let author_did = author_did.clone(); - let fetcher = fetcher.clone(); - async move { - // Resolve author DID - let did = match &author_did { - AtIdentifier::Did(d) => d.clone().into_static(), - AtIdentifier::Handle(h) => match fetcher.resolve_handle(h).await { - Ok(d) => d.into_static(), - Err(_) => return String::from("

Failed to resolve author

"), - }, - }; - - let ctx = PcktRenderContext::new(did); - - if let Some(blocks) = &content { - render_content_blocks(blocks, &ctx, &fetcher).await - } else { - String::from("

No content

") - } - } - }); - - match &*html.read() { - Some(content) => rsx! { - div { - class: "entry pckt-document", - dangerous_inner_html: "{content}" - } - }, - None => rsx! { p { "Rendering..." } }, - } -} - // ============================================================================= // NSID route wrappers (allow replacing at:// with https://host/) // ============================================================================= diff --git a/crates/weaver-renderer/src/css.rs b/crates/weaver-renderer/src/css.rs index a51a588..014c7cc 100644 --- a/crates/weaver-renderer/src/css.rs +++ b/crates/weaver-renderer/src/css.rs @@ -132,7 +132,7 @@ html {{ /* When sidenotes exist, body padding creates the gutter */ /* Left padding shrinks first as viewport narrows, right stays for sidenotes */ body:has(.sidenote) {{ - padding-inline-start: clamp(0rem, calc((100vw - 95ch - 15.5rem - 2rem) / 2), 15.5rem); + padding-inline-start: clamp(1rem, calc((100vw - 95ch - 15.5rem - 2rem) / 2), 15.5rem); padding-inline-end: 15.5rem; }} @@ -261,7 +261,6 @@ blockquote {{ border-bottom-right-radius: 5px; border-top-right-radius: 5px; }} -}} /* Tables */ table {{ diff --git a/test-sidenotes.html b/test-sidenotes.html index ce714ec..866711e 100644 --- a/test-sidenotes.html +++ b/test-sidenotes.html @@ -84,8 +84,8 @@ html { /* When sidenotes exist, body padding creates the gutter */ /* Left padding shrinks first as viewport narrows, right stays for sidenotes */ body:has(.sidenote) { - padding-left: clamp(0rem, calc((100vw - 95ch - 15.5rem - 2rem) / 2), 15.5rem); - padding-right: 15.5rem; + padding-inline-start: clamp(0rem, calc((100vw - 95ch - 15.5rem - 2rem) / 2), 15.5rem); + padding-inline-end: 15.5rem; } /* Typography */ @@ -154,7 +154,7 @@ a { /* Lists */ ul, ol { - margin-left: 1rem; + margin-inline-start: 1rem; margin-bottom: 1rem; } @@ -202,10 +202,10 @@ pre code { /* Blockquotes */ blockquote { - border-left: 2px solid var(--color-secondary); + border-inline-start: 2px solid var(--color-secondary); background: var(--color-surface); - padding-left: 1rem; - padding-right: 1rem; + padding-inline-start: 1rem; + padding-inline-end: 1rem; padding-top: 0.5rem; padding-bottom: 0.04rem; margin: 1rem 0; @@ -228,7 +228,7 @@ table { th, td { border: 1px solid var(--color-border); padding: 0.5rem; - text-align: left; + text-align: start; } th { @@ -270,21 +270,21 @@ tr:hover { .footnote-definition-label { font-weight: 600; - margin-right: 0.5rem; + margin-inline-end: 0.5rem; color: var(--color-primary); } /* Aside blocks (via WeaverBlock prefix) - scoped to notebook content */ .notebook-content aside, .notebook-content .aside { - float: left; + float: inline-start; width: 40%; margin: 0 1.5rem 1rem 0; padding: 1rem; background: var(--color-surface); - border-right: 3px solid var(--color-primary); + border-inline-end: 3px solid var(--color-primary); font-size: 0.9em; - clear: left; + clear: inline-start; } .notebook-content aside > *:first-child, @@ -300,7 +300,7 @@ tr:hover { /* Reset blockquote styling inside asides */ .notebook-content aside > blockquote, .notebook-content .aside > blockquote { - border-left: none; + border-inline-start: none; background: transparent; padding: 0; margin: 0; @@ -308,9 +308,9 @@ tr:hover { } /* Indent utilities */ -.indent-1 { margin-left: 1em; } -.indent-2 { margin-left: 2em; } -.indent-3 { margin-left: 3em; } +.indent-1 { margin-inline-start: 1em; } +.indent-2 { margin-inline-start: 2em; } +.indent-3 { margin-inline-start: 3em; } /* Tufte-style Sidenotes */ /* Hide checkbox for sidenote toggle */ @@ -329,14 +329,14 @@ tr:hover { position: relative; top: -0.5em; color: var(--color-primary); - padding-left: 0.1em; + padding-inline-start: 0.1em; } /* Sidenote content (margin notes on wide screens) */ .sidenote { - float: right; - clear: right; - margin-right: -15.5rem; + float: inline-end; + clear: inline-end; + margin-inline-end: -15.5rem; width: 14rem; margin-top: 0.3rem; margin-bottom: 1rem; @@ -354,7 +354,7 @@ tr:hover { @media (max-width: 900px) { /* Reset sidenote gutter on mobile */ body:has(.sidenote) { - padding-right: 0; + padding-inline-end: 0; } aside, .aside { @@ -374,7 +374,7 @@ tr:hover { margin: 0.5rem 2.5%; padding: 0.5rem; background: var(--color-surface); - border-left: 2px solid var(--color-primary); + border-inline-start: 2px solid var(--color-primary); } label.sidenote-number { @@ -412,19 +412,19 @@ img { margin: 1rem 0; padding: 1rem; background: var(--color-surface); - border-left: 2px solid var(--color-secondary); + border-inline-start: 2px solid var(--color-secondary); box-shadow: 0 1px 2px color-mix(in srgb, var(--color-text) 8%, transparent); } .atproto-embed:hover { - border-left-color: var(--color-primary); + border-inline-start-color: var(--color-primary); } @media (prefers-color-scheme: dark) { .atproto-embed { box-shadow: none; border: 1px solid var(--color-border); - border-left: 2px solid var(--color-secondary); + border-inline-start: 2px solid var(--color-secondary); } } @@ -601,8 +601,8 @@ a.embed-author-name:hover { } .embed-external:hover { - border-left: 2px solid var(--color-primary); - margin-left: -1px; + border-inline-start: 2px solid var(--color-primary); + margin-inline-start: -1px; } @media (prefers-color-scheme: dark) { @@ -611,8 +611,8 @@ a.embed-author-name:hover { } .embed-external:hover { - border-left: 2px solid var(--color-primary); - margin-left: -1px; + border-inline-start: 2px solid var(--color-primary); + margin-inline-start: -1px; } } @@ -698,13 +698,13 @@ a.embed-author-name:hover { margin-top: 0.5rem; padding: 0.75rem; background: var(--color-overlay); - border-left: 2px solid var(--color-tertiary); + border-inline-start: 2px solid var(--color-tertiary); } @media (prefers-color-scheme: dark) { .embed-quote { border: 1px solid var(--color-border); - border-left: 2px solid var(--color-tertiary); + border-inline-start: 2px solid var(--color-tertiary); } } @@ -735,7 +735,7 @@ a.embed-author-name:hover { display: block; padding: 1rem; background: var(--color-overlay); - border-left: 2px solid var(--color-border); + border-inline-start: 2px solid var(--color-border); color: var(--color-muted); font-style: italic; margin-top: 0.5rem; @@ -759,7 +759,7 @@ a.embed-author-name:hover { margin-top: 0.5rem; padding: 0.75rem; background: var(--color-overlay); - border-left: 2px solid var(--color-tertiary); + border-inline-start: 2px solid var(--color-tertiary); } .embed-record-card > .embed-author-name { @@ -802,9 +802,9 @@ a.embed-author-name:hover { .embed-fields .embed-fields { display: block; margin-top: 0.5rem; - margin-left: 1rem; - padding-left: 0.5rem; - border-left: 1px solid var(--color-border); + margin-inline-start: 1rem; + padding-inline-start: 0.5rem; + border-inline-start: 1px solid var(--color-border); } /* Type label inside fields should be block with spacing */ @@ -919,19 +919,19 @@ a.embed-author-name:hover { padding: 0; background: var(--color-surface); border: 1px solid var(--color-border); - border-left: 1px solid var(--color-border); + border-inline-start: 1px solid var(--color-border); box-shadow: none; overflow: hidden; } .atproto-entry:hover { - border-left-color: var(--color-border); + border-inline-start-color: var(--color-border); } @media (prefers-color-scheme: dark) { .atproto-entry { border: 1px solid var(--color-border); - border-left: 1px solid var(--color-border); + border-inline-start: 1px solid var(--color-border); } } @@ -1027,8 +1027,8 @@ hr { h3 { font-size: 1.2rem; } blockquote { - margin-left: 0; - margin-right: 0; + margin-inline-start: 0; + margin-inline-end: 0; } } @@ -1043,10 +1043,44 @@ hr { h3 { font-size: 1.1rem; } blockquote { - padding-left: 0.75rem; - padding-right: 0.75rem; + padding-inline-start: 0.75rem; + padding-inline-end: 0.75rem; } } + +/* Leaflet document embeds */ +.atproto-leaflet { + max-width: none; + width: 100%; + margin: 1rem 0; +} + +.leaflet-document { + display: block; +} + +.leaflet-text { + margin: 0.5rem 0; +} + +.leaflet-button { + display: inline-block; + padding: 0.5rem 1rem; + background: var(--color-primary); + color: var(--color-base); + text-decoration: none; + border-radius: 4px; + margin: 0.5rem 0; +} + +.leaflet-button:hover { + opacity: 0.9; +} + +/* Alignment utilities */ +.align-center { text-align: center; } +.align-right { text-align: right; } +.align-justify { text-align: justify; }