more styling fussing, new font lexicons

Orual 0625c497 9989bb3d

+1452 -444
+8 -2
crates/weaver-api/lexicons/sh_weaver_notebook_entry.json
··· 15 "createdAt" 16 ], 17 "properties": { 18 "content": { 19 "type": "string", 20 - "description": "The content of the notebook entry. This should be some flavor of Markdown.", 21 - "maxLength": 200000 22 }, 23 "createdAt": { 24 "type": "string",
··· 15 "createdAt" 16 ], 17 "properties": { 18 + "authors": { 19 + "type": "array", 20 + "items": { 21 + "type": "ref", 22 + "ref": "sh.weaver.actor.defs#author" 23 + } 24 + }, 25 "content": { 26 "type": "string", 27 + "description": "The content of the notebook entry. This should be some flavor of Markdown." 28 }, 29 "createdAt": { 30 "type": "string",
+61 -3
crates/weaver-api/lexicons/sh_weaver_notebook_theme.json
··· 4 "defs": { 5 "codeThemeFile": { 6 "type": "object", 7 "required": [ 8 "name", 9 "did", ··· 29 "codeThemeName": { 30 "type": "string" 31 }, 32 "main": { 33 "type": "record", 34 "description": "Theme for a notebook", ··· 68 }, 69 "fonts": { 70 "type": "object", 71 "required": [ 72 "body", 73 "heading", ··· 75 ], 76 "properties": { 77 "body": { 78 - "type": "string" 79 }, 80 "heading": { 81 - "type": "string" 82 }, 83 "monospace": { 84 - "type": "string" 85 } 86 } 87 },
··· 4 "defs": { 5 "codeThemeFile": { 6 "type": "object", 7 + "description": "Custom syntax highlighting theme file (sublime text/textmate theme format)", 8 "required": [ 9 "name", 10 "did", ··· 30 "codeThemeName": { 31 "type": "string" 32 }, 33 + "font": { 34 + "type": "object", 35 + "required": [ 36 + "value" 37 + ], 38 + "properties": { 39 + "value": { 40 + "type": "union", 41 + "description": "Font for a notebook", 42 + "refs": [ 43 + "#fontName", 44 + "#fontFile" 45 + ] 46 + } 47 + } 48 + }, 49 + "fontFile": { 50 + "type": "object", 51 + "description": "Custom woff(2) or ttf font file", 52 + "required": [ 53 + "name", 54 + "did", 55 + "content" 56 + ], 57 + "properties": { 58 + "content": { 59 + "type": "blob", 60 + "accept": [ 61 + "*/*" 62 + ], 63 + "maxSize": 20000 64 + }, 65 + "did": { 66 + "type": "string", 67 + "format": "did" 68 + }, 69 + "name": { 70 + "type": "string" 71 + } 72 + } 73 + }, 74 + "fontName": { 75 + "type": "string" 76 + }, 77 "main": { 78 "type": "record", 79 "description": "Theme for a notebook", ··· 113 }, 114 "fonts": { 115 "type": "object", 116 + "description": "Fonts to be used in the notebook. Can specify a name or list of names (will load if available) or a file or list of files for each. Empty lists will use site defaults.", 117 "required": [ 118 "body", 119 "heading", ··· 121 ], 122 "properties": { 123 "body": { 124 + "type": "array", 125 + "items": { 126 + "type": "ref", 127 + "ref": "#font" 128 + } 129 }, 130 "heading": { 131 + "type": "array", 132 + "items": { 133 + "type": "ref", 134 + "ref": "#font" 135 + } 136 }, 137 "monospace": { 138 + "type": "array", 139 + "items": { 140 + "type": "ref", 141 + "ref": "#font" 142 + } 143 } 144 } 145 },
+65 -39
crates/weaver-api/src/sh_weaver/notebook/entry.rs
··· 18 )] 19 #[serde(rename_all = "camelCase")] 20 pub struct Entry<'a> { 21 /// The content of the notebook entry. This should be some flavor of Markdown. 22 #[serde(borrow)] 23 pub content: jacquard_common::CowStr<'a>, ··· 117 pub struct EntryBuilder<'a, S: entry_state::State> { 118 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 119 __unsafe_private_named: ( 120 ::core::option::Option<jacquard_common::CowStr<'a>>, 121 ::core::option::Option<jacquard_common::types::string::Datetime>, 122 ::core::option::Option<EntryEmbeds<'a>>, ··· 140 pub fn new() -> Self { 141 EntryBuilder { 142 _phantom_state: ::core::marker::PhantomData, 143 - __unsafe_private_named: (None, None, None, None, None, None, None), 144 _phantom: ::core::marker::PhantomData, 145 } 146 } 147 } 148 149 impl<'a, S> EntryBuilder<'a, S> 150 where 151 S: entry_state::State, ··· 156 mut self, 157 value: impl Into<jacquard_common::CowStr<'a>>, 158 ) -> EntryBuilder<'a, entry_state::SetContent<S>> { 159 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 160 EntryBuilder { 161 _phantom_state: ::core::marker::PhantomData, 162 __unsafe_private_named: self.__unsafe_private_named, ··· 175 mut self, 176 value: impl Into<jacquard_common::types::string::Datetime>, 177 ) -> EntryBuilder<'a, entry_state::SetCreatedAt<S>> { 178 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 179 EntryBuilder { 180 _phantom_state: ::core::marker::PhantomData, 181 __unsafe_private_named: self.__unsafe_private_named, ··· 187 impl<'a, S: entry_state::State> EntryBuilder<'a, S> { 188 /// Set the `embeds` field (optional) 189 pub fn embeds(mut self, value: impl Into<Option<EntryEmbeds<'a>>>) -> Self { 190 - self.__unsafe_private_named.2 = value.into(); 191 self 192 } 193 /// Set the `embeds` field to an Option value (optional) 194 pub fn maybe_embeds(mut self, value: Option<EntryEmbeds<'a>>) -> Self { 195 - self.__unsafe_private_named.2 = value; 196 self 197 } 198 } ··· 207 mut self, 208 value: impl Into<crate::sh_weaver::notebook::Path<'a>>, 209 ) -> EntryBuilder<'a, entry_state::SetPath<S>> { 210 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 211 EntryBuilder { 212 _phantom_state: ::core::marker::PhantomData, 213 __unsafe_private_named: self.__unsafe_private_named, ··· 222 mut self, 223 value: impl Into<Option<crate::sh_weaver::notebook::Tags<'a>>>, 224 ) -> Self { 225 - self.__unsafe_private_named.4 = value.into(); 226 self 227 } 228 /// Set the `tags` field to an Option value (optional) ··· 230 mut self, 231 value: Option<crate::sh_weaver::notebook::Tags<'a>>, 232 ) -> Self { 233 - self.__unsafe_private_named.4 = value; 234 self 235 } 236 } ··· 245 mut self, 246 value: impl Into<crate::sh_weaver::notebook::Title<'a>>, 247 ) -> EntryBuilder<'a, entry_state::SetTitle<S>> { 248 - self.__unsafe_private_named.5 = ::core::option::Option::Some(value.into()); 249 EntryBuilder { 250 _phantom_state: ::core::marker::PhantomData, 251 __unsafe_private_named: self.__unsafe_private_named, ··· 260 mut self, 261 value: impl Into<Option<jacquard_common::types::string::Datetime>>, 262 ) -> Self { 263 - self.__unsafe_private_named.6 = value.into(); 264 self 265 } 266 /// Set the `updatedAt` field to an Option value (optional) ··· 268 mut self, 269 value: Option<jacquard_common::types::string::Datetime>, 270 ) -> Self { 271 - self.__unsafe_private_named.6 = value; 272 self 273 } 274 } ··· 284 /// Build the final struct 285 pub fn build(self) -> Entry<'a> { 286 Entry { 287 - content: self.__unsafe_private_named.0.unwrap(), 288 - created_at: self.__unsafe_private_named.1.unwrap(), 289 - embeds: self.__unsafe_private_named.2, 290 - path: self.__unsafe_private_named.3.unwrap(), 291 - tags: self.__unsafe_private_named.4, 292 - title: self.__unsafe_private_named.5.unwrap(), 293 - updated_at: self.__unsafe_private_named.6, 294 extra_data: Default::default(), 295 } 296 } ··· 303 >, 304 ) -> Entry<'a> { 305 Entry { 306 - content: self.__unsafe_private_named.0.unwrap(), 307 - created_at: self.__unsafe_private_named.1.unwrap(), 308 - embeds: self.__unsafe_private_named.2, 309 - path: self.__unsafe_private_named.3.unwrap(), 310 - tags: self.__unsafe_private_named.4, 311 - title: self.__unsafe_private_named.5.unwrap(), 312 - updated_at: self.__unsafe_private_named.6, 313 extra_data: Some(extra_data), 314 } 315 } ··· 393 #[allow(unused_mut)] 394 let mut map = ::std::collections::BTreeMap::new(); 395 map.insert( 396 ::jacquard_common::smol_str::SmolStr::new_static("content"), 397 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 398 description: Some( ··· 403 format: None, 404 default: None, 405 min_length: None, 406 - max_length: Some(200000usize), 407 min_graphemes: None, 408 max_graphemes: None, 409 r#enum: None, ··· 643 fn validate( 644 &self, 645 ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 646 - { 647 - let value = &self.content; 648 - #[allow(unused_comparisons)] 649 - if <str>::len(value.as_ref()) > 200000usize { 650 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 651 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 652 - "content", 653 - ), 654 - max: 200000usize, 655 - actual: <str>::len(value.as_ref()), 656 - }); 657 - } 658 - } 659 Ok(()) 660 } 661 }
··· 18 )] 19 #[serde(rename_all = "camelCase")] 20 pub struct Entry<'a> { 21 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 22 + #[serde(borrow)] 23 + pub authors: std::option::Option<Vec<crate::sh_weaver::actor::Author<'a>>>, 24 /// The content of the notebook entry. This should be some flavor of Markdown. 25 #[serde(borrow)] 26 pub content: jacquard_common::CowStr<'a>, ··· 120 pub struct EntryBuilder<'a, S: entry_state::State> { 121 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 122 __unsafe_private_named: ( 123 + ::core::option::Option<Vec<crate::sh_weaver::actor::Author<'a>>>, 124 ::core::option::Option<jacquard_common::CowStr<'a>>, 125 ::core::option::Option<jacquard_common::types::string::Datetime>, 126 ::core::option::Option<EntryEmbeds<'a>>, ··· 144 pub fn new() -> Self { 145 EntryBuilder { 146 _phantom_state: ::core::marker::PhantomData, 147 + __unsafe_private_named: (None, None, None, None, None, None, None, None), 148 _phantom: ::core::marker::PhantomData, 149 } 150 } 151 } 152 153 + impl<'a, S: entry_state::State> EntryBuilder<'a, S> { 154 + /// Set the `authors` field (optional) 155 + pub fn authors( 156 + mut self, 157 + value: impl Into<Option<Vec<crate::sh_weaver::actor::Author<'a>>>>, 158 + ) -> Self { 159 + self.__unsafe_private_named.0 = value.into(); 160 + self 161 + } 162 + /// Set the `authors` field to an Option value (optional) 163 + pub fn maybe_authors( 164 + mut self, 165 + value: Option<Vec<crate::sh_weaver::actor::Author<'a>>>, 166 + ) -> Self { 167 + self.__unsafe_private_named.0 = value; 168 + self 169 + } 170 + } 171 + 172 impl<'a, S> EntryBuilder<'a, S> 173 where 174 S: entry_state::State, ··· 179 mut self, 180 value: impl Into<jacquard_common::CowStr<'a>>, 181 ) -> EntryBuilder<'a, entry_state::SetContent<S>> { 182 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 183 EntryBuilder { 184 _phantom_state: ::core::marker::PhantomData, 185 __unsafe_private_named: self.__unsafe_private_named, ··· 198 mut self, 199 value: impl Into<jacquard_common::types::string::Datetime>, 200 ) -> EntryBuilder<'a, entry_state::SetCreatedAt<S>> { 201 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 202 EntryBuilder { 203 _phantom_state: ::core::marker::PhantomData, 204 __unsafe_private_named: self.__unsafe_private_named, ··· 210 impl<'a, S: entry_state::State> EntryBuilder<'a, S> { 211 /// Set the `embeds` field (optional) 212 pub fn embeds(mut self, value: impl Into<Option<EntryEmbeds<'a>>>) -> Self { 213 + self.__unsafe_private_named.3 = value.into(); 214 self 215 } 216 /// Set the `embeds` field to an Option value (optional) 217 pub fn maybe_embeds(mut self, value: Option<EntryEmbeds<'a>>) -> Self { 218 + self.__unsafe_private_named.3 = value; 219 self 220 } 221 } ··· 230 mut self, 231 value: impl Into<crate::sh_weaver::notebook::Path<'a>>, 232 ) -> EntryBuilder<'a, entry_state::SetPath<S>> { 233 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 234 EntryBuilder { 235 _phantom_state: ::core::marker::PhantomData, 236 __unsafe_private_named: self.__unsafe_private_named, ··· 245 mut self, 246 value: impl Into<Option<crate::sh_weaver::notebook::Tags<'a>>>, 247 ) -> Self { 248 + self.__unsafe_private_named.5 = value.into(); 249 self 250 } 251 /// Set the `tags` field to an Option value (optional) ··· 253 mut self, 254 value: Option<crate::sh_weaver::notebook::Tags<'a>>, 255 ) -> Self { 256 + self.__unsafe_private_named.5 = value; 257 self 258 } 259 } ··· 268 mut self, 269 value: impl Into<crate::sh_weaver::notebook::Title<'a>>, 270 ) -> EntryBuilder<'a, entry_state::SetTitle<S>> { 271 + self.__unsafe_private_named.6 = ::core::option::Option::Some(value.into()); 272 EntryBuilder { 273 _phantom_state: ::core::marker::PhantomData, 274 __unsafe_private_named: self.__unsafe_private_named, ··· 283 mut self, 284 value: impl Into<Option<jacquard_common::types::string::Datetime>>, 285 ) -> Self { 286 + self.__unsafe_private_named.7 = value.into(); 287 self 288 } 289 /// Set the `updatedAt` field to an Option value (optional) ··· 291 mut self, 292 value: Option<jacquard_common::types::string::Datetime>, 293 ) -> Self { 294 + self.__unsafe_private_named.7 = value; 295 self 296 } 297 } ··· 307 /// Build the final struct 308 pub fn build(self) -> Entry<'a> { 309 Entry { 310 + authors: self.__unsafe_private_named.0, 311 + content: self.__unsafe_private_named.1.unwrap(), 312 + created_at: self.__unsafe_private_named.2.unwrap(), 313 + embeds: self.__unsafe_private_named.3, 314 + path: self.__unsafe_private_named.4.unwrap(), 315 + tags: self.__unsafe_private_named.5, 316 + title: self.__unsafe_private_named.6.unwrap(), 317 + updated_at: self.__unsafe_private_named.7, 318 extra_data: Default::default(), 319 } 320 } ··· 327 >, 328 ) -> Entry<'a> { 329 Entry { 330 + authors: self.__unsafe_private_named.0, 331 + content: self.__unsafe_private_named.1.unwrap(), 332 + created_at: self.__unsafe_private_named.2.unwrap(), 333 + embeds: self.__unsafe_private_named.3, 334 + path: self.__unsafe_private_named.4.unwrap(), 335 + tags: self.__unsafe_private_named.5, 336 + title: self.__unsafe_private_named.6.unwrap(), 337 + updated_at: self.__unsafe_private_named.7, 338 extra_data: Some(extra_data), 339 } 340 } ··· 418 #[allow(unused_mut)] 419 let mut map = ::std::collections::BTreeMap::new(); 420 map.insert( 421 + ::jacquard_common::smol_str::SmolStr::new_static("authors"), 422 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 423 + description: None, 424 + items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 425 + description: None, 426 + r#ref: ::jacquard_common::CowStr::new_static( 427 + "sh.weaver.actor.defs#author", 428 + ), 429 + }), 430 + min_length: None, 431 + max_length: None, 432 + }), 433 + ); 434 + map.insert( 435 ::jacquard_common::smol_str::SmolStr::new_static("content"), 436 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 437 description: Some( ··· 442 format: None, 443 default: None, 444 min_length: None, 445 + max_length: None, 446 min_graphemes: None, 447 max_graphemes: None, 448 r#enum: None, ··· 682 fn validate( 683 &self, 684 ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 685 Ok(()) 686 } 687 }
+687 -31
crates/weaver-api/src/sh_weaver/notebook/theme.rs
··· 5 // This file was automatically generated from Lexicon schemas. 6 // Any manual changes will be overwritten on the next regeneration. 7 8 #[jacquard_derive::lexicon] 9 #[derive( 10 serde::Serialize, ··· 215 map.insert( 216 ::jacquard_common::smol_str::SmolStr::new_static("codeThemeFile"), 217 ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 218 - description: None, 219 required: Some( 220 vec![ 221 ::jacquard_common::smol_str::SmolStr::new_static("name"), ··· 287 }), 288 ); 289 map.insert( 290 ::jacquard_common::smol_str::SmolStr::new_static("main"), 291 ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 292 description: Some( ··· 357 map.insert( 358 ::jacquard_common::smol_str::SmolStr::new_static("fonts"), 359 ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { 360 - description: None, 361 required: Some( 362 vec![ 363 ::jacquard_common::smol_str::SmolStr::new_static("body"), ··· 371 let mut map = ::std::collections::BTreeMap::new(); 372 map.insert( 373 ::jacquard_common::smol_str::SmolStr::new_static("body"), 374 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 375 description: None, 376 - format: None, 377 - default: None, 378 min_length: None, 379 max_length: None, 380 - min_graphemes: None, 381 - max_graphemes: None, 382 - r#enum: None, 383 - r#const: None, 384 - known_values: None, 385 }), 386 ); 387 map.insert( 388 ::jacquard_common::smol_str::SmolStr::new_static("heading"), 389 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 390 description: None, 391 - format: None, 392 - default: None, 393 min_length: None, 394 max_length: None, 395 - min_graphemes: None, 396 - max_graphemes: None, 397 - r#enum: None, 398 - r#const: None, 399 - known_values: None, 400 }), 401 ); 402 map.insert( 403 ::jacquard_common::smol_str::SmolStr::new_static( 404 "monospace", 405 ), 406 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 407 description: None, 408 - format: None, 409 - default: None, 410 min_length: None, 411 max_length: None, 412 - min_graphemes: None, 413 - max_graphemes: None, 414 - r#enum: None, 415 - r#const: None, 416 - known_values: None, 417 }), 418 ); 419 map ··· 544 } 545 546 pub type CodeThemeName<'a> = jacquard_common::CowStr<'a>; 547 /// Theme for a notebook 548 #[jacquard_derive::lexicon] 549 #[derive( ··· 566 #[serde(skip_serializing_if = "std::option::Option::is_none")] 567 #[serde(borrow)] 568 pub default_theme: std::option::Option<jacquard_common::CowStr<'a>>, 569 #[serde(borrow)] 570 pub fonts: ThemeFonts<'a>, 571 /// Syntax highlighting theme for light mode ··· 932 CodeThemeFile(Box<crate::sh_weaver::notebook::theme::CodeThemeFile<'a>>), 933 } 934 935 #[jacquard_derive::lexicon] 936 #[derive( 937 serde::Serialize, ··· 940 Clone, 941 PartialEq, 942 Eq, 943 - jacquard_derive::IntoStatic, 944 - Default 945 )] 946 #[serde(rename_all = "camelCase")] 947 pub struct ThemeFonts<'a> { 948 #[serde(borrow)] 949 - pub body: jacquard_common::CowStr<'a>, 950 #[serde(borrow)] 951 - pub heading: jacquard_common::CowStr<'a>, 952 #[serde(borrow)] 953 - pub monospace: jacquard_common::CowStr<'a>, 954 } 955 956 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeFonts<'a> {
··· 5 // This file was automatically generated from Lexicon schemas. 6 // Any manual changes will be overwritten on the next regeneration. 7 8 + /// Custom syntax highlighting theme file (sublime text/textmate theme format) 9 #[jacquard_derive::lexicon] 10 #[derive( 11 serde::Serialize, ··· 216 map.insert( 217 ::jacquard_common::smol_str::SmolStr::new_static("codeThemeFile"), 218 ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 219 + description: Some( 220 + ::jacquard_common::CowStr::new_static( 221 + "Custom syntax highlighting theme file (sublime text/textmate theme format)", 222 + ), 223 + ), 224 required: Some( 225 vec![ 226 ::jacquard_common::smol_str::SmolStr::new_static("name"), ··· 292 }), 293 ); 294 map.insert( 295 + ::jacquard_common::smol_str::SmolStr::new_static("font"), 296 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 297 + description: None, 298 + required: Some( 299 + vec![::jacquard_common::smol_str::SmolStr::new_static("value")], 300 + ), 301 + nullable: None, 302 + properties: { 303 + #[allow(unused_mut)] 304 + let mut map = ::std::collections::BTreeMap::new(); 305 + map.insert( 306 + ::jacquard_common::smol_str::SmolStr::new_static("value"), 307 + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { 308 + description: Some( 309 + ::jacquard_common::CowStr::new_static("Font for a notebook"), 310 + ), 311 + refs: vec![ 312 + ::jacquard_common::CowStr::new_static("#fontName"), 313 + ::jacquard_common::CowStr::new_static("#fontFile") 314 + ], 315 + closed: None, 316 + }), 317 + ); 318 + map 319 + }, 320 + }), 321 + ); 322 + map.insert( 323 + ::jacquard_common::smol_str::SmolStr::new_static("fontFile"), 324 + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 325 + description: Some( 326 + ::jacquard_common::CowStr::new_static( 327 + "Custom woff(2) or ttf font file", 328 + ), 329 + ), 330 + required: Some( 331 + vec![ 332 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 333 + ::jacquard_common::smol_str::SmolStr::new_static("did"), 334 + ::jacquard_common::smol_str::SmolStr::new_static("content") 335 + ], 336 + ), 337 + nullable: None, 338 + properties: { 339 + #[allow(unused_mut)] 340 + let mut map = ::std::collections::BTreeMap::new(); 341 + map.insert( 342 + ::jacquard_common::smol_str::SmolStr::new_static("content"), 343 + ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { 344 + description: None, 345 + accept: None, 346 + max_size: None, 347 + }), 348 + ); 349 + map.insert( 350 + ::jacquard_common::smol_str::SmolStr::new_static("did"), 351 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 352 + description: None, 353 + format: Some( 354 + ::jacquard_lexicon::lexicon::LexStringFormat::Did, 355 + ), 356 + default: None, 357 + min_length: None, 358 + max_length: None, 359 + min_graphemes: None, 360 + max_graphemes: None, 361 + r#enum: None, 362 + r#const: None, 363 + known_values: None, 364 + }), 365 + ); 366 + map.insert( 367 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 368 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 369 + description: None, 370 + format: None, 371 + default: None, 372 + min_length: None, 373 + max_length: None, 374 + min_graphemes: None, 375 + max_graphemes: None, 376 + r#enum: None, 377 + r#const: None, 378 + known_values: None, 379 + }), 380 + ); 381 + map 382 + }, 383 + }), 384 + ); 385 + map.insert( 386 + ::jacquard_common::smol_str::SmolStr::new_static("fontName"), 387 + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { 388 + description: None, 389 + format: None, 390 + default: None, 391 + min_length: None, 392 + max_length: None, 393 + min_graphemes: None, 394 + max_graphemes: None, 395 + r#enum: None, 396 + r#const: None, 397 + known_values: None, 398 + }), 399 + ); 400 + map.insert( 401 ::jacquard_common::smol_str::SmolStr::new_static("main"), 402 ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 403 description: Some( ··· 468 map.insert( 469 ::jacquard_common::smol_str::SmolStr::new_static("fonts"), 470 ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { 471 + description: Some( 472 + ::jacquard_common::CowStr::new_static( 473 + "Fonts to be used in the notebook. Can specify a name or list of names (will load if available) or a file or list of files for each. Empty lists will use site defaults.", 474 + ), 475 + ), 476 required: Some( 477 vec![ 478 ::jacquard_common::smol_str::SmolStr::new_static("body"), ··· 486 let mut map = ::std::collections::BTreeMap::new(); 487 map.insert( 488 ::jacquard_common::smol_str::SmolStr::new_static("body"), 489 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 490 description: None, 491 + items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 492 + description: None, 493 + r#ref: ::jacquard_common::CowStr::new_static("#font"), 494 + }), 495 min_length: None, 496 max_length: None, 497 }), 498 ); 499 map.insert( 500 ::jacquard_common::smol_str::SmolStr::new_static("heading"), 501 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 502 description: None, 503 + items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 504 + description: None, 505 + r#ref: ::jacquard_common::CowStr::new_static("#font"), 506 + }), 507 min_length: None, 508 max_length: None, 509 }), 510 ); 511 map.insert( 512 ::jacquard_common::smol_str::SmolStr::new_static( 513 "monospace", 514 ), 515 + ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 516 description: None, 517 + items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 518 + description: None, 519 + r#ref: ::jacquard_common::CowStr::new_static("#font"), 520 + }), 521 min_length: None, 522 max_length: None, 523 }), 524 ); 525 map ··· 650 } 651 652 pub type CodeThemeName<'a> = jacquard_common::CowStr<'a>; 653 + #[jacquard_derive::lexicon] 654 + #[derive( 655 + serde::Serialize, 656 + serde::Deserialize, 657 + Debug, 658 + Clone, 659 + PartialEq, 660 + Eq, 661 + jacquard_derive::IntoStatic 662 + )] 663 + #[serde(rename_all = "camelCase")] 664 + pub struct Font<'a> { 665 + /// Font for a notebook 666 + #[serde(borrow)] 667 + pub value: FontValue<'a>, 668 + } 669 + 670 + pub mod font_state { 671 + 672 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 673 + #[allow(unused)] 674 + use ::core::marker::PhantomData; 675 + mod sealed { 676 + pub trait Sealed {} 677 + } 678 + /// State trait tracking which required fields have been set 679 + pub trait State: sealed::Sealed { 680 + type Value; 681 + } 682 + /// Empty state - all required fields are unset 683 + pub struct Empty(()); 684 + impl sealed::Sealed for Empty {} 685 + impl State for Empty { 686 + type Value = Unset; 687 + } 688 + ///State transition - sets the `value` field to Set 689 + pub struct SetValue<S: State = Empty>(PhantomData<fn() -> S>); 690 + impl<S: State> sealed::Sealed for SetValue<S> {} 691 + impl<S: State> State for SetValue<S> { 692 + type Value = Set<members::value>; 693 + } 694 + /// Marker types for field names 695 + #[allow(non_camel_case_types)] 696 + pub mod members { 697 + ///Marker type for the `value` field 698 + pub struct value(()); 699 + } 700 + } 701 + 702 + /// Builder for constructing an instance of this type 703 + pub struct FontBuilder<'a, S: font_state::State> { 704 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 705 + __unsafe_private_named: (::core::option::Option<FontValue<'a>>,), 706 + _phantom: ::core::marker::PhantomData<&'a ()>, 707 + } 708 + 709 + impl<'a> Font<'a> { 710 + /// Create a new builder for this type 711 + pub fn new() -> FontBuilder<'a, font_state::Empty> { 712 + FontBuilder::new() 713 + } 714 + } 715 + 716 + impl<'a> FontBuilder<'a, font_state::Empty> { 717 + /// Create a new builder with all fields unset 718 + pub fn new() -> Self { 719 + FontBuilder { 720 + _phantom_state: ::core::marker::PhantomData, 721 + __unsafe_private_named: (None,), 722 + _phantom: ::core::marker::PhantomData, 723 + } 724 + } 725 + } 726 + 727 + impl<'a, S> FontBuilder<'a, S> 728 + where 729 + S: font_state::State, 730 + S::Value: font_state::IsUnset, 731 + { 732 + /// Set the `value` field (required) 733 + pub fn value( 734 + mut self, 735 + value: impl Into<FontValue<'a>>, 736 + ) -> FontBuilder<'a, font_state::SetValue<S>> { 737 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 738 + FontBuilder { 739 + _phantom_state: ::core::marker::PhantomData, 740 + __unsafe_private_named: self.__unsafe_private_named, 741 + _phantom: ::core::marker::PhantomData, 742 + } 743 + } 744 + } 745 + 746 + impl<'a, S> FontBuilder<'a, S> 747 + where 748 + S: font_state::State, 749 + S::Value: font_state::IsSet, 750 + { 751 + /// Build the final struct 752 + pub fn build(self) -> Font<'a> { 753 + Font { 754 + value: self.__unsafe_private_named.0.unwrap(), 755 + extra_data: Default::default(), 756 + } 757 + } 758 + /// Build the final struct with custom extra_data 759 + pub fn build_with_data( 760 + self, 761 + extra_data: std::collections::BTreeMap< 762 + jacquard_common::smol_str::SmolStr, 763 + jacquard_common::types::value::Data<'a>, 764 + >, 765 + ) -> Font<'a> { 766 + Font { 767 + value: self.__unsafe_private_named.0.unwrap(), 768 + extra_data: Some(extra_data), 769 + } 770 + } 771 + } 772 + 773 + #[jacquard_derive::open_union] 774 + #[derive( 775 + serde::Serialize, 776 + serde::Deserialize, 777 + Debug, 778 + Clone, 779 + PartialEq, 780 + Eq, 781 + jacquard_derive::IntoStatic 782 + )] 783 + #[serde(tag = "$type")] 784 + #[serde(bound(deserialize = "'de: 'a"))] 785 + pub enum FontValue<'a> { 786 + #[serde(rename = "sh.weaver.notebook.theme#fontName")] 787 + FontName(Box<crate::sh_weaver::notebook::theme::FontName<'a>>), 788 + #[serde(rename = "sh.weaver.notebook.theme#fontFile")] 789 + FontFile(Box<crate::sh_weaver::notebook::theme::FontFile<'a>>), 790 + } 791 + 792 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Font<'a> { 793 + fn nsid() -> &'static str { 794 + "sh.weaver.notebook.theme" 795 + } 796 + fn def_name() -> &'static str { 797 + "font" 798 + } 799 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 800 + lexicon_doc_sh_weaver_notebook_theme() 801 + } 802 + fn validate( 803 + &self, 804 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 805 + Ok(()) 806 + } 807 + } 808 + 809 + /// Custom woff(2) or ttf font file 810 + #[jacquard_derive::lexicon] 811 + #[derive( 812 + serde::Serialize, 813 + serde::Deserialize, 814 + Debug, 815 + Clone, 816 + PartialEq, 817 + Eq, 818 + jacquard_derive::IntoStatic 819 + )] 820 + #[serde(rename_all = "camelCase")] 821 + pub struct FontFile<'a> { 822 + #[serde(borrow)] 823 + pub content: jacquard_common::types::blob::BlobRef<'a>, 824 + #[serde(borrow)] 825 + pub did: jacquard_common::types::string::Did<'a>, 826 + #[serde(borrow)] 827 + pub name: jacquard_common::CowStr<'a>, 828 + } 829 + 830 + pub mod font_file_state { 831 + 832 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 833 + #[allow(unused)] 834 + use ::core::marker::PhantomData; 835 + mod sealed { 836 + pub trait Sealed {} 837 + } 838 + /// State trait tracking which required fields have been set 839 + pub trait State: sealed::Sealed { 840 + type Name; 841 + type Did; 842 + type Content; 843 + } 844 + /// Empty state - all required fields are unset 845 + pub struct Empty(()); 846 + impl sealed::Sealed for Empty {} 847 + impl State for Empty { 848 + type Name = Unset; 849 + type Did = Unset; 850 + type Content = Unset; 851 + } 852 + ///State transition - sets the `name` field to Set 853 + pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>); 854 + impl<S: State> sealed::Sealed for SetName<S> {} 855 + impl<S: State> State for SetName<S> { 856 + type Name = Set<members::name>; 857 + type Did = S::Did; 858 + type Content = S::Content; 859 + } 860 + ///State transition - sets the `did` field to Set 861 + pub struct SetDid<S: State = Empty>(PhantomData<fn() -> S>); 862 + impl<S: State> sealed::Sealed for SetDid<S> {} 863 + impl<S: State> State for SetDid<S> { 864 + type Name = S::Name; 865 + type Did = Set<members::did>; 866 + type Content = S::Content; 867 + } 868 + ///State transition - sets the `content` field to Set 869 + pub struct SetContent<S: State = Empty>(PhantomData<fn() -> S>); 870 + impl<S: State> sealed::Sealed for SetContent<S> {} 871 + impl<S: State> State for SetContent<S> { 872 + type Name = S::Name; 873 + type Did = S::Did; 874 + type Content = Set<members::content>; 875 + } 876 + /// Marker types for field names 877 + #[allow(non_camel_case_types)] 878 + pub mod members { 879 + ///Marker type for the `name` field 880 + pub struct name(()); 881 + ///Marker type for the `did` field 882 + pub struct did(()); 883 + ///Marker type for the `content` field 884 + pub struct content(()); 885 + } 886 + } 887 + 888 + /// Builder for constructing an instance of this type 889 + pub struct FontFileBuilder<'a, S: font_file_state::State> { 890 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 891 + __unsafe_private_named: ( 892 + ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 893 + ::core::option::Option<jacquard_common::types::string::Did<'a>>, 894 + ::core::option::Option<jacquard_common::CowStr<'a>>, 895 + ), 896 + _phantom: ::core::marker::PhantomData<&'a ()>, 897 + } 898 + 899 + impl<'a> FontFile<'a> { 900 + /// Create a new builder for this type 901 + pub fn new() -> FontFileBuilder<'a, font_file_state::Empty> { 902 + FontFileBuilder::new() 903 + } 904 + } 905 + 906 + impl<'a> FontFileBuilder<'a, font_file_state::Empty> { 907 + /// Create a new builder with all fields unset 908 + pub fn new() -> Self { 909 + FontFileBuilder { 910 + _phantom_state: ::core::marker::PhantomData, 911 + __unsafe_private_named: (None, None, None), 912 + _phantom: ::core::marker::PhantomData, 913 + } 914 + } 915 + } 916 + 917 + impl<'a, S> FontFileBuilder<'a, S> 918 + where 919 + S: font_file_state::State, 920 + S::Content: font_file_state::IsUnset, 921 + { 922 + /// Set the `content` field (required) 923 + pub fn content( 924 + mut self, 925 + value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 926 + ) -> FontFileBuilder<'a, font_file_state::SetContent<S>> { 927 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 928 + FontFileBuilder { 929 + _phantom_state: ::core::marker::PhantomData, 930 + __unsafe_private_named: self.__unsafe_private_named, 931 + _phantom: ::core::marker::PhantomData, 932 + } 933 + } 934 + } 935 + 936 + impl<'a, S> FontFileBuilder<'a, S> 937 + where 938 + S: font_file_state::State, 939 + S::Did: font_file_state::IsUnset, 940 + { 941 + /// Set the `did` field (required) 942 + pub fn did( 943 + mut self, 944 + value: impl Into<jacquard_common::types::string::Did<'a>>, 945 + ) -> FontFileBuilder<'a, font_file_state::SetDid<S>> { 946 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 947 + FontFileBuilder { 948 + _phantom_state: ::core::marker::PhantomData, 949 + __unsafe_private_named: self.__unsafe_private_named, 950 + _phantom: ::core::marker::PhantomData, 951 + } 952 + } 953 + } 954 + 955 + impl<'a, S> FontFileBuilder<'a, S> 956 + where 957 + S: font_file_state::State, 958 + S::Name: font_file_state::IsUnset, 959 + { 960 + /// Set the `name` field (required) 961 + pub fn name( 962 + mut self, 963 + value: impl Into<jacquard_common::CowStr<'a>>, 964 + ) -> FontFileBuilder<'a, font_file_state::SetName<S>> { 965 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 966 + FontFileBuilder { 967 + _phantom_state: ::core::marker::PhantomData, 968 + __unsafe_private_named: self.__unsafe_private_named, 969 + _phantom: ::core::marker::PhantomData, 970 + } 971 + } 972 + } 973 + 974 + impl<'a, S> FontFileBuilder<'a, S> 975 + where 976 + S: font_file_state::State, 977 + S::Name: font_file_state::IsSet, 978 + S::Did: font_file_state::IsSet, 979 + S::Content: font_file_state::IsSet, 980 + { 981 + /// Build the final struct 982 + pub fn build(self) -> FontFile<'a> { 983 + FontFile { 984 + content: self.__unsafe_private_named.0.unwrap(), 985 + did: self.__unsafe_private_named.1.unwrap(), 986 + name: self.__unsafe_private_named.2.unwrap(), 987 + extra_data: Default::default(), 988 + } 989 + } 990 + /// Build the final struct with custom extra_data 991 + pub fn build_with_data( 992 + self, 993 + extra_data: std::collections::BTreeMap< 994 + jacquard_common::smol_str::SmolStr, 995 + jacquard_common::types::value::Data<'a>, 996 + >, 997 + ) -> FontFile<'a> { 998 + FontFile { 999 + content: self.__unsafe_private_named.0.unwrap(), 1000 + did: self.__unsafe_private_named.1.unwrap(), 1001 + name: self.__unsafe_private_named.2.unwrap(), 1002 + extra_data: Some(extra_data), 1003 + } 1004 + } 1005 + } 1006 + 1007 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for FontFile<'a> { 1008 + fn nsid() -> &'static str { 1009 + "sh.weaver.notebook.theme" 1010 + } 1011 + fn def_name() -> &'static str { 1012 + "fontFile" 1013 + } 1014 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1015 + lexicon_doc_sh_weaver_notebook_theme() 1016 + } 1017 + fn validate( 1018 + &self, 1019 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1020 + Ok(()) 1021 + } 1022 + } 1023 + 1024 + pub type FontName<'a> = jacquard_common::CowStr<'a>; 1025 /// Theme for a notebook 1026 #[jacquard_derive::lexicon] 1027 #[derive( ··· 1044 #[serde(skip_serializing_if = "std::option::Option::is_none")] 1045 #[serde(borrow)] 1046 pub default_theme: std::option::Option<jacquard_common::CowStr<'a>>, 1047 + /// Fonts to be used in the notebook. Can specify a name or list of names (will load if available) or a file or list of files for each. Empty lists will use site defaults. 1048 #[serde(borrow)] 1049 pub fonts: ThemeFonts<'a>, 1050 /// Syntax highlighting theme for light mode ··· 1411 CodeThemeFile(Box<crate::sh_weaver::notebook::theme::CodeThemeFile<'a>>), 1412 } 1413 1414 + /// Fonts to be used in the notebook. Can specify a name or list of names (will load if available) or a file or list of files for each. Empty lists will use site defaults. 1415 #[jacquard_derive::lexicon] 1416 #[derive( 1417 serde::Serialize, ··· 1420 Clone, 1421 PartialEq, 1422 Eq, 1423 + jacquard_derive::IntoStatic 1424 )] 1425 #[serde(rename_all = "camelCase")] 1426 pub struct ThemeFonts<'a> { 1427 #[serde(borrow)] 1428 + pub body: Vec<crate::sh_weaver::notebook::theme::Font<'a>>, 1429 #[serde(borrow)] 1430 + pub heading: Vec<crate::sh_weaver::notebook::theme::Font<'a>>, 1431 #[serde(borrow)] 1432 + pub monospace: Vec<crate::sh_weaver::notebook::theme::Font<'a>>, 1433 + } 1434 + 1435 + pub mod theme_fonts_state { 1436 + 1437 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1438 + #[allow(unused)] 1439 + use ::core::marker::PhantomData; 1440 + mod sealed { 1441 + pub trait Sealed {} 1442 + } 1443 + /// State trait tracking which required fields have been set 1444 + pub trait State: sealed::Sealed { 1445 + type Body; 1446 + type Heading; 1447 + type Monospace; 1448 + } 1449 + /// Empty state - all required fields are unset 1450 + pub struct Empty(()); 1451 + impl sealed::Sealed for Empty {} 1452 + impl State for Empty { 1453 + type Body = Unset; 1454 + type Heading = Unset; 1455 + type Monospace = Unset; 1456 + } 1457 + ///State transition - sets the `body` field to Set 1458 + pub struct SetBody<S: State = Empty>(PhantomData<fn() -> S>); 1459 + impl<S: State> sealed::Sealed for SetBody<S> {} 1460 + impl<S: State> State for SetBody<S> { 1461 + type Body = Set<members::body>; 1462 + type Heading = S::Heading; 1463 + type Monospace = S::Monospace; 1464 + } 1465 + ///State transition - sets the `heading` field to Set 1466 + pub struct SetHeading<S: State = Empty>(PhantomData<fn() -> S>); 1467 + impl<S: State> sealed::Sealed for SetHeading<S> {} 1468 + impl<S: State> State for SetHeading<S> { 1469 + type Body = S::Body; 1470 + type Heading = Set<members::heading>; 1471 + type Monospace = S::Monospace; 1472 + } 1473 + ///State transition - sets the `monospace` field to Set 1474 + pub struct SetMonospace<S: State = Empty>(PhantomData<fn() -> S>); 1475 + impl<S: State> sealed::Sealed for SetMonospace<S> {} 1476 + impl<S: State> State for SetMonospace<S> { 1477 + type Body = S::Body; 1478 + type Heading = S::Heading; 1479 + type Monospace = Set<members::monospace>; 1480 + } 1481 + /// Marker types for field names 1482 + #[allow(non_camel_case_types)] 1483 + pub mod members { 1484 + ///Marker type for the `body` field 1485 + pub struct body(()); 1486 + ///Marker type for the `heading` field 1487 + pub struct heading(()); 1488 + ///Marker type for the `monospace` field 1489 + pub struct monospace(()); 1490 + } 1491 + } 1492 + 1493 + /// Builder for constructing an instance of this type 1494 + pub struct ThemeFontsBuilder<'a, S: theme_fonts_state::State> { 1495 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1496 + __unsafe_private_named: ( 1497 + ::core::option::Option<Vec<crate::sh_weaver::notebook::theme::Font<'a>>>, 1498 + ::core::option::Option<Vec<crate::sh_weaver::notebook::theme::Font<'a>>>, 1499 + ::core::option::Option<Vec<crate::sh_weaver::notebook::theme::Font<'a>>>, 1500 + ), 1501 + _phantom: ::core::marker::PhantomData<&'a ()>, 1502 + } 1503 + 1504 + impl<'a> ThemeFonts<'a> { 1505 + /// Create a new builder for this type 1506 + pub fn new() -> ThemeFontsBuilder<'a, theme_fonts_state::Empty> { 1507 + ThemeFontsBuilder::new() 1508 + } 1509 + } 1510 + 1511 + impl<'a> ThemeFontsBuilder<'a, theme_fonts_state::Empty> { 1512 + /// Create a new builder with all fields unset 1513 + pub fn new() -> Self { 1514 + ThemeFontsBuilder { 1515 + _phantom_state: ::core::marker::PhantomData, 1516 + __unsafe_private_named: (None, None, None), 1517 + _phantom: ::core::marker::PhantomData, 1518 + } 1519 + } 1520 + } 1521 + 1522 + impl<'a, S> ThemeFontsBuilder<'a, S> 1523 + where 1524 + S: theme_fonts_state::State, 1525 + S::Body: theme_fonts_state::IsUnset, 1526 + { 1527 + /// Set the `body` field (required) 1528 + pub fn body( 1529 + mut self, 1530 + value: impl Into<Vec<crate::sh_weaver::notebook::theme::Font<'a>>>, 1531 + ) -> ThemeFontsBuilder<'a, theme_fonts_state::SetBody<S>> { 1532 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1533 + ThemeFontsBuilder { 1534 + _phantom_state: ::core::marker::PhantomData, 1535 + __unsafe_private_named: self.__unsafe_private_named, 1536 + _phantom: ::core::marker::PhantomData, 1537 + } 1538 + } 1539 + } 1540 + 1541 + impl<'a, S> ThemeFontsBuilder<'a, S> 1542 + where 1543 + S: theme_fonts_state::State, 1544 + S::Heading: theme_fonts_state::IsUnset, 1545 + { 1546 + /// Set the `heading` field (required) 1547 + pub fn heading( 1548 + mut self, 1549 + value: impl Into<Vec<crate::sh_weaver::notebook::theme::Font<'a>>>, 1550 + ) -> ThemeFontsBuilder<'a, theme_fonts_state::SetHeading<S>> { 1551 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 1552 + ThemeFontsBuilder { 1553 + _phantom_state: ::core::marker::PhantomData, 1554 + __unsafe_private_named: self.__unsafe_private_named, 1555 + _phantom: ::core::marker::PhantomData, 1556 + } 1557 + } 1558 + } 1559 + 1560 + impl<'a, S> ThemeFontsBuilder<'a, S> 1561 + where 1562 + S: theme_fonts_state::State, 1563 + S::Monospace: theme_fonts_state::IsUnset, 1564 + { 1565 + /// Set the `monospace` field (required) 1566 + pub fn monospace( 1567 + mut self, 1568 + value: impl Into<Vec<crate::sh_weaver::notebook::theme::Font<'a>>>, 1569 + ) -> ThemeFontsBuilder<'a, theme_fonts_state::SetMonospace<S>> { 1570 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 1571 + ThemeFontsBuilder { 1572 + _phantom_state: ::core::marker::PhantomData, 1573 + __unsafe_private_named: self.__unsafe_private_named, 1574 + _phantom: ::core::marker::PhantomData, 1575 + } 1576 + } 1577 + } 1578 + 1579 + impl<'a, S> ThemeFontsBuilder<'a, S> 1580 + where 1581 + S: theme_fonts_state::State, 1582 + S::Body: theme_fonts_state::IsSet, 1583 + S::Heading: theme_fonts_state::IsSet, 1584 + S::Monospace: theme_fonts_state::IsSet, 1585 + { 1586 + /// Build the final struct 1587 + pub fn build(self) -> ThemeFonts<'a> { 1588 + ThemeFonts { 1589 + body: self.__unsafe_private_named.0.unwrap(), 1590 + heading: self.__unsafe_private_named.1.unwrap(), 1591 + monospace: self.__unsafe_private_named.2.unwrap(), 1592 + extra_data: Default::default(), 1593 + } 1594 + } 1595 + /// Build the final struct with custom extra_data 1596 + pub fn build_with_data( 1597 + self, 1598 + extra_data: std::collections::BTreeMap< 1599 + jacquard_common::smol_str::SmolStr, 1600 + jacquard_common::types::value::Data<'a>, 1601 + >, 1602 + ) -> ThemeFonts<'a> { 1603 + ThemeFonts { 1604 + body: self.__unsafe_private_named.0.unwrap(), 1605 + heading: self.__unsafe_private_named.1.unwrap(), 1606 + monospace: self.__unsafe_private_named.2.unwrap(), 1607 + extra_data: Some(extra_data), 1608 + } 1609 + } 1610 } 1611 1612 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeFonts<'a> {
+150
crates/weaver-app/assets/styling/cards-base.css
···
··· 1 + /* Shared card primitives 2 + Used by entry-card.css and notebook-card.css */ 3 + 4 + /* ========================================================================== 5 + CARD SURFACE 6 + Container with background, shadow (light) or border (dark) 7 + ========================================================================== */ 8 + 9 + .card-surface { 10 + background: var(--color-surface); 11 + box-shadow: 0 1px 2px color-mix(in srgb, var(--color-text) 6%, transparent); 12 + } 13 + 14 + @media (prefers-color-scheme: dark) { 15 + .card-surface { 16 + box-shadow: none; 17 + border: 1px solid var(--color-border); 18 + } 19 + } 20 + 21 + /* ========================================================================== 22 + CARD TITLE 23 + Heading style for card titles 24 + ========================================================================== */ 25 + 26 + .card-title { 27 + font-family: var(--font-heading); 28 + font-weight: 600; 29 + color: var(--color-primary); 30 + margin: 0; 31 + transition: color 0.2s ease; 32 + } 33 + 34 + /* ========================================================================== 35 + CARD META 36 + Muted metadata text (dates, counts, etc.) 37 + ========================================================================== */ 38 + 39 + .card-meta { 40 + color: var(--color-muted); 41 + font-size: 0.85rem; 42 + } 43 + 44 + /* ========================================================================== 45 + CARD TAGS 46 + Tag list container and individual tags 47 + ========================================================================== */ 48 + 49 + .card-tags { 50 + display: flex; 51 + gap: 0.4rem; 52 + flex-wrap: wrap; 53 + } 54 + 55 + .card-tag { 56 + padding: 0.2rem 0.4rem 0.2rem 0; 57 + font-size: 0.75rem; 58 + color: var(--color-subtle); 59 + border-bottom: 1px solid var(--color-border); 60 + transition: 61 + color 0.15s ease, 62 + border-color 0.15s ease; 63 + } 64 + 65 + /* ========================================================================== 66 + CARD PREVIEW 67 + Truncated content preview with line clamping 68 + ========================================================================== */ 69 + 70 + .card-preview { 71 + color: var(--color-subtle); 72 + font-family: var(--font-body); 73 + font-size: 0.875rem; 74 + line-height: 1.5; 75 + display: -webkit-box; 76 + -webkit-box-orient: vertical; 77 + overflow: hidden; 78 + } 79 + 80 + .card-preview-3 { 81 + -webkit-line-clamp: 3; 82 + } 83 + 84 + .card-preview-4 { 85 + -webkit-line-clamp: 4; 86 + } 87 + 88 + /* Preview content normalization */ 89 + .card-preview h1, 90 + .card-preview h2, 91 + .card-preview h3, 92 + .card-preview h4, 93 + .card-preview h5, 94 + .card-preview h6 { 95 + font-size: 0.875rem; 96 + font-weight: 600; 97 + margin-top: 0; 98 + margin-left: 0; 99 + margin-right: 0; 100 + } 101 + 102 + .card-preview p { 103 + margin: 0; 104 + display: inline; 105 + } 106 + 107 + .card-preview code { 108 + font-size: 0.8rem; 109 + white-space: pre-wrap; 110 + word-break: break-all; 111 + } 112 + 113 + .card-preview pre { 114 + white-space: pre-wrap; 115 + word-break: break-all; 116 + max-width: 100%; 117 + } 118 + 119 + /* ========================================================================== 120 + CARD HOVER STATES 121 + Common hover patterns for cards 122 + ========================================================================== */ 123 + 124 + .card-hover-border:hover { 125 + border-left-color: var(--color-secondary); 126 + } 127 + 128 + .card-hover-title:hover .card-title { 129 + color: var(--color-secondary); 130 + } 131 + 132 + .card-hover-tags:hover .card-tag { 133 + color: var(--color-tertiary); 134 + border-color: var(--color-tertiary); 135 + } 136 + 137 + /* ========================================================================== 138 + LOADING/ERROR STATES 139 + ========================================================================== */ 140 + 141 + .card-loading, 142 + .card-error { 143 + text-align: center; 144 + padding: 2rem; 145 + color: var(--color-muted); 146 + } 147 + 148 + .card-error { 149 + color: var(--color-error); 150 + }
+7 -72
crates/weaver-app/assets/styling/entry-card.css
··· 1 /* Entry card styling */ 2 - 3 - /* Notebook layout - sidebar in left gutter on desktop, header on mobile */ 4 - .notebook-layout { 5 - display: grid; 6 - grid-template-columns: minmax(280px, 1fr) minmax(0, 90rem) minmax(280px, 1fr); 7 - gap: 2rem; 8 - max-width: calc(90rem + 560px + 4rem); /* content + gutters + gaps */ 9 - margin: 0 auto; 10 - padding: 2.5rem 1.25rem 2.5rem 0; 11 - } 12 - 13 - .notebook-sidebar { 14 - grid-column: 1; 15 - position: sticky; 16 - top: 2rem; 17 - align-self: flex-start; 18 - max-height: calc(100vh - 4rem); 19 - overflow-y: auto; 20 - } 21 - 22 - .notebook-main { 23 - grid-column: 2; 24 - padding: 0 1rem; 25 - } 26 - 27 - /* Mobile layout - sidebar becomes header */ 28 - @media (max-width: 1400px) { 29 - .notebook-layout { 30 - grid-template-columns: minmax(1rem, 1fr) minmax(0, 90rem) minmax(1rem, 1fr) !important; 31 - gap: 0 !important; 32 - max-width: 100vw !important; 33 - box-sizing: border-box !important; 34 - } 35 36 - .notebook-sidebar { 37 - grid-column: 2; 38 - position: static; 39 - max-height: none; 40 - min-width: 0; 41 - margin-bottom: 2rem; 42 - } 43 - 44 - .notebook-main { 45 - grid-column: 2; 46 - padding: 0; 47 - min-width: 0; 48 - } 49 - } 50 - 51 - /* Entries list - width constrained by grid column */ 52 .entries-list { 53 } 54 ··· 110 color: var(--color-subtle); 111 } 112 113 - a.entry-card-author, 114 .entry-card-author { 115 - display: flex; 116 - align-items: center; 117 - gap: 0.5rem; 118 margin-top: 0.25rem; 119 - text-decoration: none; 120 - } 121 - 122 - .entry-card-author .author-name { 123 - font-weight: 500; 124 - color: var(--color-text); 125 - transition: color 0.2s ease; 126 - } 127 - 128 - .entry-card-author .meta-label { 129 - color: var(--color-muted); 130 - font-size: 0.8rem; 131 - transition: color 0.2s ease; 132 - } 133 - 134 - .entry-card-author:hover .author-name, 135 - .entry-card-author:hover .meta-label, 136 - .feed-entry-card .entry-card-author:hover .meta-label { 137 - color: var(--color-link); 138 } 139 140 .entry-card-date { ··· 173 } 174 175 .feed-entry-card .entry-card-byline .entry-card-date { 176 margin-left: auto; 177 } 178
··· 1 /* Entry card styling */ 2 + /* Layout classes (.notebook-layout, etc.) are now in layouts.css */ 3 4 .entries-list { 5 } 6 ··· 62 color: var(--color-subtle); 63 } 64 65 + /* Card-specific author positioning (uses embed-author base styles) */ 66 .entry-card-author { 67 margin-top: 0.25rem; 68 } 69 70 .entry-card-date { ··· 103 } 104 105 .feed-entry-card .entry-card-byline .entry-card-date { 106 + margin-left: auto; 107 + } 108 + 109 + /* Date in header (no author) aligns right */ 110 + .feed-entry-card .entry-card-header .entry-card-date { 111 margin-left: auto; 112 } 113
+190
crates/weaver-app/assets/styling/layouts.css
···
··· 1 + /* Sidebar layouts - 3-column grid with sticky sidebar */ 2 + 3 + /* Base sidebar layout pattern 4 + Uses CSS custom properties for sizing flexibility: 5 + --layout-gutter: sidebar/gutter width (default: 240px) 6 + --layout-content: main content max-width (default: 95ch) 7 + --layout-gap: grid gap (default: 1.5rem) 8 + --layout-padding: container padding (default: 2rem 1.25rem 2rem 0) 9 + */ 10 + 11 + .sidebar-layout { 12 + display: grid; 13 + grid-template-columns: 14 + minmax(var(--layout-gutter, 240px), 1fr) 15 + minmax(0, var(--layout-content, 95ch)) 16 + minmax(var(--layout-gutter, 240px), 1fr); 17 + gap: var(--layout-gap, 1.5rem); 18 + max-width: calc(var(--layout-content, 95ch) + var(--layout-gutter, 240px) * 2 + 4rem); 19 + margin: 0 auto; 20 + padding: var(--layout-padding, 2rem 1.25rem 2rem 0); 21 + } 22 + 23 + .sidebar-layout-sidebar { 24 + grid-column: 1; 25 + position: sticky; 26 + top: 2rem; 27 + align-self: flex-start; 28 + max-height: calc(100vh - 4rem); 29 + overflow-y: auto; 30 + } 31 + 32 + .sidebar-layout-main { 33 + grid-column: 2; 34 + padding: 0 1rem; 35 + } 36 + 37 + .sidebar-layout-actions { 38 + grid-column: 3; 39 + position: sticky; 40 + top: 2rem; 41 + align-self: flex-start; 42 + } 43 + 44 + /* Mobile: sidebar collapses to header above main content */ 45 + @media (max-width: 1400px) { 46 + .sidebar-layout { 47 + grid-template-columns: minmax(1rem, 1fr) minmax(0, var(--layout-content, 95ch)) minmax(1rem, 1fr) !important; 48 + gap: 0 !important; 49 + max-width: 100vw !important; 50 + box-sizing: border-box !important; 51 + } 52 + 53 + .sidebar-layout-sidebar { 54 + grid-column: 2; 55 + position: static; 56 + max-height: none; 57 + min-width: 0; 58 + margin-bottom: 2rem; 59 + } 60 + 61 + .sidebar-layout-main { 62 + grid-column: 2; 63 + padding: 0; 64 + min-width: 0; 65 + } 66 + 67 + .sidebar-layout-actions { 68 + display: none; 69 + } 70 + } 71 + 72 + /* Variant: notebook layout (wider gutters and content) */ 73 + .sidebar-layout-notebook { 74 + --layout-gutter: 280px; 75 + --layout-content: 90rem; 76 + --layout-gap: 2rem; 77 + --layout-padding: 2.5rem 1.25rem 2.5rem 0; 78 + } 79 + 80 + /* Variant: repository/profile layout (standard sizing) */ 81 + .sidebar-layout-repository { 82 + --layout-gutter: 240px; 83 + --layout-content: 95ch; 84 + --layout-gap: 1rem; 85 + --layout-padding: 2.25rem 1.25rem 2.25rem 0; 86 + } 87 + 88 + /* ========================================================================== 89 + BACKWARD-COMPATIBLE CLASS ALIASES 90 + These map existing component class names to the unified layout system. 91 + ========================================================================== */ 92 + 93 + /* Notebook layout (used in notebook.rs) */ 94 + .notebook-layout { 95 + display: grid; 96 + grid-template-columns: 97 + minmax(280px, 1fr) 98 + minmax(0, 90rem) 99 + minmax(280px, 1fr); 100 + gap: 2rem; 101 + max-width: calc(90rem + 560px + 4rem); 102 + margin: 0 auto; 103 + padding: 2.5rem 1.25rem 2.5rem 0; 104 + } 105 + 106 + .notebook-sidebar { 107 + grid-column: 1; 108 + position: sticky; 109 + top: 2rem; 110 + align-self: flex-start; 111 + max-height: calc(100vh - 4rem); 112 + overflow-y: auto; 113 + } 114 + 115 + .notebook-main { 116 + grid-column: 2; 117 + padding: 0 1rem; 118 + } 119 + 120 + @media (max-width: 1400px) { 121 + .notebook-layout { 122 + grid-template-columns: minmax(1rem, 1fr) minmax(0, 90rem) minmax(1rem, 1fr) !important; 123 + gap: 0 !important; 124 + max-width: 100vw !important; 125 + box-sizing: border-box !important; 126 + } 127 + 128 + .notebook-sidebar { 129 + grid-column: 2; 130 + position: static; 131 + max-height: none; 132 + min-width: 0; 133 + margin-bottom: 2rem; 134 + } 135 + 136 + .notebook-main { 137 + grid-column: 2; 138 + padding: 0; 139 + min-width: 0; 140 + } 141 + } 142 + 143 + /* Repository layout (used in identity.rs) */ 144 + .repository-layout { 145 + display: grid; 146 + grid-template-columns: 147 + minmax(240px, 1fr) 148 + minmax(0, 95ch) 149 + minmax(240px, 1fr); 150 + gap: 1rem; 151 + max-width: calc(95ch + 480px + 4rem); 152 + margin: 0 auto; 153 + padding: 2.25rem 1.25rem 2.25rem 0; 154 + } 155 + 156 + .repository-sidebar { 157 + grid-column: 1; 158 + position: sticky; 159 + top: 2rem; 160 + align-self: flex-start; 161 + overflow-y: auto; 162 + } 163 + 164 + .repository-main { 165 + grid-column: 2; 166 + padding: 0 1rem; 167 + } 168 + 169 + @media (max-width: 1400px) { 170 + .repository-layout { 171 + grid-template-columns: minmax(1rem, 1fr) minmax(0, 95ch) minmax(1rem, 1fr) !important; 172 + gap: 0 !important; 173 + max-width: 100vw !important; 174 + box-sizing: border-box !important; 175 + } 176 + 177 + .repository-sidebar { 178 + grid-column: 2; 179 + position: static; 180 + max-height: none; 181 + min-width: 0; 182 + margin-bottom: 2rem; 183 + } 184 + 185 + .repository-main { 186 + grid-column: 2; 187 + padding: 0; 188 + min-width: 0; 189 + } 190 + }
+3 -70
crates/weaver-app/assets/styling/main.css
··· 1 body { 2 background-color: var(--color-base); 3 color: var(--color-text); ··· 13 max-width: 1200px; 14 } 15 16 - .record-view-container { 17 - max-width: 1200px; 18 - margin: 2rem auto; 19 - padding: 0 1rem; 20 - } 21 - 22 - .uri-input-section { 23 - margin-bottom: 2.5rem; 24 - } 25 - 26 - .uri-input { 27 - font-family: var(--font-mono); 28 - font-size: 0.9rem; 29 - width: 100%; 30 - max-width: 100%; 31 - box-sizing: border-box; 32 - padding: 0.5rem 0.75rem; 33 - background: var(--color-surface, rgba(0, 0, 0, 0.2)); 34 - border: 1px solid var(--color-border); 35 - color: var(--color-text); 36 - outline: none; 37 - transition: border-color 0.2s; 38 - } 39 - 40 .notebook-content { 41 width: 100%; 42 max-width: 95ch; 43 } 44 - 45 - .uri-input:focus { 46 - border-color: var(--color-primary); 47 - } 48 - 49 - .uri-input::placeholder { 50 - color: var(--color-subtle); 51 - opacity: 0.5; 52 - } 53 - 54 - @font-face { 55 - font-family: "Ioskeley Mono"; 56 - font-style: normal; 57 - font-weight: normal; 58 - src: url("/assets/IoskeleyMono-Regular.woff2") format("woff2"); 59 - } 60 - @font-face { 61 - font-family: "Ioskeley Mono"; 62 - font-style: normal; 63 - font-weight: lighter; 64 - src: url("/assets/IoskeleyMono-Light.woff2") format("woff2"); 65 - } 66 - @font-face { 67 - font-family: "Ioskeley Mono"; 68 - font-style: italic; 69 - font-weight: lighter; 70 - src: url("/assets/IoskeleyMono-LightItalic.woff2") format("woff2"); 71 - } 72 - @font-face { 73 - font-family: "Ioskeley Mono"; 74 - font-style: normal; 75 - font-weight: bold; 76 - src: url("/assets/IoskeleyMono-Bold.woff2") format("woff2"); 77 - } 78 - @font-face { 79 - font-family: "Ioskeley Mono"; 80 - font-style: italic; 81 - font-weight: normal; 82 - src: url("/assets/IoskeleyMono-Italic.woff2") format("woff2"); 83 - } 84 - @font-face { 85 - font-family: "Ioskeley Mono"; 86 - font-style: italic; 87 - font-weight: bold; 88 - src: url("/assets/IoskeleyMono-BoldItalic.woff2") format("woff2"); 89 - }
··· 1 + /* App shell styles - theme-defaults.css must load first */ 2 + 3 body { 4 background-color: var(--color-base); 5 color: var(--color-text); ··· 15 max-width: 1200px; 16 } 17 18 + /* .notebook-content width constraint for rendered content */ 19 .notebook-content { 20 width: 100%; 21 max-width: 95ch; 22 }
+2 -50
crates/weaver-app/assets/styling/notebook-card.css
··· 1 /* Notebook card styling */ 2 - 3 - /* Repository layout - sidebar in left gutter on desktop, header on mobile */ 4 - 5 - .repository-layout { 6 - display: grid; 7 - grid-template-columns: minmax(240px, 1fr) minmax(0, 95ch) minmax(240px, 1fr); 8 - gap: 1rem; 9 - max-width: calc(95ch + 480px + 4rem); /* content + gutters + gaps */ 10 - margin: 0 auto; 11 - padding: 2.25rem 1.25rem 2.25rem 0; 12 - } 13 - 14 - .repository-sidebar { 15 - grid-column: 1; 16 - position: sticky; 17 - top: 2rem; 18 - align-self: flex-start; 19 - overflow-y: auto; 20 - } 21 - 22 - .repository-main { 23 - grid-column: 2; 24 - padding: 0 1rem; 25 - } 26 - 27 - /* Mobile layout - sidebar becomes header */ 28 - @media (max-width: 1400px) { 29 - .repository-layout { 30 - grid-template-columns: minmax(1rem, 1fr) minmax(0, 95ch) minmax(1rem, 1fr) !important; 31 - gap: 0 !important; 32 - max-width: 100vw !important; 33 - box-sizing: border-box !important; 34 - } 35 - 36 - .repository-sidebar { 37 - grid-column: 2; 38 - position: static; 39 - max-height: none; 40 - min-width: 0; 41 - margin-bottom: 2rem; 42 - } 43 44 - .repository-main { 45 - grid-column: 2; 46 - padding: 0; 47 - min-width: 0; 48 - } 49 - } 50 - 51 - /* Notebook list - width constrained by grid column */ 52 .notebooks-list { 53 margin-top: 0.25rem; 54 } 55 56 .notebook-card { 57 - margin-bottom: 2.5rem; /* 2 grid units */ 58 } 59 60 .notebook-card-container {
··· 1 /* Notebook card styling */ 2 + /* Layout classes (.repository-layout, etc.) are now in layouts.css */ 3 4 .notebooks-list { 5 margin-top: 0.25rem; 6 } 7 8 .notebook-card { 9 + margin-bottom: calc(1.25rem * var(--spacing-scale, 1.25)); 10 } 11 12 .notebook-card-container {
+4 -25
crates/weaver-app/assets/styling/notebook-cover.css
··· 46 display: none; 47 } 48 49 - .notebook-author { 50 - display: flex; 51 - align-items: center; 52 - gap: 0.75rem; 53 - } 54 - 55 - .notebook-author .avatar { 56 width: 48px; 57 height: 48px; 58 - flex-shrink: 0; 59 - } 60 - 61 - .notebook-author-info { 62 - display: flex; 63 - flex-direction: column; 64 - min-width: 0; 65 - } 66 - 67 - .notebook-author-name { 68 - font-weight: 600; 69 - color: var(--color-text); 70 - font-size: 1rem; 71 - } 72 - 73 - .notebook-author-handle { 74 - color: var(--color-subtle); 75 - font-size: 0.875rem; 76 } 77 78 .notebook-cover-description {
··· 46 display: none; 47 } 48 49 + /* Cover-specific author sizing (uses embed-author base styles) */ 50 + .notebook-author .embed-avatar { 51 width: 48px; 52 height: 48px; 53 + min-width: 48px; 54 + min-height: 48px; 55 } 56 57 .notebook-cover-description {
+2 -2
crates/weaver-app/assets/styling/record-view.css
··· 549 } 550 551 /* Array Section Styling */ 552 - .section-header { 553 display: flex; 554 align-items: baseline; 555 gap: 0.5rem; 556 } 557 558 - .array-item .section-header { 559 } 560 .array-item .section-content .record-field { 561 }
··· 549 } 550 551 /* Array Section Styling */ 552 + .record-section-header { 553 display: flex; 554 align-items: baseline; 555 gap: 0.5rem; 556 } 557 558 + .array-item .record-section-header { 559 } 560 .array-item .section-content .record-field { 561 }
+117 -93
crates/weaver-app/src/components/entry.rs
··· 5 use crate::blobcache::BlobCache; 6 use crate::{ 7 components::EntryActions, 8 - components::avatar::{Avatar, AvatarImage}, 9 data::use_handle, 10 }; 11 use dioxus::prelude::*; ··· 454 } 455 } 456 if let Some(author) = first_author { 457 - div { class: "entry-card-author", 458 - { 459 - use weaver_api::sh_weaver::actor::ProfileDataViewInner; 460 461 - match &author.record.inner { 462 - ProfileDataViewInner::ProfileView(profile) => { 463 - let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 464 - let handle = profile.handle.clone(); 465 - rsx! { 466 if let Some(ref avatar_url) = profile.avatar { 467 - Avatar { 468 - AvatarImage { src: avatar_url.as_ref() } 469 - } 470 } 471 - span { class: "author-name", "{display_name}" } 472 - span { class: "meta-label", "@{handle}" } 473 } 474 } 475 - ProfileDataViewInner::ProfileViewDetailed(profile) => { 476 - let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 477 - let handle = profile.handle.clone(); 478 - rsx! { 479 if let Some(ref avatar_url) = profile.avatar { 480 - Avatar { 481 - AvatarImage { src: avatar_url.as_ref() } 482 - } 483 } 484 - span { class: "author-name", "{display_name}" } 485 - span { class: "meta-label", "@{handle}" } 486 } 487 } 488 - ProfileDataViewInner::TangledProfileView(profile) => { 489 - rsx! { 490 - span { class: "author-name", "@{profile.handle.as_ref()}" } 491 } 492 } 493 - _ => { 494 - rsx! { 495 - span { class: "author-name", "Unknown" } 496 } 497 } 498 } ··· 518 } 519 520 /// Card for entries in a feed (e.g., home page) 521 - /// Takes EntryView directly (not BookEntryView) and always shows author info 522 #[component] 523 pub fn FeedEntryCard( 524 entry_view: EntryView<'static>, 525 entry: entry::Entry<'static>, 526 #[props(default = false)] show_actions: bool, 527 #[props(default = false)] is_pinned: bool, 528 #[props(default)] on_pinned_changed: Option<EventHandler<bool>>, 529 ) -> Element { 530 use crate::Route; ··· 555 // Format date from record's created_at 556 let formatted_date = entry.created_at.as_ref().format("%B %d, %Y").to_string(); 557 558 - // Get first author 559 - let first_author = entry_view.authors.first(); 560 561 // Check ownership for actions 562 let auth_state = use_context::<Signal<AuthState>>(); ··· 578 579 rsx! { 580 div { class: "entry-card feed-entry-card", 581 - // Title 582 - Link { 583 - to: Route::StandaloneEntry { 584 - ident: ident.clone(), 585 - rkey: rkey.clone().into() 586 - }, 587 - class: "entry-card-title-link", 588 - h3 { class: "entry-card-title", "{title}" } 589 } 590 591 - // Byline: author + date 592 - div { class: "entry-card-byline", 593 - if let Some(author) = first_author { 594 { 595 match &author.record.inner { 596 ProfileDataViewInner::ProfileView(profile) => { ··· 599 rsx! { 600 Link { 601 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 602 - class: "entry-card-author", 603 if let Some(ref avatar_url) = profile.avatar { 604 - Avatar { 605 - AvatarImage { src: avatar_url.as_ref() } 606 - } 607 } 608 - span { class: "author-name", "{display_name}" } 609 - span { class: "meta-label", "@{handle}" } 610 } 611 } 612 } ··· 616 rsx! { 617 Link { 618 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 619 - class: "entry-card-author", 620 if let Some(ref avatar_url) = profile.avatar { 621 - Avatar { 622 - AvatarImage { src: avatar_url.as_ref() } 623 - } 624 } 625 - span { class: "author-name", "{display_name}" } 626 - span { class: "meta-label", "@{handle}" } 627 } 628 } 629 } ··· 632 rsx! { 633 Link { 634 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 635 - class: "entry-card-author", 636 - span { class: "author-name", "@{handle}" } 637 } 638 } 639 } 640 _ => { 641 rsx! { 642 - div { class: "entry-card-author", 643 - span { class: "author-name", "Unknown" } 644 } 645 } 646 } 647 } 648 } 649 - } 650 - div { class: "entry-card-date", 651 - time { datetime: "{entry.created_at.as_str()}", "{formatted_date}" } 652 - } 653 - if show_actions && is_owner { 654 - crate::components::EntryActions { 655 - entry_uri: entry_view.uri.clone().into_static(), 656 - entry_cid: entry_view.cid.clone().into_static(), 657 - entry_title: title.to_string(), 658 - in_notebook: false, 659 - is_pinned, 660 - on_pinned_changed 661 } 662 } 663 } ··· 738 rsx! { 739 Link { 740 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 741 - div { class: "entry-authors", 742 - if let Some(ref avatar_url) = profile.avatar { 743 - Avatar { 744 - AvatarImage { 745 - src: avatar_url.as_ref() 746 - } 747 - } 748 - } 749 - span { class: "author-name", "{display_name}" } 750 - span { class: "meta-label", "@{handle}" } 751 } 752 } 753 } ··· 758 rsx! { 759 Link { 760 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 761 - div { class: "entry-authors", 762 - if let Some(ref avatar_url) = profile.avatar { 763 - Avatar { 764 - AvatarImage { 765 - src: avatar_url.as_ref() 766 - } 767 - } 768 - } 769 - span { class: "author-name", "{display_name}" } 770 - span { class: "meta-label", "@{handle}" } 771 } 772 } 773 } 774 } 775 ProfileDataViewInner::TangledProfileView(profile) => { 776 rsx! { 777 - span { class: "author-name", "@{profile.handle.as_ref()}" } 778 } 779 } 780 _ => { 781 rsx! { 782 - span { class: "author-name", "Unknown" } 783 } 784 } 785 }
··· 5 use crate::blobcache::BlobCache; 6 use crate::{ 7 components::EntryActions, 8 data::use_handle, 9 }; 10 use dioxus::prelude::*; ··· 453 } 454 } 455 if let Some(author) = first_author { 456 + { 457 + use weaver_api::sh_weaver::actor::ProfileDataViewInner; 458 459 + match &author.record.inner { 460 + ProfileDataViewInner::ProfileView(profile) => { 461 + let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 462 + let handle = profile.handle.clone(); 463 + rsx! { 464 + span { class: "embed-author entry-card-author", 465 if let Some(ref avatar_url) = profile.avatar { 466 + img { class: "embed-avatar", src: avatar_url.as_ref(), alt: "" } 467 + } 468 + span { class: "embed-author-info", 469 + span { class: "embed-author-name", "{display_name}" } 470 + span { class: "embed-author-handle", "@{handle}" } 471 } 472 } 473 } 474 + } 475 + ProfileDataViewInner::ProfileViewDetailed(profile) => { 476 + let display_name = profile.display_name.as_ref().map(|n| n.as_ref()).unwrap_or("Unknown"); 477 + let handle = profile.handle.clone(); 478 + rsx! { 479 + span { class: "embed-author entry-card-author", 480 if let Some(ref avatar_url) = profile.avatar { 481 + img { class: "embed-avatar", src: avatar_url.as_ref(), alt: "" } 482 } 483 + span { class: "embed-author-info", 484 + span { class: "embed-author-name", "{display_name}" } 485 + span { class: "embed-author-handle", "@{handle}" } 486 + } 487 } 488 } 489 + } 490 + ProfileDataViewInner::TangledProfileView(profile) => { 491 + rsx! { 492 + span { class: "embed-author entry-card-author", 493 + span { class: "embed-author-info", 494 + span { class: "embed-author-handle", "@{profile.handle.as_ref()}" } 495 + } 496 } 497 } 498 + } 499 + _ => { 500 + rsx! { 501 + span { class: "embed-author entry-card-author", 502 + span { class: "embed-author-info", 503 + span { class: "embed-author-name", "Unknown" } 504 + } 505 } 506 } 507 } ··· 527 } 528 529 /// Card for entries in a feed (e.g., home page) 530 + /// Takes EntryView directly (not BookEntryView) 531 #[component] 532 pub fn FeedEntryCard( 533 entry_view: EntryView<'static>, 534 entry: entry::Entry<'static>, 535 #[props(default = false)] show_actions: bool, 536 #[props(default = false)] is_pinned: bool, 537 + #[props(default = true)] show_author: bool, 538 #[props(default)] on_pinned_changed: Option<EventHandler<bool>>, 539 ) -> Element { 540 use crate::Route; ··· 565 // Format date from record's created_at 566 let formatted_date = entry.created_at.as_ref().format("%B %d, %Y").to_string(); 567 568 + // Get first author if we're showing it 569 + let first_author = if show_author { entry_view.authors.first() } else { None }; 570 571 // Check ownership for actions 572 let auth_state = use_context::<Signal<AuthState>>(); ··· 588 589 rsx! { 590 div { class: "entry-card feed-entry-card", 591 + // Header: title (and date if no author) 592 + div { class: "entry-card-header", 593 + Link { 594 + to: Route::StandaloneEntry { 595 + ident: ident.clone(), 596 + rkey: rkey.clone().into() 597 + }, 598 + class: "entry-card-title-link", 599 + h3 { class: "entry-card-title", "{title}" } 600 + } 601 + // Date inline with title when no author shown 602 + if first_author.is_none() { 603 + div { class: "entry-card-date", 604 + time { datetime: "{entry.created_at.as_str()}", "{formatted_date}" } 605 + } 606 + } 607 + if show_actions && is_owner { 608 + crate::components::EntryActions { 609 + entry_uri: entry_view.uri.clone().into_static(), 610 + entry_cid: entry_view.cid.clone().into_static(), 611 + entry_title: title.to_string(), 612 + in_notebook: false, 613 + is_pinned, 614 + on_pinned_changed 615 + } 616 + } 617 } 618 619 + // Byline: author + date (only when author shown) 620 + if let Some(author) = first_author { 621 + div { class: "entry-card-byline", 622 { 623 match &author.record.inner { 624 ProfileDataViewInner::ProfileView(profile) => { ··· 627 rsx! { 628 Link { 629 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 630 + class: "embed-author entry-card-author", 631 if let Some(ref avatar_url) = profile.avatar { 632 + img { class: "embed-avatar", src: avatar_url.as_ref(), alt: "" } 633 } 634 + span { class: "embed-author-info", 635 + span { class: "embed-author-name", "{display_name}" } 636 + span { class: "embed-author-handle", "@{handle}" } 637 + } 638 } 639 } 640 } ··· 644 rsx! { 645 Link { 646 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 647 + class: "embed-author entry-card-author", 648 if let Some(ref avatar_url) = profile.avatar { 649 + img { class: "embed-avatar", src: avatar_url.as_ref(), alt: "" } 650 } 651 + span { class: "embed-author-info", 652 + span { class: "embed-author-name", "{display_name}" } 653 + span { class: "embed-author-handle", "@{handle}" } 654 + } 655 } 656 } 657 } ··· 660 rsx! { 661 Link { 662 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 663 + class: "embed-author entry-card-author", 664 + span { class: "embed-author-info", 665 + span { class: "embed-author-handle", "@{handle}" } 666 + } 667 } 668 } 669 } 670 _ => { 671 rsx! { 672 + span { class: "embed-author entry-card-author", 673 + span { class: "embed-author-info", 674 + span { class: "embed-author-name", "Unknown" } 675 + } 676 } 677 } 678 } 679 } 680 } 681 + div { class: "entry-card-date", 682 + time { datetime: "{entry.created_at.as_str()}", "{formatted_date}" } 683 } 684 } 685 } ··· 760 rsx! { 761 Link { 762 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 763 + class: "embed-author", 764 + if let Some(ref avatar_url) = profile.avatar { 765 + img { class: "embed-avatar", src: avatar_url.as_ref(), alt: "" } 766 + } 767 + span { class: "embed-author-info", 768 + span { class: "embed-author-name", "{display_name}" } 769 + span { class: "embed-author-handle", "@{handle}" } 770 } 771 } 772 } ··· 777 rsx! { 778 Link { 779 to: Route::RepositoryIndex { ident: AtIdentifier::Handle(handle.clone()) }, 780 + class: "embed-author", 781 + if let Some(ref avatar_url) = profile.avatar { 782 + img { class: "embed-avatar", src: avatar_url.as_ref(), alt: "" } 783 + } 784 + span { class: "embed-author-info", 785 + span { class: "embed-author-name", "{display_name}" } 786 + span { class: "embed-author-handle", "@{handle}" } 787 } 788 } 789 } 790 } 791 ProfileDataViewInner::TangledProfileView(profile) => { 792 rsx! { 793 + span { class: "embed-author", 794 + span { class: "embed-author-info", 795 + span { class: "embed-author-handle", "@{profile.handle.as_ref()}" } 796 + } 797 + } 798 } 799 } 800 _ => { 801 rsx! { 802 + span { class: "embed-author", 803 + span { class: "embed-author-info", 804 + span { class: "embed-author-name", "Unknown" } 805 + } 806 + } 807 } 808 } 809 }
+10 -9
crates/weaver-app/src/components/identity.rs
··· 68 } 69 } 70 71 - const NOTEBOOK_CARD_CSS: Asset = asset!("/assets/styling/notebook-card.css"); 72 const ENTRY_CSS: Asset = asset!("/assets/styling/entry.css"); 73 - const ENTRY_CARD_CSS: Asset = asset!("/assets/styling/entry-card.css"); 74 75 #[component] 76 pub fn Repository(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 77 rsx! { 78 DefaultNotebookCss { } 79 - document::Link { rel: "stylesheet", href: NOTEBOOK_CARD_CSS } 80 document::Link { rel: "stylesheet", href: ENTRY_CSS } 81 - document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 82 div { 83 Outlet::<Route> {} 84 } ··· 316 }; 317 318 rsx! { 319 - document::Stylesheet { href: NOTEBOOK_CARD_CSS } 320 {og_meta} 321 322 div { class: "repository-layout", ··· 363 entry_view: entry_view.clone(), 364 entry: entry.clone(), 365 show_actions: true, 366 - is_pinned: true 367 } 368 } 369 } ··· 409 entry_view: entry_view.clone(), 410 entry: entry.clone(), 411 show_actions: true, 412 - is_pinned: false 413 } 414 } 415 } ··· 434 notebook: NotebookView<'static>, 435 entry_refs: Vec<StrongRef<'static>>, 436 #[props(default = false)] is_pinned: bool, 437 #[props(default)] on_pinned_changed: Option<EventHandler<bool>>, 438 #[props(default)] on_deleted: Option<EventHandler<()>>, 439 ) -> Element { ··· 469 // Format date 470 let formatted_date = notebook.indexed_at.as_ref().format("%B %d, %Y").to_string(); 471 472 - // Show authors only if multiple 473 - let show_authors = notebook.authors.len() > 1; 474 475 let ident = notebook.uri.authority().clone().into_static(); 476 let book_title: SmolStr = notebook_path.clone().into();
··· 68 } 69 } 70 71 + // Card styles (entry-card, notebook-card) loaded at navbar level 72 const ENTRY_CSS: Asset = asset!("/assets/styling/entry.css"); 73 + const LAYOUTS_CSS: Asset = asset!("/assets/styling/layouts.css"); 74 75 #[component] 76 pub fn Repository(ident: ReadSignal<AtIdentifier<'static>>) -> Element { 77 rsx! { 78 DefaultNotebookCss { } 79 + document::Link { rel: "stylesheet", href: LAYOUTS_CSS } 80 document::Link { rel: "stylesheet", href: ENTRY_CSS } 81 div { 82 Outlet::<Route> {} 83 } ··· 315 }; 316 317 rsx! { 318 {og_meta} 319 320 div { class: "repository-layout", ··· 361 entry_view: entry_view.clone(), 362 entry: entry.clone(), 363 show_actions: true, 364 + is_pinned: true, 365 + show_author: false 366 } 367 } 368 } ··· 408 entry_view: entry_view.clone(), 409 entry: entry.clone(), 410 show_actions: true, 411 + is_pinned: false, 412 + show_author: false 413 } 414 } 415 } ··· 434 notebook: NotebookView<'static>, 435 entry_refs: Vec<StrongRef<'static>>, 436 #[props(default = false)] is_pinned: bool, 437 + #[props(default)] show_author: Option<bool>, 438 #[props(default)] on_pinned_changed: Option<EventHandler<bool>>, 439 #[props(default)] on_deleted: Option<EventHandler<()>>, 440 ) -> Element { ··· 470 // Format date 471 let formatted_date = notebook.indexed_at.as_ref().format("%B %d, %Y").to_string(); 472 473 + // Show authors: explicit prop overrides, otherwise show only if multiple 474 + let show_authors = show_author.unwrap_or(notebook.authors.len() > 1); 475 476 let ident = notebook.uri.authority().clone().into_static(); 477 let book_title: SmolStr = notebook_path.clone().into();
+18 -19
crates/weaver-app/src/components/notebook_cover.rs
··· 1 #![allow(non_snake_case)] 2 3 use crate::Route; 4 - use crate::components::avatar::{Avatar, AvatarImage}; 5 use crate::components::button::{Button, ButtonVariant}; 6 use dioxus::prelude::*; 7 use jacquard::smol_str::SmolStr; ··· 129 .unwrap_or("Unknown"); 130 131 rsx! { 132 - div { class: "notebook-author", 133 if let Some(ref avatar) = p.avatar { 134 - Avatar { 135 - AvatarImage { src: avatar.as_ref() } 136 - } 137 } 138 - div { class: "notebook-author-info", 139 - div { class: "notebook-author-name", "{display_name}" } 140 - div { class: "notebook-author-handle", "@{p.handle}" } 141 } 142 } 143 } ··· 150 .unwrap_or("Unknown"); 151 152 rsx! { 153 - div { class: "notebook-author", 154 if let Some(ref avatar) = p.avatar { 155 - Avatar { 156 - AvatarImage { src: avatar.as_ref() } 157 - } 158 } 159 - div { class: "notebook-author-info", 160 - div { class: "notebook-author-name", "{display_name}" } 161 - div { class: "notebook-author-handle", "@{p.handle}" } 162 } 163 } 164 } 165 } 166 ProfileDataViewInner::TangledProfileView(p) => { 167 rsx! { 168 - div { class: "notebook-author", 169 - div { class: "notebook-author-name", "@{p.handle.as_ref()}" } 170 } 171 } 172 } 173 _ => rsx! { 174 - div { class: "notebook-author", 175 - "Unknown author" 176 } 177 }, 178 }
··· 1 #![allow(non_snake_case)] 2 3 use crate::Route; 4 use crate::components::button::{Button, ButtonVariant}; 5 use dioxus::prelude::*; 6 use jacquard::smol_str::SmolStr; ··· 128 .unwrap_or("Unknown"); 129 130 rsx! { 131 + span { class: "embed-author notebook-author", 132 if let Some(ref avatar) = p.avatar { 133 + img { class: "embed-avatar", src: avatar.as_ref(), alt: "" } 134 } 135 + span { class: "embed-author-info", 136 + span { class: "embed-author-name", "{display_name}" } 137 + span { class: "embed-author-handle", "@{p.handle}" } 138 } 139 } 140 } ··· 147 .unwrap_or("Unknown"); 148 149 rsx! { 150 + span { class: "embed-author notebook-author", 151 if let Some(ref avatar) = p.avatar { 152 + img { class: "embed-avatar", src: avatar.as_ref(), alt: "" } 153 } 154 + span { class: "embed-author-info", 155 + span { class: "embed-author-name", "{display_name}" } 156 + span { class: "embed-author-handle", "@{p.handle}" } 157 } 158 } 159 } 160 } 161 ProfileDataViewInner::TangledProfileView(p) => { 162 rsx! { 163 + span { class: "embed-author notebook-author", 164 + span { class: "embed-author-info", 165 + span { class: "embed-author-handle", "@{p.handle.as_ref()}" } 166 + } 167 } 168 } 169 } 170 _ => rsx! { 171 + span { class: "embed-author notebook-author", 172 + span { class: "embed-author-info", 173 + span { class: "embed-author-name", "Unknown" } 174 + } 175 } 176 }, 177 }
+2 -2
crates/weaver-app/src/components/record_editor.rs
··· 935 default_open: true, 936 index: 0, 937 AccordionTrigger { 938 - div { class: "section-header", 939 div { class: "section-label", 940 { 941 let parts: Vec<&str> = path.split('.').collect(); ··· 1038 default_open: true, 1039 index: 0, 1040 AccordionTrigger { 1041 - div { class: "section-header", 1042 div { class: "section-label", 1043 { 1044 let parts: Vec<&str> = path.split('.').collect();
··· 935 default_open: true, 936 index: 0, 937 AccordionTrigger { 938 + div { class: "record-section-header", 939 div { class: "section-label", 940 { 941 let parts: Vec<&str> = path.split('.').collect(); ··· 1038 default_open: true, 1039 index: 0, 1040 AccordionTrigger { 1041 + div { class: "record-section-header", 1042 div { class: "section-label", 1043 { 1044 let parts: Vec<&str> = path.split('.').collect();
+5 -3
crates/weaver-app/src/main.rs
··· 281 282 rsx! { 283 document::Link { rel: "icon", href: FAVICON } 284 - document::Link { rel: "stylesheet", href: MAIN_CSS } 285 - document::Link { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=IBM+Plex+Serif:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" } 286 document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" } 287 document::Link { rel: "preconnect", href: "https://fonts.gstatic.com" } 288 - 289 document::Link { rel: "stylesheet", href: THEME_DEFAULTS_CSS } 290 components::toast::ToastProvider { 291 Router::<Route> {} 292 }
··· 281 282 rsx! { 283 document::Link { rel: "icon", href: FAVICON } 284 + // Preconnect for external fonts (before loading them) 285 document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" } 286 document::Link { rel: "preconnect", href: "https://fonts.gstatic.com" } 287 + // Theme defaults first: CSS variables, font-faces, reset 288 document::Link { rel: "stylesheet", href: THEME_DEFAULTS_CSS } 289 + document::Link { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=IBM+Plex+Serif:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" } 290 + // App shell styles (depends on theme variables) 291 + document::Link { rel: "stylesheet", href: MAIN_CSS } 292 components::toast::ToastProvider { 293 Router::<Route> {} 294 }
+3 -5
crates/weaver-app/src/views/home.rs
··· 65 } 66 } 67 68 - const NOTEBOOK_CARD_CSS: Asset = asset!("/assets/styling/notebook-card.css"); 69 const ENTRY_CSS: Asset = asset!("/assets/styling/entry.css"); 70 - const ENTRY_CARD_CSS: Asset = asset!("/assets/styling/entry-card.css"); 71 const HOME_CSS: Asset = asset!("/assets/styling/home.css"); 72 73 /// The Home page component that will be rendered when the current route is `[Route::Home]` ··· 86 SiteOgMeta {} 87 88 document::Link { rel: "stylesheet", href: HOME_CSS } 89 - document::Link { rel: "stylesheet", href: NOTEBOOK_CARD_CSS } 90 document::Link { rel: "stylesheet", href: ENTRY_CSS } 91 - document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 92 DefaultNotebookCss { } 93 div { 94 class: "home-container", ··· 156 Some((view, entries)) => rsx! { 157 NotebookCard { 158 notebook: view.clone(), 159 - entry_refs: entries.clone() 160 } 161 }, 162 None => rsx! {
··· 65 } 66 } 67 68 + // Card styles (entry-card, notebook-card) loaded at navbar level 69 const ENTRY_CSS: Asset = asset!("/assets/styling/entry.css"); 70 const HOME_CSS: Asset = asset!("/assets/styling/home.css"); 71 72 /// The Home page component that will be rendered when the current route is `[Route::Home]` ··· 85 SiteOgMeta {} 86 87 document::Link { rel: "stylesheet", href: HOME_CSS } 88 document::Link { rel: "stylesheet", href: ENTRY_CSS } 89 DefaultNotebookCss { } 90 div { 91 class: "home-container", ··· 153 Some((view, entries)) => rsx! { 154 NotebookCard { 155 notebook: view.clone(), 156 + entry_refs: entries.clone(), 157 + show_author: Some(true) 158 } 159 }, 160 None => rsx! {
+6
crates/weaver-app/src/views/navbar.rs
··· 10 use jacquard::types::string::Did; 11 12 const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css"); 13 14 /// The Navbar component that will be rendered on all pages of our app since every page is under the layout. 15 /// ··· 61 rsx! { 62 document::Link { rel: "stylesheet", href: NAVBAR_CSS } 63 document::Link { rel: "stylesheet", href: asset!("/assets/styling/button.css") } 64 div { 65 id: "navbar", 66 nav { class: "breadcrumbs",
··· 10 use jacquard::types::string::Did; 11 12 const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css"); 13 + const CARDS_BASE_CSS: Asset = asset!("/assets/styling/cards-base.css"); 14 + const ENTRY_CARD_CSS: Asset = asset!("/assets/styling/entry-card.css"); 15 + const NOTEBOOK_CARD_CSS: Asset = asset!("/assets/styling/notebook-card.css"); 16 17 /// The Navbar component that will be rendered on all pages of our app since every page is under the layout. 18 /// ··· 64 rsx! { 65 document::Link { rel: "stylesheet", href: NAVBAR_CSS } 66 document::Link { rel: "stylesheet", href: asset!("/assets/styling/button.css") } 67 + document::Link { rel: "stylesheet", href: CARDS_BASE_CSS } 68 + document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 69 + document::Link { rel: "stylesheet", href: NOTEBOOK_CARD_CSS } 70 div { 71 id: "navbar", 72 nav { class: "breadcrumbs",
+3 -2
crates/weaver-app/src/views/notebook.rs
··· 44 } 45 } 46 47 - const ENTRY_CARD_CSS: Asset = asset!("/assets/styling/entry-card.css"); 48 49 /// The Blog page component that will be rendered when the current route is `[Route::Blog]` 50 /// ··· 96 }; 97 98 rsx! { 99 - document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS } 100 101 match (&*notebook_data.read(), &*entries_resource.read()) { 102 (Some(data), Some(entries)) => {
··· 44 } 45 } 46 47 + // Card styles loaded at navbar level 48 + const LAYOUTS_CSS: Asset = asset!("/assets/styling/layouts.css"); 49 50 /// The Blog page component that will be rendered when the current route is `[Route::Blog]` 51 /// ··· 97 }; 98 99 rsx! { 100 + document::Link { rel: "stylesheet", href: LAYOUTS_CSS } 101 102 match (&*notebook_data.read(), &*entries_resource.read()) { 103 (Some(data), Some(entries)) => {
+37 -3
crates/weaver-renderer/src/css.rs
··· 1 use crate::theme::{ResolvedTheme, ThemeDarkCodeTheme, ThemeLightCodeTheme}; 2 use miette::IntoDiagnostic; 3 use std::io::Cursor; 4 use syntect::highlighting::ThemeSet; 5 use syntect::html::{ClassStyle, css_for_theme_with_class_style}; 6 use weaver_api::com_atproto::sync::get_blob::GetBlob; 7 use weaver_common::jacquard::client::BasicClient; 8 use weaver_common::jacquard::prelude::*; 9 use weaver_common::jacquard::xrpc::XrpcExt; ··· 17 let light = &theme.light_scheme; 18 let fonts = &theme.fonts; 19 let spacing = &theme.spacing; 20 21 format!( 22 r#"/* CSS Reset */ ··· 930 light.link, 931 light.highlight, 932 // Fonts and spacing 933 - fonts.body, 934 - fonts.heading, 935 - fonts.monospace, 936 spacing.base_size, 937 spacing.line_height, 938 spacing.scale,
··· 1 use crate::theme::{ResolvedTheme, ThemeDarkCodeTheme, ThemeLightCodeTheme}; 2 use miette::IntoDiagnostic; 3 + use smol_str::format_smolstr; 4 use std::io::Cursor; 5 use syntect::highlighting::ThemeSet; 6 use syntect::html::{ClassStyle, css_for_theme_with_class_style}; 7 use weaver_api::com_atproto::sync::get_blob::GetBlob; 8 + use weaver_api::sh_weaver::notebook::theme::FontValue; 9 use weaver_common::jacquard::client::BasicClient; 10 use weaver_common::jacquard::prelude::*; 11 use weaver_common::jacquard::xrpc::XrpcExt; ··· 19 let light = &theme.light_scheme; 20 let fonts = &theme.fonts; 21 let spacing = &theme.spacing; 22 + 23 + // interim until handle fonts from blobs 24 + let body = fonts 25 + .body 26 + .iter() 27 + .filter_map(|f| match &f.value { 28 + FontValue::FontName(cow_str) => Some(format_smolstr!("'{cow_str}'")), 29 + FontValue::FontFile(_font_file) => None, 30 + FontValue::Unknown(_data) => None, 31 + }) 32 + .collect::<Vec<_>>() 33 + .join(","); 34 + let monospace = fonts 35 + .monospace 36 + .iter() 37 + .filter_map(|f| match &f.value { 38 + FontValue::FontName(cow_str) => Some(format_smolstr!("'{cow_str}'")), 39 + FontValue::FontFile(_font_file) => None, 40 + FontValue::Unknown(_data) => None, 41 + }) 42 + .collect::<Vec<_>>() 43 + .join(","); 44 + let heading = fonts 45 + .heading 46 + .iter() 47 + .filter_map(|f| match &f.value { 48 + FontValue::FontName(cow_str) => Some(format_smolstr!("'{cow_str}'")), 49 + FontValue::FontFile(_font_file) => None, 50 + FontValue::Unknown(_data) => None, 51 + }) 52 + .collect::<Vec<_>>() 53 + .join(","); 54 55 format!( 56 r#"/* CSS Reset */ ··· 964 light.link, 965 light.highlight, 966 // Fonts and spacing 967 + body, 968 + heading, 969 + monospace, 970 spacing.base_size, 971 spacing.line_height, 972 spacing.scale,
+60 -10
crates/weaver-renderer/src/theme.rs
··· 1 use miette::IntoDiagnostic; 2 pub use weaver_api::sh_weaver::notebook::colour_scheme::{ColourScheme, ColourSchemeColours}; 3 pub use weaver_api::sh_weaver::notebook::theme::{ 4 Theme, ThemeDarkCodeTheme, ThemeFonts, ThemeLightCodeTheme, ThemeSpacing, 5 }; ··· 54 pub fn default_fonts() -> ThemeFonts<'static> { 55 ThemeFonts { 56 // Serif for body text, sans for headings/UI 57 - body: CowStr::new_static( 58 - "'Adobe Caslon Pro', 'Latin Modern Roman', 'CM Serif', Georgia, serif", 59 - ), 60 - heading: CowStr::new_static( 61 - "'IBM Plex Sans', 'CM Sans','Junction', 'Proza Libre', system-ui, sans-serif", 62 - ), 63 - monospace: CowStr::new_static( 64 - "'Ioskeley Mono', 'IBM Plex Mono', 'Berkeley Mono', Consolas, monospace", 65 - ), 66 - ..Default::default() 67 } 68 } 69
··· 1 use miette::IntoDiagnostic; 2 pub use weaver_api::sh_weaver::notebook::colour_scheme::{ColourScheme, ColourSchemeColours}; 3 + use weaver_api::sh_weaver::notebook::theme::Font; 4 + use weaver_api::sh_weaver::notebook::theme::FontName; 5 + use weaver_api::sh_weaver::notebook::theme::FontValue; 6 pub use weaver_api::sh_weaver::notebook::theme::{ 7 Theme, ThemeDarkCodeTheme, ThemeFonts, ThemeLightCodeTheme, ThemeSpacing, 8 }; ··· 57 pub fn default_fonts() -> ThemeFonts<'static> { 58 ThemeFonts { 59 // Serif for body text, sans for headings/UI 60 + body: vec![ 61 + Font::new() 62 + .value(FontValue::FontName( 63 + CowStr::new_static("Adobe Caslon Pro").into(), 64 + )) 65 + .build(), 66 + Font::new() 67 + .value(FontValue::FontName( 68 + CowStr::new_static("Latin Modern Roman").into(), 69 + )) 70 + .build(), 71 + Font::new() 72 + .value(FontValue::FontName( 73 + CowStr::new_static("Times New Roman").into(), 74 + )) 75 + .build(), 76 + Font::new() 77 + .value(FontValue::FontName(CowStr::new_static("serif").into())) 78 + .build(), 79 + ], 80 + heading: vec![ 81 + Font::new() 82 + .value(FontValue::FontName( 83 + CowStr::new_static("IBM Plex Sans").into(), 84 + )) 85 + .build(), 86 + Font::new() 87 + .value(FontValue::FontName(CowStr::new_static("system-ui").into())) 88 + .build(), 89 + Font::new() 90 + .value(FontValue::FontName(CowStr::new_static("sans-serif").into())) 91 + .build(), 92 + ], 93 + monospace: vec![ 94 + Font::new() 95 + .value(FontValue::FontName( 96 + CowStr::new_static("Ioskeley Mono").into(), 97 + )) 98 + .build(), 99 + Font::new() 100 + .value(FontValue::FontName( 101 + CowStr::new_static("IBM Plex Mono").into(), 102 + )) 103 + .build(), 104 + Font::new() 105 + .value(FontValue::FontName( 106 + CowStr::new_static("Berkeley Mono").into(), 107 + )) 108 + .build(), 109 + Font::new() 110 + .value(FontValue::FontName(CowStr::new_static("Consolas").into())) 111 + .build(), 112 + Font::new() 113 + .value(FontValue::FontName(CowStr::new_static("monospace").into())) 114 + .build(), 115 + ], 116 + extra_data: None, 117 } 118 } 119
+12 -4
lexicons/notebook/theme.json
··· 28 "fonts": { 29 "type": "object", 30 "required": ["body", "heading", "monospace"], 31 - "description": "Fonts to be used in the notebook. Can specify a name or list of names (will load if available) or a file or list of files for each. Empty lists will use site defaults." 32 "properties": { 33 "body": { 34 "type": "array", ··· 86 }, 87 "codeThemeFile": { 88 "type": "object", 89 - "required": ["name", "content"], 90 "description": "Custom syntax highlighting theme file (sublime text/textmate theme format)", 91 "properties": { 92 "name": { 93 "type": "string" 94 }, 95 "content": { 96 "type": "blob", ··· 115 }, 116 "fontFile": { 117 "type": "object", 118 - "required": ["name", "content"], 119 - "description": "Custom woff(2) or ttf font file" 120 "properties": { 121 "name": { 122 "type": "string" 123 }, 124 "content": { 125 "type": "blob",
··· 28 "fonts": { 29 "type": "object", 30 "required": ["body", "heading", "monospace"], 31 + "description": "Fonts to be used in the notebook. Can specify a name or list of names (will load if available) or a file or list of files for each. Empty lists will use site defaults.", 32 "properties": { 33 "body": { 34 "type": "array", ··· 86 }, 87 "codeThemeFile": { 88 "type": "object", 89 + "required": ["name", "did", "content"], 90 "description": "Custom syntax highlighting theme file (sublime text/textmate theme format)", 91 "properties": { 92 "name": { 93 "type": "string" 94 + }, 95 + "did": { 96 + "type": "string", 97 + "format": "did" 98 }, 99 "content": { 100 "type": "blob", ··· 119 }, 120 "fontFile": { 121 "type": "object", 122 + "required": ["name", "did", "content"], 123 + "description": "Custom woff(2) or ttf font file", 124 "properties": { 125 "name": { 126 "type": "string" 127 + }, 128 + "did": { 129 + "type": "string", 130 + "format": "did" 131 }, 132 "content": { 133 "type": "blob",