css fetch/cache

Orual 27110a41 9345aba3

+1977 -321
+3
Cargo.lock
··· 8754 8754 "tokio-util", 8755 8755 "unicode-normalization", 8756 8756 "url", 8757 + "weaver-api", 8757 8758 "weaver-common", 8758 8759 "yaml-rust2", 8759 8760 ] ··· 8762 8763 name = "weaver-server" 8763 8764 version = "0.1.0" 8764 8765 dependencies = [ 8766 + "axum", 8765 8767 "dashmap 6.1.0", 8766 8768 "dioxus", 8767 8769 "dioxus-primitives", ··· 8772 8774 "moka", 8773 8775 "weaver-api", 8774 8776 "weaver-common", 8777 + "weaver-renderer", 8775 8778 ] 8776 8779 8777 8780 [[package]]
+24 -8
crates/weaver-api/lexicons/sh_weaver_embed_video.json
··· 50 50 "maxLength": 10000, 51 51 "maxGraphemes": 1000 52 52 }, 53 - "aspectRatio": { 54 - "type": "ref", 55 - "ref": "app.bsky.embed.defs#aspectRatio" 56 - }, 57 53 "captions": { 58 54 "type": "array", 59 55 "items": { ··· 61 57 "ref": "#caption" 62 58 }, 63 59 "maxLength": 20 60 + }, 61 + "dimensions": { 62 + "type": "union", 63 + "refs": [ 64 + "app.bsky.embed.defs#aspectRatio", 65 + "sh.weaver.embed.defs#percentSize", 66 + "sh.weaver.embed.defs#pixelSize" 67 + ] 68 + }, 69 + "name": { 70 + "type": "string", 71 + "maxLength": 128 64 72 }, 65 73 "video": { 66 74 "type": "blob", ··· 84 92 "maxLength": 10000, 85 93 "maxGraphemes": 1000 86 94 }, 87 - "aspectRatio": { 88 - "type": "ref", 89 - "ref": "app.bsky.embed.defs#aspectRatio" 90 - }, 91 95 "cid": { 92 96 "type": "string", 93 97 "format": "cid" 98 + }, 99 + "dimensions": { 100 + "type": "union", 101 + "refs": [ 102 + "app.bsky.embed.defs#aspectRatio", 103 + "sh.weaver.embed.defs#percentSize", 104 + "sh.weaver.embed.defs#pixelSize" 105 + ] 106 + }, 107 + "name": { 108 + "type": "string", 109 + "maxLength": 128 94 110 }, 95 111 "playlist": { 96 112 "type": "string",
+4
crates/weaver-api/lexicons/sh_weaver_notebook_book.json
··· 36 36 "type": "ref", 37 37 "ref": "sh.weaver.notebook.defs#tags" 38 38 }, 39 + "theme": { 40 + "type": "ref", 41 + "ref": "com.atproto.repo.strongRef" 42 + }, 39 43 "title": { 40 44 "type": "ref", 41 45 "ref": "sh.weaver.notebook.defs#title"
+125
crates/weaver-api/lexicons/sh_weaver_notebook_theme.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.theme", 4 + "defs": { 5 + "codeThemeFile": { 6 + "type": "object", 7 + "required": [ 8 + "name", 9 + "did", 10 + "content" 11 + ], 12 + "properties": { 13 + "content": { 14 + "type": "blob", 15 + "accept": [ 16 + "*/*" 17 + ], 18 + "maxSize": 20000 19 + }, 20 + "did": { 21 + "type": "string", 22 + "format": "did" 23 + }, 24 + "name": { 25 + "type": "string" 26 + } 27 + } 28 + }, 29 + "codeThemeName": { 30 + "type": "string" 31 + }, 32 + "main": { 33 + "type": "record", 34 + "description": "Theme for a notebook", 35 + "key": "tid", 36 + "record": { 37 + "type": "object", 38 + "required": [ 39 + "colours", 40 + "fonts", 41 + "spacing", 42 + "codeTheme" 43 + ], 44 + "properties": { 45 + "codeTheme": { 46 + "type": "union", 47 + "refs": [ 48 + "#codeThemeName", 49 + "#codeThemeFile" 50 + ] 51 + }, 52 + "colours": { 53 + "type": "object", 54 + "required": [ 55 + "background", 56 + "foreground", 57 + "primary", 58 + "secondary", 59 + "link", 60 + "link_hover" 61 + ], 62 + "properties": { 63 + "background": { 64 + "type": "string" 65 + }, 66 + "foreground": { 67 + "type": "string" 68 + }, 69 + "link": { 70 + "type": "string" 71 + }, 72 + "link_hover": { 73 + "type": "string" 74 + }, 75 + "primary": { 76 + "type": "string" 77 + }, 78 + "secondary": { 79 + "type": "string" 80 + } 81 + } 82 + }, 83 + "fonts": { 84 + "type": "object", 85 + "required": [ 86 + "body", 87 + "heading", 88 + "monospace" 89 + ], 90 + "properties": { 91 + "body": { 92 + "type": "string" 93 + }, 94 + "heading": { 95 + "type": "string" 96 + }, 97 + "monospace": { 98 + "type": "string" 99 + } 100 + } 101 + }, 102 + "spacing": { 103 + "type": "object", 104 + "required": [ 105 + "baseSize", 106 + "lineHeight", 107 + "scale" 108 + ], 109 + "properties": { 110 + "baseSize": { 111 + "type": "string" 112 + }, 113 + "lineHeight": { 114 + "type": "string" 115 + }, 116 + "scale": { 117 + "type": "string" 118 + } 119 + } 120 + } 121 + } 122 + } 123 + } 124 + } 125 + }
+216 -82
crates/weaver-api/src/sh_weaver/embed/video.rs
··· 273 273 }), 274 274 ); 275 275 map.insert( 276 - ::jacquard_common::smol_str::SmolStr::new_static( 277 - "aspectRatio", 278 - ), 279 - ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 280 - description: None, 281 - r#ref: ::jacquard_common::CowStr::new_static( 282 - "app.bsky.embed.defs#aspectRatio", 283 - ), 284 - }), 285 - ); 286 - map.insert( 287 276 ::jacquard_common::smol_str::SmolStr::new_static("captions"), 288 277 ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 289 278 description: None, ··· 296 285 }), 297 286 ); 298 287 map.insert( 288 + ::jacquard_common::smol_str::SmolStr::new_static( 289 + "dimensions", 290 + ), 291 + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { 292 + description: None, 293 + refs: vec![ 294 + ::jacquard_common::CowStr::new_static("app.bsky.embed.defs#aspectRatio"), 295 + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#percentSize"), 296 + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#pixelSize") 297 + ], 298 + closed: None, 299 + }), 300 + ); 301 + map.insert( 302 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 303 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 304 + description: None, 305 + format: None, 306 + default: None, 307 + min_length: None, 308 + max_length: Some(128usize), 309 + min_graphemes: None, 310 + max_graphemes: None, 311 + r#enum: None, 312 + r#const: None, 313 + known_values: None, 314 + }), 315 + ); 316 + map.insert( 299 317 ::jacquard_common::smol_str::SmolStr::new_static("video"), 300 318 ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { 301 319 description: None, ··· 337 355 }), 338 356 ); 339 357 map.insert( 358 + ::jacquard_common::smol_str::SmolStr::new_static("cid"), 359 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 360 + description: None, 361 + format: Some( 362 + ::jacquard_lexicon::lexicon::LexStringFormat::Cid, 363 + ), 364 + default: None, 365 + min_length: None, 366 + max_length: None, 367 + min_graphemes: None, 368 + max_graphemes: None, 369 + r#enum: None, 370 + r#const: None, 371 + known_values: None, 372 + }), 373 + ); 374 + map.insert( 340 375 ::jacquard_common::smol_str::SmolStr::new_static( 341 - "aspectRatio", 376 + "dimensions", 342 377 ), 343 - ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 378 + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { 344 379 description: None, 345 - r#ref: ::jacquard_common::CowStr::new_static( 346 - "app.bsky.embed.defs#aspectRatio", 347 - ), 380 + refs: vec![ 381 + ::jacquard_common::CowStr::new_static("app.bsky.embed.defs#aspectRatio"), 382 + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#percentSize"), 383 + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#pixelSize") 384 + ], 385 + closed: None, 348 386 }), 349 387 ); 350 388 map.insert( 351 - ::jacquard_common::smol_str::SmolStr::new_static("cid"), 389 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 352 390 ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 353 391 description: None, 354 - format: Some( 355 - ::jacquard_lexicon::lexicon::LexStringFormat::Cid, 356 - ), 392 + format: None, 357 393 default: None, 358 394 min_length: None, 359 - max_length: None, 395 + max_length: Some(128usize), 360 396 min_graphemes: None, 361 397 max_graphemes: None, 362 398 r#enum: None, ··· 582 618 pub alt: Option<jacquard_common::CowStr<'a>>, 583 619 #[serde(skip_serializing_if = "std::option::Option::is_none")] 584 620 #[serde(borrow)] 585 - pub aspect_ratio: Option<crate::app_bsky::embed::AspectRatio<'a>>, 621 + pub captions: Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 622 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 623 + #[serde(borrow)] 624 + pub dimensions: Option<VideoDimensions<'a>>, 586 625 #[serde(skip_serializing_if = "std::option::Option::is_none")] 587 626 #[serde(borrow)] 588 - pub captions: Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 627 + pub name: Option<jacquard_common::CowStr<'a>>, 589 628 /// The mp4 video file. May be up to 100mb, formerly limited to 50mb. 590 629 #[serde(borrow)] 591 630 pub video: jacquard_common::types::blob::BlobRef<'a>, ··· 628 667 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 629 668 __unsafe_private_named: ( 630 669 ::core::option::Option<jacquard_common::CowStr<'a>>, 631 - ::core::option::Option<crate::app_bsky::embed::AspectRatio<'a>>, 632 670 ::core::option::Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 671 + ::core::option::Option<VideoDimensions<'a>>, 672 + ::core::option::Option<jacquard_common::CowStr<'a>>, 633 673 ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 634 674 ), 635 675 _phantom: ::core::marker::PhantomData<&'a ()>, ··· 647 687 pub fn new() -> Self { 648 688 VideoBuilder { 649 689 _phantom_state: ::core::marker::PhantomData, 650 - __unsafe_private_named: (None, None, None, None), 690 + __unsafe_private_named: (None, None, None, None, None), 651 691 _phantom: ::core::marker::PhantomData, 652 692 } 653 693 } ··· 667 707 } 668 708 669 709 impl<'a, S: video_state::State> VideoBuilder<'a, S> { 670 - /// Set the `aspectRatio` field (optional) 671 - pub fn aspect_ratio( 710 + /// Set the `captions` field (optional) 711 + pub fn captions( 672 712 mut self, 673 - value: impl Into<Option<crate::app_bsky::embed::AspectRatio<'a>>>, 713 + value: impl Into<Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>>, 674 714 ) -> Self { 675 715 self.__unsafe_private_named.1 = value.into(); 676 716 self 677 717 } 678 - /// Set the `aspectRatio` field to an Option value (optional) 679 - pub fn maybe_aspect_ratio( 718 + /// Set the `captions` field to an Option value (optional) 719 + pub fn maybe_captions( 680 720 mut self, 681 - value: Option<crate::app_bsky::embed::AspectRatio<'a>>, 721 + value: Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 682 722 ) -> Self { 683 723 self.__unsafe_private_named.1 = value; 684 724 self ··· 686 726 } 687 727 688 728 impl<'a, S: video_state::State> VideoBuilder<'a, S> { 689 - /// Set the `captions` field (optional) 690 - pub fn captions( 691 - mut self, 692 - value: impl Into<Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>>, 693 - ) -> Self { 729 + /// Set the `dimensions` field (optional) 730 + pub fn dimensions(mut self, value: impl Into<Option<VideoDimensions<'a>>>) -> Self { 694 731 self.__unsafe_private_named.2 = value.into(); 695 732 self 696 733 } 697 - /// Set the `captions` field to an Option value (optional) 698 - pub fn maybe_captions( 734 + /// Set the `dimensions` field to an Option value (optional) 735 + pub fn maybe_dimensions(mut self, value: Option<VideoDimensions<'a>>) -> Self { 736 + self.__unsafe_private_named.2 = value; 737 + self 738 + } 739 + } 740 + 741 + impl<'a, S: video_state::State> VideoBuilder<'a, S> { 742 + /// Set the `name` field (optional) 743 + pub fn name( 699 744 mut self, 700 - value: Option<Vec<crate::sh_weaver::embed::video::Caption<'a>>>, 745 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 701 746 ) -> Self { 702 - self.__unsafe_private_named.2 = value; 747 + self.__unsafe_private_named.3 = value.into(); 748 + self 749 + } 750 + /// Set the `name` field to an Option value (optional) 751 + pub fn maybe_name(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 752 + self.__unsafe_private_named.3 = value; 703 753 self 704 754 } 705 755 } ··· 714 764 mut self, 715 765 value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 716 766 ) -> VideoBuilder<'a, video_state::SetVideo<S>> { 717 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 767 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 718 768 VideoBuilder { 719 769 _phantom_state: ::core::marker::PhantomData, 720 770 __unsafe_private_named: self.__unsafe_private_named, ··· 732 782 pub fn build(self) -> Video<'a> { 733 783 Video { 734 784 alt: self.__unsafe_private_named.0, 735 - aspect_ratio: self.__unsafe_private_named.1, 736 - captions: self.__unsafe_private_named.2, 737 - video: self.__unsafe_private_named.3.unwrap(), 785 + captions: self.__unsafe_private_named.1, 786 + dimensions: self.__unsafe_private_named.2, 787 + name: self.__unsafe_private_named.3, 788 + video: self.__unsafe_private_named.4.unwrap(), 738 789 extra_data: Default::default(), 739 790 } 740 791 } ··· 748 799 ) -> Video<'a> { 749 800 Video { 750 801 alt: self.__unsafe_private_named.0, 751 - aspect_ratio: self.__unsafe_private_named.1, 752 - captions: self.__unsafe_private_named.2, 753 - video: self.__unsafe_private_named.3.unwrap(), 802 + captions: self.__unsafe_private_named.1, 803 + dimensions: self.__unsafe_private_named.2, 804 + name: self.__unsafe_private_named.3, 805 + video: self.__unsafe_private_named.4.unwrap(), 754 806 extra_data: Some(extra_data), 755 807 } 756 808 } 757 809 } 758 810 811 + #[jacquard_derive::open_union] 812 + #[derive( 813 + serde::Serialize, 814 + serde::Deserialize, 815 + Debug, 816 + Clone, 817 + PartialEq, 818 + Eq, 819 + jacquard_derive::IntoStatic 820 + )] 821 + #[serde(tag = "$type")] 822 + #[serde(bound(deserialize = "'de: 'a"))] 823 + pub enum VideoDimensions<'a> { 824 + #[serde(rename = "app.bsky.embed.defs#aspectRatio")] 825 + AspectRatio(Box<crate::app_bsky::embed::AspectRatio<'a>>), 826 + #[serde(rename = "sh.weaver.embed.defs#percentSize")] 827 + PercentSize(Box<crate::sh_weaver::embed::PercentSize<'a>>), 828 + #[serde(rename = "sh.weaver.embed.defs#pixelSize")] 829 + PixelSize(Box<crate::sh_weaver::embed::PixelSize<'a>>), 830 + } 831 + 759 832 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Video<'a> { 760 833 fn nsid() -> &'static str { 761 834 "sh.weaver.embed.video" ··· 811 884 }); 812 885 } 813 886 } 887 + if let Some(ref value) = self.name { 888 + #[allow(unused_comparisons)] 889 + if <str>::len(value.as_ref()) > 128usize { 890 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 891 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 892 + "name", 893 + ), 894 + max: 128usize, 895 + actual: <str>::len(value.as_ref()), 896 + }); 897 + } 898 + } 814 899 Ok(()) 815 900 } 816 901 } ··· 830 915 #[serde(skip_serializing_if = "std::option::Option::is_none")] 831 916 #[serde(borrow)] 832 917 pub alt: Option<jacquard_common::CowStr<'a>>, 918 + #[serde(borrow)] 919 + pub cid: jacquard_common::types::string::Cid<'a>, 833 920 #[serde(skip_serializing_if = "std::option::Option::is_none")] 834 921 #[serde(borrow)] 835 - pub aspect_ratio: Option<crate::app_bsky::embed::AspectRatio<'a>>, 922 + pub dimensions: Option<ViewDimensions<'a>>, 923 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 836 924 #[serde(borrow)] 837 - pub cid: jacquard_common::types::string::Cid<'a>, 925 + pub name: Option<jacquard_common::CowStr<'a>>, 838 926 #[serde(borrow)] 839 927 pub playlist: jacquard_common::types::string::Uri<'a>, 840 928 #[serde(skip_serializing_if = "std::option::Option::is_none")] ··· 891 979 _phantom_state: ::core::marker::PhantomData<fn() -> S>, 892 980 __unsafe_private_named: ( 893 981 ::core::option::Option<jacquard_common::CowStr<'a>>, 894 - ::core::option::Option<crate::app_bsky::embed::AspectRatio<'a>>, 895 982 ::core::option::Option<jacquard_common::types::string::Cid<'a>>, 983 + ::core::option::Option<ViewDimensions<'a>>, 984 + ::core::option::Option<jacquard_common::CowStr<'a>>, 896 985 ::core::option::Option<jacquard_common::types::string::Uri<'a>>, 897 986 ::core::option::Option<jacquard_common::types::string::Uri<'a>>, 898 987 ), ··· 911 1000 pub fn new() -> Self { 912 1001 ViewBuilder { 913 1002 _phantom_state: ::core::marker::PhantomData, 914 - __unsafe_private_named: (None, None, None, None, None), 1003 + __unsafe_private_named: (None, None, None, None, None, None), 915 1004 _phantom: ::core::marker::PhantomData, 916 1005 } 917 1006 } ··· 930 1019 } 931 1020 } 932 1021 933 - impl<'a, S: view_state::State> ViewBuilder<'a, S> { 934 - /// Set the `aspectRatio` field (optional) 935 - pub fn aspect_ratio( 936 - mut self, 937 - value: impl Into<Option<crate::app_bsky::embed::AspectRatio<'a>>>, 938 - ) -> Self { 939 - self.__unsafe_private_named.1 = value.into(); 940 - self 941 - } 942 - /// Set the `aspectRatio` field to an Option value (optional) 943 - pub fn maybe_aspect_ratio( 944 - mut self, 945 - value: Option<crate::app_bsky::embed::AspectRatio<'a>>, 946 - ) -> Self { 947 - self.__unsafe_private_named.1 = value; 948 - self 949 - } 950 - } 951 - 952 1022 impl<'a, S> ViewBuilder<'a, S> 953 1023 where 954 1024 S: view_state::State, ··· 959 1029 mut self, 960 1030 value: impl Into<jacquard_common::types::string::Cid<'a>>, 961 1031 ) -> ViewBuilder<'a, view_state::SetCid<S>> { 962 - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 1032 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 963 1033 ViewBuilder { 964 1034 _phantom_state: ::core::marker::PhantomData, 965 1035 __unsafe_private_named: self.__unsafe_private_named, ··· 968 1038 } 969 1039 } 970 1040 1041 + impl<'a, S: view_state::State> ViewBuilder<'a, S> { 1042 + /// Set the `dimensions` field (optional) 1043 + pub fn dimensions(mut self, value: impl Into<Option<ViewDimensions<'a>>>) -> Self { 1044 + self.__unsafe_private_named.2 = value.into(); 1045 + self 1046 + } 1047 + /// Set the `dimensions` field to an Option value (optional) 1048 + pub fn maybe_dimensions(mut self, value: Option<ViewDimensions<'a>>) -> Self { 1049 + self.__unsafe_private_named.2 = value; 1050 + self 1051 + } 1052 + } 1053 + 1054 + impl<'a, S: view_state::State> ViewBuilder<'a, S> { 1055 + /// Set the `name` field (optional) 1056 + pub fn name( 1057 + mut self, 1058 + value: impl Into<Option<jacquard_common::CowStr<'a>>>, 1059 + ) -> Self { 1060 + self.__unsafe_private_named.3 = value.into(); 1061 + self 1062 + } 1063 + /// Set the `name` field to an Option value (optional) 1064 + pub fn maybe_name(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 1065 + self.__unsafe_private_named.3 = value; 1066 + self 1067 + } 1068 + } 1069 + 971 1070 impl<'a, S> ViewBuilder<'a, S> 972 1071 where 973 1072 S: view_state::State, ··· 978 1077 mut self, 979 1078 value: impl Into<jacquard_common::types::string::Uri<'a>>, 980 1079 ) -> ViewBuilder<'a, view_state::SetPlaylist<S>> { 981 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 1080 + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 982 1081 ViewBuilder { 983 1082 _phantom_state: ::core::marker::PhantomData, 984 1083 __unsafe_private_named: self.__unsafe_private_named, ··· 993 1092 mut self, 994 1093 value: impl Into<Option<jacquard_common::types::string::Uri<'a>>>, 995 1094 ) -> Self { 996 - self.__unsafe_private_named.4 = value.into(); 1095 + self.__unsafe_private_named.5 = value.into(); 997 1096 self 998 1097 } 999 1098 /// Set the `thumbnail` field to an Option value (optional) ··· 1001 1100 mut self, 1002 1101 value: Option<jacquard_common::types::string::Uri<'a>>, 1003 1102 ) -> Self { 1004 - self.__unsafe_private_named.4 = value; 1103 + self.__unsafe_private_named.5 = value; 1005 1104 self 1006 1105 } 1007 1106 } ··· 1016 1115 pub fn build(self) -> View<'a> { 1017 1116 View { 1018 1117 alt: self.__unsafe_private_named.0, 1019 - aspect_ratio: self.__unsafe_private_named.1, 1020 - cid: self.__unsafe_private_named.2.unwrap(), 1021 - playlist: self.__unsafe_private_named.3.unwrap(), 1022 - thumbnail: self.__unsafe_private_named.4, 1118 + cid: self.__unsafe_private_named.1.unwrap(), 1119 + dimensions: self.__unsafe_private_named.2, 1120 + name: self.__unsafe_private_named.3, 1121 + playlist: self.__unsafe_private_named.4.unwrap(), 1122 + thumbnail: self.__unsafe_private_named.5, 1023 1123 extra_data: Default::default(), 1024 1124 } 1025 1125 } ··· 1033 1133 ) -> View<'a> { 1034 1134 View { 1035 1135 alt: self.__unsafe_private_named.0, 1036 - aspect_ratio: self.__unsafe_private_named.1, 1037 - cid: self.__unsafe_private_named.2.unwrap(), 1038 - playlist: self.__unsafe_private_named.3.unwrap(), 1039 - thumbnail: self.__unsafe_private_named.4, 1136 + cid: self.__unsafe_private_named.1.unwrap(), 1137 + dimensions: self.__unsafe_private_named.2, 1138 + name: self.__unsafe_private_named.3, 1139 + playlist: self.__unsafe_private_named.4.unwrap(), 1140 + thumbnail: self.__unsafe_private_named.5, 1040 1141 extra_data: Some(extra_data), 1041 1142 } 1042 1143 } 1144 + } 1145 + 1146 + #[jacquard_derive::open_union] 1147 + #[derive( 1148 + serde::Serialize, 1149 + serde::Deserialize, 1150 + Debug, 1151 + Clone, 1152 + PartialEq, 1153 + Eq, 1154 + jacquard_derive::IntoStatic 1155 + )] 1156 + #[serde(tag = "$type")] 1157 + #[serde(bound(deserialize = "'de: 'a"))] 1158 + pub enum ViewDimensions<'a> { 1159 + #[serde(rename = "app.bsky.embed.defs#aspectRatio")] 1160 + AspectRatio(Box<crate::app_bsky::embed::AspectRatio<'a>>), 1161 + #[serde(rename = "sh.weaver.embed.defs#percentSize")] 1162 + PercentSize(Box<crate::sh_weaver::embed::PercentSize<'a>>), 1163 + #[serde(rename = "sh.weaver.embed.defs#pixelSize")] 1164 + PixelSize(Box<crate::sh_weaver::embed::PixelSize<'a>>), 1043 1165 } 1044 1166 1045 1167 impl<'a> ::jacquard_lexicon::schema::LexiconSchema for View<'a> { ··· 1083 1205 actual: count, 1084 1206 }); 1085 1207 } 1208 + } 1209 + } 1210 + if let Some(ref value) = self.name { 1211 + #[allow(unused_comparisons)] 1212 + if <str>::len(value.as_ref()) > 128usize { 1213 + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 1214 + path: ::jacquard_lexicon::validation::ValidationPath::from_field( 1215 + "name", 1216 + ), 1217 + max: 128usize, 1218 + actual: <str>::len(value.as_ref()), 1219 + }); 1086 1220 } 1087 1221 } 1088 1222 Ok(())
+1
crates/weaver-api/src/sh_weaver/notebook.rs
··· 10 10 pub mod chapter; 11 11 pub mod entry; 12 12 pub mod page; 13 + pub mod theme; 13 14 14 15 #[jacquard_derive::lexicon] 15 16 #[derive(
+39 -5
crates/weaver-api/src/sh_weaver/notebook/book.rs
··· 30 30 pub tags: Option<crate::sh_weaver::notebook::Tags<'a>>, 31 31 #[serde(skip_serializing_if = "std::option::Option::is_none")] 32 32 #[serde(borrow)] 33 + pub theme: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 34 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 35 + #[serde(borrow)] 33 36 pub title: Option<crate::sh_weaver::notebook::Title<'a>>, 34 37 } 35 38 ··· 85 88 ::core::option::Option<jacquard_common::types::string::Datetime>, 86 89 ::core::option::Option<Vec<crate::com_atproto::repo::strong_ref::StrongRef<'a>>>, 87 90 ::core::option::Option<crate::sh_weaver::notebook::Tags<'a>>, 91 + ::core::option::Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 88 92 ::core::option::Option<crate::sh_weaver::notebook::Title<'a>>, 89 93 ), 90 94 _phantom: ::core::marker::PhantomData<&'a ()>, ··· 102 106 pub fn new() -> Self { 103 107 BookBuilder { 104 108 _phantom_state: ::core::marker::PhantomData, 105 - __unsafe_private_named: (None, None, None, None, None), 109 + __unsafe_private_named: (None, None, None, None, None, None), 106 110 _phantom: ::core::marker::PhantomData, 107 111 } 108 112 } ··· 185 189 } 186 190 187 191 impl<'a, S: book_state::State> BookBuilder<'a, S> { 192 + /// Set the `theme` field (optional) 193 + pub fn theme( 194 + mut self, 195 + value: impl Into<Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>>, 196 + ) -> Self { 197 + self.__unsafe_private_named.4 = value.into(); 198 + self 199 + } 200 + /// Set the `theme` field to an Option value (optional) 201 + pub fn maybe_theme( 202 + mut self, 203 + value: Option<crate::com_atproto::repo::strong_ref::StrongRef<'a>>, 204 + ) -> Self { 205 + self.__unsafe_private_named.4 = value; 206 + self 207 + } 208 + } 209 + 210 + impl<'a, S: book_state::State> BookBuilder<'a, S> { 188 211 /// Set the `title` field (optional) 189 212 pub fn title( 190 213 mut self, 191 214 value: impl Into<Option<crate::sh_weaver::notebook::Title<'a>>>, 192 215 ) -> Self { 193 - self.__unsafe_private_named.4 = value.into(); 216 + self.__unsafe_private_named.5 = value.into(); 194 217 self 195 218 } 196 219 /// Set the `title` field to an Option value (optional) ··· 198 221 mut self, 199 222 value: Option<crate::sh_weaver::notebook::Title<'a>>, 200 223 ) -> Self { 201 - self.__unsafe_private_named.4 = value; 224 + self.__unsafe_private_named.5 = value; 202 225 self 203 226 } 204 227 } ··· 216 239 created_at: self.__unsafe_private_named.1, 217 240 entry_list: self.__unsafe_private_named.2.unwrap(), 218 241 tags: self.__unsafe_private_named.3, 219 - title: self.__unsafe_private_named.4, 242 + theme: self.__unsafe_private_named.4, 243 + title: self.__unsafe_private_named.5, 220 244 extra_data: Default::default(), 221 245 } 222 246 } ··· 233 257 created_at: self.__unsafe_private_named.1, 234 258 entry_list: self.__unsafe_private_named.2.unwrap(), 235 259 tags: self.__unsafe_private_named.3, 236 - title: self.__unsafe_private_named.4, 260 + theme: self.__unsafe_private_named.4, 261 + title: self.__unsafe_private_named.5, 237 262 extra_data: Some(extra_data), 238 263 } 239 264 } ··· 407 432 description: None, 408 433 r#ref: ::jacquard_common::CowStr::new_static( 409 434 "sh.weaver.notebook.defs#tags", 435 + ), 436 + }), 437 + ); 438 + map.insert( 439 + ::jacquard_common::smol_str::SmolStr::new_static("theme"), 440 + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 441 + description: None, 442 + r#ref: ::jacquard_common::CowStr::new_static( 443 + "com.atproto.repo.strongRef", 410 444 ), 411 445 }), 412 446 );
+1059
crates/weaver-api/src/sh_weaver/notebook/theme.rs
··· 1 + // @generated by jacquard-lexicon. DO NOT EDIT. 2 + // 3 + // Lexicon: sh.weaver.notebook.theme 4 + // 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, 11 + serde::Deserialize, 12 + Debug, 13 + Clone, 14 + PartialEq, 15 + Eq, 16 + jacquard_derive::IntoStatic 17 + )] 18 + #[serde(rename_all = "camelCase")] 19 + pub struct CodeThemeFile<'a> { 20 + #[serde(borrow)] 21 + pub content: jacquard_common::types::blob::BlobRef<'a>, 22 + #[serde(borrow)] 23 + pub did: jacquard_common::types::string::Did<'a>, 24 + #[serde(borrow)] 25 + pub name: jacquard_common::CowStr<'a>, 26 + } 27 + 28 + pub mod code_theme_file_state { 29 + 30 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 31 + #[allow(unused)] 32 + use ::core::marker::PhantomData; 33 + mod sealed { 34 + pub trait Sealed {} 35 + } 36 + /// State trait tracking which required fields have been set 37 + pub trait State: sealed::Sealed { 38 + type Name; 39 + type Did; 40 + type Content; 41 + } 42 + /// Empty state - all required fields are unset 43 + pub struct Empty(()); 44 + impl sealed::Sealed for Empty {} 45 + impl State for Empty { 46 + type Name = Unset; 47 + type Did = Unset; 48 + type Content = Unset; 49 + } 50 + ///State transition - sets the `name` field to Set 51 + pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>); 52 + impl<S: State> sealed::Sealed for SetName<S> {} 53 + impl<S: State> State for SetName<S> { 54 + type Name = Set<members::name>; 55 + type Did = S::Did; 56 + type Content = S::Content; 57 + } 58 + ///State transition - sets the `did` field to Set 59 + pub struct SetDid<S: State = Empty>(PhantomData<fn() -> S>); 60 + impl<S: State> sealed::Sealed for SetDid<S> {} 61 + impl<S: State> State for SetDid<S> { 62 + type Name = S::Name; 63 + type Did = Set<members::did>; 64 + type Content = S::Content; 65 + } 66 + ///State transition - sets the `content` field to Set 67 + pub struct SetContent<S: State = Empty>(PhantomData<fn() -> S>); 68 + impl<S: State> sealed::Sealed for SetContent<S> {} 69 + impl<S: State> State for SetContent<S> { 70 + type Name = S::Name; 71 + type Did = S::Did; 72 + type Content = Set<members::content>; 73 + } 74 + /// Marker types for field names 75 + #[allow(non_camel_case_types)] 76 + pub mod members { 77 + ///Marker type for the `name` field 78 + pub struct name(()); 79 + ///Marker type for the `did` field 80 + pub struct did(()); 81 + ///Marker type for the `content` field 82 + pub struct content(()); 83 + } 84 + } 85 + 86 + /// Builder for constructing an instance of this type 87 + pub struct CodeThemeFileBuilder<'a, S: code_theme_file_state::State> { 88 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 89 + __unsafe_private_named: ( 90 + ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 91 + ::core::option::Option<jacquard_common::types::string::Did<'a>>, 92 + ::core::option::Option<jacquard_common::CowStr<'a>>, 93 + ), 94 + _phantom: ::core::marker::PhantomData<&'a ()>, 95 + } 96 + 97 + impl<'a> CodeThemeFile<'a> { 98 + /// Create a new builder for this type 99 + pub fn new() -> CodeThemeFileBuilder<'a, code_theme_file_state::Empty> { 100 + CodeThemeFileBuilder::new() 101 + } 102 + } 103 + 104 + impl<'a> CodeThemeFileBuilder<'a, code_theme_file_state::Empty> { 105 + /// Create a new builder with all fields unset 106 + pub fn new() -> Self { 107 + CodeThemeFileBuilder { 108 + _phantom_state: ::core::marker::PhantomData, 109 + __unsafe_private_named: (None, None, None), 110 + _phantom: ::core::marker::PhantomData, 111 + } 112 + } 113 + } 114 + 115 + impl<'a, S> CodeThemeFileBuilder<'a, S> 116 + where 117 + S: code_theme_file_state::State, 118 + S::Content: code_theme_file_state::IsUnset, 119 + { 120 + /// Set the `content` field (required) 121 + pub fn content( 122 + mut self, 123 + value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 124 + ) -> CodeThemeFileBuilder<'a, code_theme_file_state::SetContent<S>> { 125 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 126 + CodeThemeFileBuilder { 127 + _phantom_state: ::core::marker::PhantomData, 128 + __unsafe_private_named: self.__unsafe_private_named, 129 + _phantom: ::core::marker::PhantomData, 130 + } 131 + } 132 + } 133 + 134 + impl<'a, S> CodeThemeFileBuilder<'a, S> 135 + where 136 + S: code_theme_file_state::State, 137 + S::Did: code_theme_file_state::IsUnset, 138 + { 139 + /// Set the `did` field (required) 140 + pub fn did( 141 + mut self, 142 + value: impl Into<jacquard_common::types::string::Did<'a>>, 143 + ) -> CodeThemeFileBuilder<'a, code_theme_file_state::SetDid<S>> { 144 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 145 + CodeThemeFileBuilder { 146 + _phantom_state: ::core::marker::PhantomData, 147 + __unsafe_private_named: self.__unsafe_private_named, 148 + _phantom: ::core::marker::PhantomData, 149 + } 150 + } 151 + } 152 + 153 + impl<'a, S> CodeThemeFileBuilder<'a, S> 154 + where 155 + S: code_theme_file_state::State, 156 + S::Name: code_theme_file_state::IsUnset, 157 + { 158 + /// Set the `name` field (required) 159 + pub fn name( 160 + mut self, 161 + value: impl Into<jacquard_common::CowStr<'a>>, 162 + ) -> CodeThemeFileBuilder<'a, code_theme_file_state::SetName<S>> { 163 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 164 + CodeThemeFileBuilder { 165 + _phantom_state: ::core::marker::PhantomData, 166 + __unsafe_private_named: self.__unsafe_private_named, 167 + _phantom: ::core::marker::PhantomData, 168 + } 169 + } 170 + } 171 + 172 + impl<'a, S> CodeThemeFileBuilder<'a, S> 173 + where 174 + S: code_theme_file_state::State, 175 + S::Name: code_theme_file_state::IsSet, 176 + S::Did: code_theme_file_state::IsSet, 177 + S::Content: code_theme_file_state::IsSet, 178 + { 179 + /// Build the final struct 180 + pub fn build(self) -> CodeThemeFile<'a> { 181 + CodeThemeFile { 182 + content: self.__unsafe_private_named.0.unwrap(), 183 + did: self.__unsafe_private_named.1.unwrap(), 184 + name: self.__unsafe_private_named.2.unwrap(), 185 + extra_data: Default::default(), 186 + } 187 + } 188 + /// Build the final struct with custom extra_data 189 + pub fn build_with_data( 190 + self, 191 + extra_data: std::collections::BTreeMap< 192 + jacquard_common::smol_str::SmolStr, 193 + jacquard_common::types::value::Data<'a>, 194 + >, 195 + ) -> CodeThemeFile<'a> { 196 + CodeThemeFile { 197 + content: self.__unsafe_private_named.0.unwrap(), 198 + did: self.__unsafe_private_named.1.unwrap(), 199 + name: self.__unsafe_private_named.2.unwrap(), 200 + extra_data: Some(extra_data), 201 + } 202 + } 203 + } 204 + 205 + fn lexicon_doc_sh_weaver_notebook_theme() -> ::jacquard_lexicon::lexicon::LexiconDoc< 206 + 'static, 207 + > { 208 + ::jacquard_lexicon::lexicon::LexiconDoc { 209 + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 210 + id: ::jacquard_common::CowStr::new_static("sh.weaver.notebook.theme"), 211 + revision: None, 212 + description: None, 213 + defs: { 214 + let mut map = ::std::collections::BTreeMap::new(); 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"), 222 + ::jacquard_common::smol_str::SmolStr::new_static("did"), 223 + ::jacquard_common::smol_str::SmolStr::new_static("content") 224 + ], 225 + ), 226 + nullable: None, 227 + properties: { 228 + #[allow(unused_mut)] 229 + let mut map = ::std::collections::BTreeMap::new(); 230 + map.insert( 231 + ::jacquard_common::smol_str::SmolStr::new_static("content"), 232 + ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { 233 + description: None, 234 + accept: None, 235 + max_size: None, 236 + }), 237 + ); 238 + map.insert( 239 + ::jacquard_common::smol_str::SmolStr::new_static("did"), 240 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 241 + description: None, 242 + format: Some( 243 + ::jacquard_lexicon::lexicon::LexStringFormat::Did, 244 + ), 245 + default: None, 246 + min_length: None, 247 + max_length: None, 248 + min_graphemes: None, 249 + max_graphemes: None, 250 + r#enum: None, 251 + r#const: None, 252 + known_values: None, 253 + }), 254 + ); 255 + map.insert( 256 + ::jacquard_common::smol_str::SmolStr::new_static("name"), 257 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 258 + description: None, 259 + format: None, 260 + default: None, 261 + min_length: None, 262 + max_length: None, 263 + min_graphemes: None, 264 + max_graphemes: None, 265 + r#enum: None, 266 + r#const: None, 267 + known_values: None, 268 + }), 269 + ); 270 + map 271 + }, 272 + }), 273 + ); 274 + map.insert( 275 + ::jacquard_common::smol_str::SmolStr::new_static("codeThemeName"), 276 + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { 277 + description: None, 278 + format: None, 279 + default: None, 280 + min_length: None, 281 + max_length: None, 282 + min_graphemes: None, 283 + max_graphemes: None, 284 + r#enum: None, 285 + r#const: None, 286 + known_values: None, 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( 293 + ::jacquard_common::CowStr::new_static("Theme for a notebook"), 294 + ), 295 + key: Some(::jacquard_common::CowStr::new_static("tid")), 296 + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 297 + description: None, 298 + required: Some( 299 + vec![ 300 + ::jacquard_common::smol_str::SmolStr::new_static("colours"), 301 + ::jacquard_common::smol_str::SmolStr::new_static("fonts"), 302 + ::jacquard_common::smol_str::SmolStr::new_static("spacing"), 303 + ::jacquard_common::smol_str::SmolStr::new_static("codeTheme") 304 + ], 305 + ), 306 + nullable: None, 307 + properties: { 308 + #[allow(unused_mut)] 309 + let mut map = ::std::collections::BTreeMap::new(); 310 + map.insert( 311 + ::jacquard_common::smol_str::SmolStr::new_static( 312 + "codeTheme", 313 + ), 314 + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { 315 + description: None, 316 + refs: vec![ 317 + ::jacquard_common::CowStr::new_static("#codeThemeName"), 318 + ::jacquard_common::CowStr::new_static("#codeThemeFile") 319 + ], 320 + closed: None, 321 + }), 322 + ); 323 + map.insert( 324 + ::jacquard_common::smol_str::SmolStr::new_static("colours"), 325 + ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { 326 + description: None, 327 + required: Some( 328 + vec![ 329 + ::jacquard_common::smol_str::SmolStr::new_static("background"), 330 + ::jacquard_common::smol_str::SmolStr::new_static("foreground"), 331 + ::jacquard_common::smol_str::SmolStr::new_static("primary"), 332 + ::jacquard_common::smol_str::SmolStr::new_static("secondary"), 333 + ::jacquard_common::smol_str::SmolStr::new_static("link"), 334 + ::jacquard_common::smol_str::SmolStr::new_static("link_hover") 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( 343 + "background", 344 + ), 345 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 346 + description: None, 347 + format: None, 348 + default: None, 349 + min_length: None, 350 + max_length: None, 351 + min_graphemes: None, 352 + max_graphemes: None, 353 + r#enum: None, 354 + r#const: None, 355 + known_values: None, 356 + }), 357 + ); 358 + map.insert( 359 + ::jacquard_common::smol_str::SmolStr::new_static( 360 + "foreground", 361 + ), 362 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 363 + description: None, 364 + format: None, 365 + default: None, 366 + min_length: None, 367 + max_length: None, 368 + min_graphemes: None, 369 + max_graphemes: None, 370 + r#enum: None, 371 + r#const: None, 372 + known_values: None, 373 + }), 374 + ); 375 + map.insert( 376 + ::jacquard_common::smol_str::SmolStr::new_static("link"), 377 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 378 + description: None, 379 + format: None, 380 + default: None, 381 + min_length: None, 382 + max_length: None, 383 + min_graphemes: None, 384 + max_graphemes: None, 385 + r#enum: None, 386 + r#const: None, 387 + known_values: None, 388 + }), 389 + ); 390 + map.insert( 391 + ::jacquard_common::smol_str::SmolStr::new_static( 392 + "link_hover", 393 + ), 394 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 395 + description: None, 396 + format: None, 397 + default: None, 398 + min_length: None, 399 + max_length: None, 400 + min_graphemes: None, 401 + max_graphemes: None, 402 + r#enum: None, 403 + r#const: None, 404 + known_values: None, 405 + }), 406 + ); 407 + map.insert( 408 + ::jacquard_common::smol_str::SmolStr::new_static("primary"), 409 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 410 + description: None, 411 + format: None, 412 + default: None, 413 + min_length: None, 414 + max_length: None, 415 + min_graphemes: None, 416 + max_graphemes: None, 417 + r#enum: None, 418 + r#const: None, 419 + known_values: None, 420 + }), 421 + ); 422 + map.insert( 423 + ::jacquard_common::smol_str::SmolStr::new_static( 424 + "secondary", 425 + ), 426 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 427 + description: None, 428 + format: None, 429 + default: None, 430 + min_length: None, 431 + max_length: None, 432 + min_graphemes: None, 433 + max_graphemes: None, 434 + r#enum: None, 435 + r#const: None, 436 + known_values: None, 437 + }), 438 + ); 439 + map 440 + }, 441 + }), 442 + ); 443 + map.insert( 444 + ::jacquard_common::smol_str::SmolStr::new_static("fonts"), 445 + ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { 446 + description: None, 447 + required: Some( 448 + vec![ 449 + ::jacquard_common::smol_str::SmolStr::new_static("body"), 450 + ::jacquard_common::smol_str::SmolStr::new_static("heading"), 451 + ::jacquard_common::smol_str::SmolStr::new_static("monospace") 452 + ], 453 + ), 454 + nullable: None, 455 + properties: { 456 + #[allow(unused_mut)] 457 + let mut map = ::std::collections::BTreeMap::new(); 458 + map.insert( 459 + ::jacquard_common::smol_str::SmolStr::new_static("body"), 460 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 461 + description: None, 462 + format: None, 463 + default: None, 464 + min_length: None, 465 + max_length: None, 466 + min_graphemes: None, 467 + max_graphemes: None, 468 + r#enum: None, 469 + r#const: None, 470 + known_values: None, 471 + }), 472 + ); 473 + map.insert( 474 + ::jacquard_common::smol_str::SmolStr::new_static("heading"), 475 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 476 + description: None, 477 + format: None, 478 + default: None, 479 + min_length: None, 480 + max_length: None, 481 + min_graphemes: None, 482 + max_graphemes: None, 483 + r#enum: None, 484 + r#const: None, 485 + known_values: None, 486 + }), 487 + ); 488 + map.insert( 489 + ::jacquard_common::smol_str::SmolStr::new_static( 490 + "monospace", 491 + ), 492 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 493 + description: None, 494 + format: None, 495 + default: None, 496 + min_length: None, 497 + max_length: None, 498 + min_graphemes: None, 499 + max_graphemes: None, 500 + r#enum: None, 501 + r#const: None, 502 + known_values: None, 503 + }), 504 + ); 505 + map 506 + }, 507 + }), 508 + ); 509 + map.insert( 510 + ::jacquard_common::smol_str::SmolStr::new_static("spacing"), 511 + ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { 512 + description: None, 513 + required: Some( 514 + vec![ 515 + ::jacquard_common::smol_str::SmolStr::new_static("baseSize"), 516 + ::jacquard_common::smol_str::SmolStr::new_static("lineHeight"), 517 + ::jacquard_common::smol_str::SmolStr::new_static("scale") 518 + ], 519 + ), 520 + nullable: None, 521 + properties: { 522 + #[allow(unused_mut)] 523 + let mut map = ::std::collections::BTreeMap::new(); 524 + map.insert( 525 + ::jacquard_common::smol_str::SmolStr::new_static( 526 + "baseSize", 527 + ), 528 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 529 + description: None, 530 + format: None, 531 + default: None, 532 + min_length: None, 533 + max_length: None, 534 + min_graphemes: None, 535 + max_graphemes: None, 536 + r#enum: None, 537 + r#const: None, 538 + known_values: None, 539 + }), 540 + ); 541 + map.insert( 542 + ::jacquard_common::smol_str::SmolStr::new_static( 543 + "lineHeight", 544 + ), 545 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 546 + description: None, 547 + format: None, 548 + default: None, 549 + min_length: None, 550 + max_length: None, 551 + min_graphemes: None, 552 + max_graphemes: None, 553 + r#enum: None, 554 + r#const: None, 555 + known_values: None, 556 + }), 557 + ); 558 + map.insert( 559 + ::jacquard_common::smol_str::SmolStr::new_static("scale"), 560 + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 561 + description: None, 562 + format: None, 563 + default: None, 564 + min_length: None, 565 + max_length: None, 566 + min_graphemes: None, 567 + max_graphemes: None, 568 + r#enum: None, 569 + r#const: None, 570 + known_values: None, 571 + }), 572 + ); 573 + map 574 + }, 575 + }), 576 + ); 577 + map 578 + }, 579 + }), 580 + }), 581 + ); 582 + map 583 + }, 584 + } 585 + } 586 + 587 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CodeThemeFile<'a> { 588 + fn nsid() -> &'static str { 589 + "sh.weaver.notebook.theme" 590 + } 591 + fn def_name() -> &'static str { 592 + "codeThemeFile" 593 + } 594 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 595 + lexicon_doc_sh_weaver_notebook_theme() 596 + } 597 + fn validate( 598 + &self, 599 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 600 + Ok(()) 601 + } 602 + } 603 + 604 + pub type CodeThemeName<'a> = jacquard_common::CowStr<'a>; 605 + /// Theme for a notebook 606 + #[jacquard_derive::lexicon] 607 + #[derive( 608 + serde::Serialize, 609 + serde::Deserialize, 610 + Debug, 611 + Clone, 612 + PartialEq, 613 + Eq, 614 + jacquard_derive::IntoStatic 615 + )] 616 + #[serde(rename_all = "camelCase")] 617 + pub struct Theme<'a> { 618 + #[serde(borrow)] 619 + pub code_theme: ThemeCodeTheme<'a>, 620 + #[serde(borrow)] 621 + pub colours: ThemeColours<'a>, 622 + #[serde(borrow)] 623 + pub fonts: ThemeFonts<'a>, 624 + #[serde(borrow)] 625 + pub spacing: ThemeSpacing<'a>, 626 + } 627 + 628 + pub mod theme_state { 629 + 630 + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 631 + #[allow(unused)] 632 + use ::core::marker::PhantomData; 633 + mod sealed { 634 + pub trait Sealed {} 635 + } 636 + /// State trait tracking which required fields have been set 637 + pub trait State: sealed::Sealed { 638 + type Colours; 639 + type Fonts; 640 + type Spacing; 641 + type CodeTheme; 642 + } 643 + /// Empty state - all required fields are unset 644 + pub struct Empty(()); 645 + impl sealed::Sealed for Empty {} 646 + impl State for Empty { 647 + type Colours = Unset; 648 + type Fonts = Unset; 649 + type Spacing = Unset; 650 + type CodeTheme = Unset; 651 + } 652 + ///State transition - sets the `colours` field to Set 653 + pub struct SetColours<S: State = Empty>(PhantomData<fn() -> S>); 654 + impl<S: State> sealed::Sealed for SetColours<S> {} 655 + impl<S: State> State for SetColours<S> { 656 + type Colours = Set<members::colours>; 657 + type Fonts = S::Fonts; 658 + type Spacing = S::Spacing; 659 + type CodeTheme = S::CodeTheme; 660 + } 661 + ///State transition - sets the `fonts` field to Set 662 + pub struct SetFonts<S: State = Empty>(PhantomData<fn() -> S>); 663 + impl<S: State> sealed::Sealed for SetFonts<S> {} 664 + impl<S: State> State for SetFonts<S> { 665 + type Colours = S::Colours; 666 + type Fonts = Set<members::fonts>; 667 + type Spacing = S::Spacing; 668 + type CodeTheme = S::CodeTheme; 669 + } 670 + ///State transition - sets the `spacing` field to Set 671 + pub struct SetSpacing<S: State = Empty>(PhantomData<fn() -> S>); 672 + impl<S: State> sealed::Sealed for SetSpacing<S> {} 673 + impl<S: State> State for SetSpacing<S> { 674 + type Colours = S::Colours; 675 + type Fonts = S::Fonts; 676 + type Spacing = Set<members::spacing>; 677 + type CodeTheme = S::CodeTheme; 678 + } 679 + ///State transition - sets the `code_theme` field to Set 680 + pub struct SetCodeTheme<S: State = Empty>(PhantomData<fn() -> S>); 681 + impl<S: State> sealed::Sealed for SetCodeTheme<S> {} 682 + impl<S: State> State for SetCodeTheme<S> { 683 + type Colours = S::Colours; 684 + type Fonts = S::Fonts; 685 + type Spacing = S::Spacing; 686 + type CodeTheme = Set<members::code_theme>; 687 + } 688 + /// Marker types for field names 689 + #[allow(non_camel_case_types)] 690 + pub mod members { 691 + ///Marker type for the `colours` field 692 + pub struct colours(()); 693 + ///Marker type for the `fonts` field 694 + pub struct fonts(()); 695 + ///Marker type for the `spacing` field 696 + pub struct spacing(()); 697 + ///Marker type for the `code_theme` field 698 + pub struct code_theme(()); 699 + } 700 + } 701 + 702 + /// Builder for constructing an instance of this type 703 + pub struct ThemeBuilder<'a, S: theme_state::State> { 704 + _phantom_state: ::core::marker::PhantomData<fn() -> S>, 705 + __unsafe_private_named: ( 706 + ::core::option::Option<ThemeCodeTheme<'a>>, 707 + ::core::option::Option<ThemeColours<'a>>, 708 + ::core::option::Option<ThemeFonts<'a>>, 709 + ::core::option::Option<ThemeSpacing<'a>>, 710 + ), 711 + _phantom: ::core::marker::PhantomData<&'a ()>, 712 + } 713 + 714 + impl<'a> Theme<'a> { 715 + /// Create a new builder for this type 716 + pub fn new() -> ThemeBuilder<'a, theme_state::Empty> { 717 + ThemeBuilder::new() 718 + } 719 + } 720 + 721 + impl<'a> ThemeBuilder<'a, theme_state::Empty> { 722 + /// Create a new builder with all fields unset 723 + pub fn new() -> Self { 724 + ThemeBuilder { 725 + _phantom_state: ::core::marker::PhantomData, 726 + __unsafe_private_named: (None, None, None, None), 727 + _phantom: ::core::marker::PhantomData, 728 + } 729 + } 730 + } 731 + 732 + impl<'a, S> ThemeBuilder<'a, S> 733 + where 734 + S: theme_state::State, 735 + S::CodeTheme: theme_state::IsUnset, 736 + { 737 + /// Set the `codeTheme` field (required) 738 + pub fn code_theme( 739 + mut self, 740 + value: impl Into<ThemeCodeTheme<'a>>, 741 + ) -> ThemeBuilder<'a, theme_state::SetCodeTheme<S>> { 742 + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 743 + ThemeBuilder { 744 + _phantom_state: ::core::marker::PhantomData, 745 + __unsafe_private_named: self.__unsafe_private_named, 746 + _phantom: ::core::marker::PhantomData, 747 + } 748 + } 749 + } 750 + 751 + impl<'a, S> ThemeBuilder<'a, S> 752 + where 753 + S: theme_state::State, 754 + S::Colours: theme_state::IsUnset, 755 + { 756 + /// Set the `colours` field (required) 757 + pub fn colours( 758 + mut self, 759 + value: impl Into<ThemeColours<'a>>, 760 + ) -> ThemeBuilder<'a, theme_state::SetColours<S>> { 761 + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 762 + ThemeBuilder { 763 + _phantom_state: ::core::marker::PhantomData, 764 + __unsafe_private_named: self.__unsafe_private_named, 765 + _phantom: ::core::marker::PhantomData, 766 + } 767 + } 768 + } 769 + 770 + impl<'a, S> ThemeBuilder<'a, S> 771 + where 772 + S: theme_state::State, 773 + S::Fonts: theme_state::IsUnset, 774 + { 775 + /// Set the `fonts` field (required) 776 + pub fn fonts( 777 + mut self, 778 + value: impl Into<ThemeFonts<'a>>, 779 + ) -> ThemeBuilder<'a, theme_state::SetFonts<S>> { 780 + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 781 + ThemeBuilder { 782 + _phantom_state: ::core::marker::PhantomData, 783 + __unsafe_private_named: self.__unsafe_private_named, 784 + _phantom: ::core::marker::PhantomData, 785 + } 786 + } 787 + } 788 + 789 + impl<'a, S> ThemeBuilder<'a, S> 790 + where 791 + S: theme_state::State, 792 + S::Spacing: theme_state::IsUnset, 793 + { 794 + /// Set the `spacing` field (required) 795 + pub fn spacing( 796 + mut self, 797 + value: impl Into<ThemeSpacing<'a>>, 798 + ) -> ThemeBuilder<'a, theme_state::SetSpacing<S>> { 799 + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 800 + ThemeBuilder { 801 + _phantom_state: ::core::marker::PhantomData, 802 + __unsafe_private_named: self.__unsafe_private_named, 803 + _phantom: ::core::marker::PhantomData, 804 + } 805 + } 806 + } 807 + 808 + impl<'a, S> ThemeBuilder<'a, S> 809 + where 810 + S: theme_state::State, 811 + S::Colours: theme_state::IsSet, 812 + S::Fonts: theme_state::IsSet, 813 + S::Spacing: theme_state::IsSet, 814 + S::CodeTheme: theme_state::IsSet, 815 + { 816 + /// Build the final struct 817 + pub fn build(self) -> Theme<'a> { 818 + Theme { 819 + code_theme: self.__unsafe_private_named.0.unwrap(), 820 + colours: self.__unsafe_private_named.1.unwrap(), 821 + fonts: self.__unsafe_private_named.2.unwrap(), 822 + spacing: self.__unsafe_private_named.3.unwrap(), 823 + extra_data: Default::default(), 824 + } 825 + } 826 + /// Build the final struct with custom extra_data 827 + pub fn build_with_data( 828 + self, 829 + extra_data: std::collections::BTreeMap< 830 + jacquard_common::smol_str::SmolStr, 831 + jacquard_common::types::value::Data<'a>, 832 + >, 833 + ) -> Theme<'a> { 834 + Theme { 835 + code_theme: self.__unsafe_private_named.0.unwrap(), 836 + colours: self.__unsafe_private_named.1.unwrap(), 837 + fonts: self.__unsafe_private_named.2.unwrap(), 838 + spacing: self.__unsafe_private_named.3.unwrap(), 839 + extra_data: Some(extra_data), 840 + } 841 + } 842 + } 843 + 844 + impl<'a> Theme<'a> { 845 + pub fn uri( 846 + uri: impl Into<jacquard_common::CowStr<'a>>, 847 + ) -> Result< 848 + jacquard_common::types::uri::RecordUri<'a, ThemeRecord>, 849 + jacquard_common::types::uri::UriError, 850 + > { 851 + jacquard_common::types::uri::RecordUri::try_from_uri( 852 + jacquard_common::types::string::AtUri::new_cow(uri.into())?, 853 + ) 854 + } 855 + } 856 + 857 + #[jacquard_derive::open_union] 858 + #[derive( 859 + serde::Serialize, 860 + serde::Deserialize, 861 + Debug, 862 + Clone, 863 + PartialEq, 864 + Eq, 865 + jacquard_derive::IntoStatic 866 + )] 867 + #[serde(tag = "$type")] 868 + #[serde(bound(deserialize = "'de: 'a"))] 869 + pub enum ThemeCodeTheme<'a> { 870 + #[serde(rename = "sh.weaver.notebook.theme#codeThemeName")] 871 + CodeThemeName(Box<crate::sh_weaver::notebook::theme::CodeThemeName<'a>>), 872 + #[serde(rename = "sh.weaver.notebook.theme#codeThemeFile")] 873 + CodeThemeFile(Box<crate::sh_weaver::notebook::theme::CodeThemeFile<'a>>), 874 + } 875 + 876 + #[jacquard_derive::lexicon] 877 + #[derive( 878 + serde::Serialize, 879 + serde::Deserialize, 880 + Debug, 881 + Clone, 882 + PartialEq, 883 + Eq, 884 + jacquard_derive::IntoStatic, 885 + Default 886 + )] 887 + #[serde(rename_all = "camelCase")] 888 + pub struct ThemeColours<'a> { 889 + #[serde(borrow)] 890 + pub background: jacquard_common::CowStr<'a>, 891 + #[serde(borrow)] 892 + pub foreground: jacquard_common::CowStr<'a>, 893 + #[serde(borrow)] 894 + pub link: jacquard_common::CowStr<'a>, 895 + #[serde(borrow)] 896 + pub link_hover: jacquard_common::CowStr<'a>, 897 + #[serde(borrow)] 898 + pub primary: jacquard_common::CowStr<'a>, 899 + #[serde(borrow)] 900 + pub secondary: jacquard_common::CowStr<'a>, 901 + } 902 + 903 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeColours<'a> { 904 + fn nsid() -> &'static str { 905 + "sh.weaver.notebook.theme" 906 + } 907 + fn def_name() -> &'static str { 908 + "ThemeColours" 909 + } 910 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 911 + lexicon_doc_sh_weaver_notebook_theme() 912 + } 913 + fn validate( 914 + &self, 915 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 916 + Ok(()) 917 + } 918 + } 919 + 920 + #[jacquard_derive::lexicon] 921 + #[derive( 922 + serde::Serialize, 923 + serde::Deserialize, 924 + Debug, 925 + Clone, 926 + PartialEq, 927 + Eq, 928 + jacquard_derive::IntoStatic, 929 + Default 930 + )] 931 + #[serde(rename_all = "camelCase")] 932 + pub struct ThemeFonts<'a> { 933 + #[serde(borrow)] 934 + pub body: jacquard_common::CowStr<'a>, 935 + #[serde(borrow)] 936 + pub heading: jacquard_common::CowStr<'a>, 937 + #[serde(borrow)] 938 + pub monospace: jacquard_common::CowStr<'a>, 939 + } 940 + 941 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeFonts<'a> { 942 + fn nsid() -> &'static str { 943 + "sh.weaver.notebook.theme" 944 + } 945 + fn def_name() -> &'static str { 946 + "ThemeFonts" 947 + } 948 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 949 + lexicon_doc_sh_weaver_notebook_theme() 950 + } 951 + fn validate( 952 + &self, 953 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 954 + Ok(()) 955 + } 956 + } 957 + 958 + #[jacquard_derive::lexicon] 959 + #[derive( 960 + serde::Serialize, 961 + serde::Deserialize, 962 + Debug, 963 + Clone, 964 + PartialEq, 965 + Eq, 966 + jacquard_derive::IntoStatic, 967 + Default 968 + )] 969 + #[serde(rename_all = "camelCase")] 970 + pub struct ThemeSpacing<'a> { 971 + #[serde(borrow)] 972 + pub base_size: jacquard_common::CowStr<'a>, 973 + #[serde(borrow)] 974 + pub line_height: jacquard_common::CowStr<'a>, 975 + #[serde(borrow)] 976 + pub scale: jacquard_common::CowStr<'a>, 977 + } 978 + 979 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeSpacing<'a> { 980 + fn nsid() -> &'static str { 981 + "sh.weaver.notebook.theme" 982 + } 983 + fn def_name() -> &'static str { 984 + "ThemeSpacing" 985 + } 986 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 987 + lexicon_doc_sh_weaver_notebook_theme() 988 + } 989 + fn validate( 990 + &self, 991 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 992 + Ok(()) 993 + } 994 + } 995 + 996 + /// Typed wrapper for GetRecord response with this collection's record type. 997 + #[derive( 998 + serde::Serialize, 999 + serde::Deserialize, 1000 + Debug, 1001 + Clone, 1002 + PartialEq, 1003 + Eq, 1004 + jacquard_derive::IntoStatic 1005 + )] 1006 + #[serde(rename_all = "camelCase")] 1007 + pub struct ThemeGetRecordOutput<'a> { 1008 + #[serde(skip_serializing_if = "std::option::Option::is_none")] 1009 + #[serde(borrow)] 1010 + pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 1011 + #[serde(borrow)] 1012 + pub uri: jacquard_common::types::string::AtUri<'a>, 1013 + #[serde(borrow)] 1014 + pub value: Theme<'a>, 1015 + } 1016 + 1017 + impl From<ThemeGetRecordOutput<'_>> for Theme<'_> { 1018 + fn from(output: ThemeGetRecordOutput<'_>) -> Self { 1019 + use jacquard_common::IntoStatic; 1020 + output.value.into_static() 1021 + } 1022 + } 1023 + 1024 + impl jacquard_common::types::collection::Collection for Theme<'_> { 1025 + const NSID: &'static str = "sh.weaver.notebook.theme"; 1026 + type Record = ThemeRecord; 1027 + } 1028 + 1029 + /// Marker type for deserializing records from this collection. 1030 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 1031 + pub struct ThemeRecord; 1032 + impl jacquard_common::xrpc::XrpcResp for ThemeRecord { 1033 + const NSID: &'static str = "sh.weaver.notebook.theme"; 1034 + const ENCODING: &'static str = "application/json"; 1035 + type Output<'de> = ThemeGetRecordOutput<'de>; 1036 + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 1037 + } 1038 + 1039 + impl jacquard_common::types::collection::Collection for ThemeRecord { 1040 + const NSID: &'static str = "sh.weaver.notebook.theme"; 1041 + type Record = ThemeRecord; 1042 + } 1043 + 1044 + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Theme<'a> { 1045 + fn nsid() -> &'static str { 1046 + "sh.weaver.notebook.theme" 1047 + } 1048 + fn def_name() -> &'static str { 1049 + "main" 1050 + } 1051 + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1052 + lexicon_doc_sh_weaver_notebook_theme() 1053 + } 1054 + fn validate( 1055 + &self, 1056 + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1057 + Ok(()) 1058 + } 1059 + }
+1
crates/weaver-renderer/Cargo.toml
··· 8 8 [dependencies] 9 9 n0-future.workspace = true 10 10 weaver-common = { path = "../weaver-common" } 11 + weaver-api = { path = "../weaver-api" } 11 12 markdown-weaver = { workspace = true } 12 13 compact_string = "0.1.0" 13 14 http = "1.3.1"
+66 -47
crates/weaver-renderer/src/css.rs
··· 4 4 use syntect::highlighting::{Theme as SyntectTheme, ThemeSet}; 5 5 use syntect::html::{ClassStyle, css_for_theme_with_class_style}; 6 6 use syntect::parsing::SyntaxSet; 7 + use weaver_api::com_atproto::sync::get_blob::GetBlob; 8 + use weaver_api::sh_weaver::notebook::theme::ThemeCodeTheme; 9 + use weaver_common::jacquard::client::BasicClient; 10 + use weaver_common::jacquard::prelude::*; 11 + use weaver_common::jacquard::xrpc::XrpcExt; 7 12 8 13 // Embed rose-pine themes at compile time 9 14 const ROSE_PINE_THEME: &str = include_str!("../themes/rose-pine.tmTheme"); ··· 187 192 margin: 2rem 0; 188 193 }} 189 194 "#, 190 - theme.colors.background, 191 - theme.colors.foreground, 192 - theme.colors.link, 193 - theme.colors.link_hover, 194 - theme.colors.primary, 195 - theme.colors.secondary, 195 + theme.colours.background, 196 + theme.colours.foreground, 197 + theme.colours.link, 198 + theme.colours.link_hover, 199 + theme.colours.primary, 200 + theme.colours.secondary, 196 201 theme.fonts.body, 197 202 theme.fonts.heading, 198 203 theme.fonts.monospace, 199 - theme.spacing.base_font_size, 204 + theme.spacing.base_size, 200 205 theme.spacing.line_height, 201 206 theme.spacing.scale, 202 207 ) 203 208 } 204 209 205 - pub fn generate_syntax_css(theme: &Theme, _syntax_set: &SyntaxSet) -> miette::Result<String> { 206 - let syntect_theme = if let Some(custom_path) = &theme.custom_syntect_theme_path { 207 - // Load custom theme from file 208 - ThemeSet::get_theme(custom_path) 209 - .into_diagnostic() 210 - .map_err(|e| { 211 - miette::miette!("Failed to load custom theme from {:?}: {}", custom_path, e) 212 - })? 213 - } else { 214 - // Check for embedded themes first 215 - match theme.syntect_theme_name.as_str() { 216 - "rose-pine" => { 217 - let mut cursor = Cursor::new(ROSE_PINE_THEME.as_bytes()); 218 - ThemeSet::load_from_reader(&mut cursor) 219 - .into_diagnostic() 220 - .map_err(|e| { 221 - miette::miette!("Failed to load embedded rose-pine theme: {}", e) 222 - })? 223 - } 224 - "rose-pine-dawn" => { 225 - let mut cursor = Cursor::new(ROSE_PINE_DAWN_THEME.as_bytes()); 226 - ThemeSet::load_from_reader(&mut cursor) 227 - .into_diagnostic() 228 - .map_err(|e| { 229 - miette::miette!("Failed to load embedded rose-pine-dawn theme: {}", e) 230 - })? 231 - } 232 - _ => { 233 - // Fall back to syntect's built-in themes 234 - let theme_set = ThemeSet::load_defaults(); 235 - theme_set 236 - .themes 237 - .get(theme.syntect_theme_name.as_str()) 238 - .ok_or_else(|| { 239 - miette::miette!( 240 - "Theme '{}' not found in defaults", 241 - theme.syntect_theme_name 242 - ) 243 - })? 244 - .clone() 210 + pub async fn generate_syntax_css(theme: &Theme<'_>) -> miette::Result<String> { 211 + let syntect_theme = match &theme.code_theme { 212 + ThemeCodeTheme::CodeThemeName(name) => { 213 + match name.as_str() { 214 + "rose-pine" => { 215 + let mut cursor = Cursor::new(ROSE_PINE_THEME.as_bytes()); 216 + ThemeSet::load_from_reader(&mut cursor) 217 + .into_diagnostic() 218 + .map_err(|e| { 219 + miette::miette!("Failed to load embedded rose-pine theme: {}", e) 220 + })? 221 + } 222 + "rose-pine-dawn" => { 223 + let mut cursor = Cursor::new(ROSE_PINE_DAWN_THEME.as_bytes()); 224 + ThemeSet::load_from_reader(&mut cursor) 225 + .into_diagnostic() 226 + .map_err(|e| { 227 + miette::miette!("Failed to load embedded rose-pine-dawn theme: {}", e) 228 + })? 229 + } 230 + _ => { 231 + // Fall back to syntect's built-in themes 232 + let theme_set = ThemeSet::load_defaults(); 233 + theme_set 234 + .themes 235 + .get(name.as_str()) 236 + .ok_or_else(|| miette::miette!("Theme '{}' not found in defaults", name))? 237 + .clone() 238 + } 245 239 } 240 + } 241 + ThemeCodeTheme::CodeThemeFile(file) => { 242 + let client = BasicClient::unauthenticated(); 243 + let pds = client.pds_for_did(&file.did).await?; 244 + let blob = client 245 + .xrpc(pds) 246 + .send( 247 + &GetBlob::new() 248 + .did(file.did.clone()) 249 + .cid(file.content.blob().cid().clone()) 250 + .build(), 251 + ) 252 + .await? 253 + .buffer() 254 + .clone(); 255 + let mut cursor = Cursor::new(blob); 256 + ThemeSet::load_from_reader(&mut cursor) 257 + .into_diagnostic() 258 + .map_err(|e| miette::miette!("Failed to download theme: {}", e))? 259 + } 260 + _ => { 261 + let mut cursor = Cursor::new(ROSE_PINE_THEME.as_bytes()); 262 + ThemeSet::load_from_reader(&mut cursor) 263 + .into_diagnostic() 264 + .map_err(|e| miette::miette!("Failed to load embedded rose-pine theme: {}", e))? 246 265 } 247 266 }; 248 267
+56 -70
crates/weaver-renderer/src/static_site.rs
··· 16 16 document::{CssMode, write_document_footer, write_document_head}, 17 17 writer::StaticPageWriter, 18 18 }, 19 + theme::defaultTheme, 19 20 utils::flatten_dir_to_just_one_parent, 20 21 walker::{WalkOptions, vault_contents}, 21 22 }; ··· 149 150 // Generate CSS files for multi-file mode 150 151 self.generate_css_files().await?; 151 152 152 - let mut writers = 153 - Vec::with_capacity(self.context.dir_contents.clone().unwrap_or_default().len()); 154 - 155 153 for file in self 156 154 .context 157 155 .dir_contents ··· 162 160 .filter(|file| file.starts_with(&self.context.start_at)) 163 161 { 164 162 let context = self.context.clone(); 165 - let file = file.clone(); 166 - // we'll see if this is a problem or not 167 - writers.push(n0_future::task::spawn(async move { 168 - let relative_path = file 169 - .strip_prefix(context.start_at.clone()) 170 - .expect("file should always be nested under root") 171 - .to_path_buf(); 163 + let relative_path = file 164 + .strip_prefix(context.start_at.clone()) 165 + .expect("file should always be nested under root") 166 + .to_path_buf(); 172 167 173 - // Check if this is a markdown file 174 - let is_markdown = file 175 - .extension() 176 - .and_then(|ext| ext.to_str()) 177 - .map(|ext| ext == "md" || ext == "markdown") 178 - .unwrap_or(false); 168 + // Check if this is a markdown file 169 + let is_markdown = file 170 + .extension() 171 + .and_then(|ext| ext.to_str()) 172 + .map(|ext| ext == "md" || ext == "markdown") 173 + .unwrap_or(false); 179 174 180 - if !is_markdown { 181 - // Copy non-markdown files directly 182 - let output_path = if context 183 - .options 184 - .contains(StaticSiteOptions::FLATTEN_STRUCTURE) 185 - { 186 - let path_str = relative_path.to_string_lossy(); 187 - let (parent, fname) = flatten_dir_to_just_one_parent(&path_str); 188 - let parent = if parent.is_empty() { "entry" } else { parent }; 189 - context 190 - .destination 191 - .join(String::from(parent)) 192 - .join(String::from(fname)) 193 - } else { 194 - context.destination.join(relative_path.clone()) 195 - }; 196 - 197 - // Create parent directory if needed 198 - if let Some(parent) = output_path.parent() { 199 - tokio::fs::create_dir_all(parent).await.into_diagnostic()?; 200 - } 201 - 202 - tokio::fs::copy(&file, &output_path) 203 - .await 204 - .into_diagnostic()?; 205 - return Ok(()); 206 - } 207 - 208 - // Process markdown files 209 - // Check if this is the designated index file 210 - if let Some(index) = &context.index_file { 211 - if &relative_path == index { 212 - let output_path = context.destination.join("index.html"); 213 - return write_page(context.clone(), file, output_path).await; 214 - } 215 - } 216 - 217 - if context 175 + if !is_markdown { 176 + // Copy non-markdown files directly 177 + let output_path = if context 218 178 .options 219 179 .contains(StaticSiteOptions::FLATTEN_STRUCTURE) 220 180 { 221 181 let path_str = relative_path.to_string_lossy(); 222 182 let (parent, fname) = flatten_dir_to_just_one_parent(&path_str); 223 183 let parent = if parent.is_empty() { "entry" } else { parent }; 224 - let output_path = context 184 + context 225 185 .destination 226 186 .join(String::from(parent)) 227 - .join(String::from(fname)); 228 - 229 - write_page(context.clone(), file.clone(), output_path).await?; 187 + .join(String::from(fname)) 230 188 } else { 231 - let output_path = context.destination.join(relative_path.clone()); 189 + context.destination.join(relative_path.clone()) 190 + }; 232 191 233 - write_page(context.clone(), file.clone(), output_path).await?; 192 + // Create parent directory if needed 193 + if let Some(parent) = output_path.parent() { 194 + tokio::fs::create_dir_all(parent).await.into_diagnostic()?; 234 195 } 235 - Ok::<(), miette::Report>(()) 236 - })); 237 - } 196 + 197 + tokio::fs::copy(&file, &output_path) 198 + .await 199 + .into_diagnostic()?; 200 + return Ok(()); 201 + } 238 202 239 - // def want to scope these so we wait until they all complete before we return 240 - // and then we def want the errors, or at least the first error 241 - for fut in n0_future::join_all(writers).await.into_iter() { 242 - fut.into_diagnostic()??; 203 + // Process markdown files 204 + // Check if this is the designated index file 205 + if let Some(index) = &context.index_file { 206 + if &relative_path == index { 207 + let output_path = context.destination.join("index.html"); 208 + return write_page(context.clone(), file, output_path).await; 209 + } 210 + } 211 + 212 + if context 213 + .options 214 + .contains(StaticSiteOptions::FLATTEN_STRUCTURE) 215 + { 216 + let path_str = relative_path.to_string_lossy(); 217 + let (parent, fname) = flatten_dir_to_just_one_parent(&path_str); 218 + let parent = if parent.is_empty() { "entry" } else { parent }; 219 + let output_path = context 220 + .destination 221 + .join(String::from(parent)) 222 + .join(String::from(fname)); 223 + 224 + write_page(context.clone(), file.clone(), output_path).await?; 225 + } else { 226 + let output_path = context.destination.join(relative_path.clone()); 227 + 228 + write_page(context.clone(), file.clone(), output_path).await?; 229 + } 243 230 } 244 231 245 232 // Generate default index if requested and no custom index specified ··· 257 244 258 245 async fn generate_css_files(&self) -> Result<(), miette::Report> { 259 246 use crate::css::{generate_base_css, generate_syntax_css}; 260 - use crate::theme::Theme; 261 247 262 248 let css_dir = self.context.destination.join("css"); 263 249 tokio::fs::create_dir_all(&css_dir) 264 250 .await 265 251 .into_diagnostic()?; 266 252 267 - let default_theme = Theme::default(); 253 + let default_theme = defaultTheme(); 268 254 let theme = self.context.theme.as_deref().unwrap_or(&default_theme); 269 255 270 256 // Write base.css ··· 274 260 .into_diagnostic()?; 275 261 276 262 // Write syntax.css 277 - let syntax_css = generate_syntax_css(theme, &self.context.syntax_set)?; 263 + let syntax_css = generate_syntax_css(theme).await?; 278 264 tokio::fs::write(css_dir.join("syntax.css"), syntax_css) 279 265 .await 280 266 .into_diagnostic()?;
+2 -2
crates/weaver-renderer/src/static_site/context.rs
··· 39 39 pub client: Option<reqwest::Client>, 40 40 agent: Option<Arc<Agent<A>>>, 41 41 42 - pub theme: Option<Arc<Theme>>, 42 + pub theme: Option<Arc<Theme<'static>>>, 43 43 pub katex_source: Option<KaTeXSource>, 44 44 pub syntax_set: Arc<SyntaxSet>, 45 45 pub index_file: Option<PathBuf>, ··· 143 143 } 144 144 } 145 145 146 - pub fn with_theme(mut self, theme: Theme) -> Self { 146 + pub fn with_theme(mut self, theme: Theme<'static>) -> Self { 147 147 self.theme = Some(Arc::new(theme)); 148 148 self 149 149 }
+3 -3
crates/weaver-renderer/src/static_site/document.rs
··· 1 1 use crate::css::{generate_base_css, generate_syntax_css}; 2 2 use crate::static_site::context::{KaTeXSource, StaticSiteContext}; 3 - use crate::theme::Theme; 3 + use crate::theme::{Theme, defaultTheme}; 4 4 use miette::IntoDiagnostic; 5 5 use weaver_common::jacquard::client::AgentSession; 6 6 ··· 98 98 .into_diagnostic()?; 99 99 } 100 100 CssMode::Inline => { 101 - let default_theme = Theme::default(); 101 + let default_theme = defaultTheme(); 102 102 let theme = context.theme.as_deref().unwrap_or(&default_theme); 103 103 104 104 writer.write_all(b" <style>\n").await.into_diagnostic()?; ··· 109 109 writer.write_all(b" </style>\n").await.into_diagnostic()?; 110 110 111 111 writer.write_all(b" <style>\n").await.into_diagnostic()?; 112 - let syntax_css = generate_syntax_css(theme, &context.syntax_set)?; 112 + let syntax_css = generate_syntax_css(theme).await?; 113 113 writer 114 114 .write_all(syntax_css.as_bytes()) 115 115 .await
+29 -77
crates/weaver-renderer/src/theme.rs
··· 1 - use smol_str::SmolStr; 2 - use std::path::PathBuf; 1 + pub use weaver_api::sh_weaver::notebook::theme::{ 2 + Theme, ThemeCodeTheme, ThemeColours, ThemeFonts, ThemeSpacing, 3 + }; 4 + use weaver_common::jacquard::CowStr; 5 + use weaver_common::jacquard::cowstr::ToCowStr; 3 6 4 - #[derive(Debug, Clone)] 5 - pub struct Theme { 6 - pub colors: ColorScheme, 7 - pub fonts: FontScheme, 8 - pub spacing: SpacingScheme, 9 - pub syntect_theme_name: SmolStr, 10 - pub custom_syntect_theme_path: Option<PathBuf>, 11 - } 12 - 13 - #[derive(Debug, Clone)] 14 - pub struct ColorScheme { 15 - pub background: SmolStr, 16 - pub foreground: SmolStr, 17 - pub link: SmolStr, 18 - pub link_hover: SmolStr, 19 - pub primary: SmolStr, 20 - pub secondary: SmolStr, 21 - } 22 - 23 - #[derive(Debug, Clone)] 24 - pub struct FontScheme { 25 - pub body: SmolStr, 26 - pub heading: SmolStr, 27 - pub monospace: SmolStr, 28 - } 29 - 30 - #[derive(Debug, Clone)] 31 - pub struct SpacingScheme { 32 - pub base_font_size: SmolStr, 33 - pub line_height: SmolStr, 34 - pub scale: SmolStr, 35 - } 36 - 37 - impl Default for Theme { 38 - fn default() -> Self { 39 - Self { 40 - colors: ColorScheme::default(), 41 - fonts: FontScheme::default(), 42 - spacing: SpacingScheme::default(), 43 - syntect_theme_name: SmolStr::new("rose-pine-dawn"), 44 - custom_syntect_theme_path: None, 45 - } 46 - } 47 - } 48 - 49 - impl Default for ColorScheme { 50 - fn default() -> Self { 51 - Self { 52 - background: SmolStr::new("#faf4ed"), 53 - foreground: SmolStr::new("#2b303b"), 54 - link: SmolStr::new("#286983"), 55 - link_hover: SmolStr::new("#56949f"), 56 - primary: SmolStr::new("#c4a7e7"), 57 - secondary: SmolStr::new("#3e8fb0"), 58 - } 59 - } 60 - } 7 + pub fn defaultTheme() -> Theme<'static> { 8 + Theme::new() 9 + .code_theme(ThemeCodeTheme::CodeThemeName(Box::new( 10 + "rose-pine".to_cowstr(), 11 + ))) 12 + .colours(ThemeColours { 13 + background: CowStr::new_static("#faf4ed"), 14 + foreground: CowStr::new_static("#2b303b"), 15 + link: CowStr::new_static("#286983"), 16 + link_hover: CowStr::new_static("#56949f"), 17 + primary: CowStr::new_static("#c4a7e7"), 18 + secondary: CowStr::new_static("#3e8fb0"), 61 19 62 - impl Default for FontScheme { 63 - fn default() -> Self { 64 - Self { 65 - body: SmolStr::new( 20 + ..Default::default() 21 + }).fonts(ThemeFonts { 22 + body: CowStr::new_static( 66 23 "IBM Plex, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", 67 24 ), 68 - heading: SmolStr::new( 25 + heading:CowStr::new_static( 69 26 "IBM Plex Sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", 70 27 ), 71 - monospace: SmolStr::new( 28 + monospace: CowStr::new_static( 72 29 "'IBM Plex Mono', 'Berkeley Mono', 'Cascadia Code', 'Roboto Mono', Consolas, monospace", 73 30 ), 74 - } 75 - } 76 - } 77 - 78 - impl Default for SpacingScheme { 79 - fn default() -> Self { 80 - Self { 81 - base_font_size: SmolStr::new("16px"), 82 - line_height: SmolStr::new("1.6"), 83 - scale: SmolStr::new("1.25"), 84 - } 85 - } 31 + ..Default::default() 32 + }).spacing(ThemeSpacing { 33 + base_size: CowStr::new_static("16px"), 34 + line_height: CowStr::new_static("1.6"), 35 + scale: CowStr::new_static("1.25"), 36 + ..Default::default() 37 + }).build() 86 38 }
+1
crates/weaver-renderer/src/utils.rs
··· 223 223 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 224 224 use tokio::fs::{self, File}; 225 225 226 + #[cfg(not(all(target_family = "wasm", target_os = "unknown")))] 226 227 pub async fn create_file(dest: &Path) -> miette::Result<File> { 227 228 let file = File::create(dest) 228 229 .or_else(async |err| {
+3 -1
crates/weaver-server/Cargo.toml
··· 14 14 jacquard-axum = { workspace = true, optional = true } 15 15 weaver-api = { path = "../weaver-api", features = ["streaming"] } 16 16 markdown-weaver = { workspace = true } 17 + weaver-renderer = { path = "../weaver-renderer" } 17 18 moka = { version = "0.12", features = ["future"], optional = true } 18 19 mini-moka = { version = "0.10" } 19 20 dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false } 21 + axum = {version = "0.8.6", optional = true} 20 22 21 23 [features] 22 24 default = ["web"] ··· 27 29 # The feature that are only required for the mobile = ["dioxus/mobile"] build target should be optional and only enabled in the mobile = ["dioxus/mobile"] feature 28 30 mobile = ["dioxus/mobile"] 29 31 # The feature that are only required for the server = ["dioxus/server"] build target should be optional and only enabled in the server = ["dioxus/server"] feature 30 - server = ["dioxus/server", "dep:jacquard-axum", "dep:moka"] 32 + server = ["dioxus/server", "dep:jacquard-axum", "dep:moka", "dep:axum"]
+72
crates/weaver-server/src/blobcache.rs
··· 1 + use dioxus::{CapturedError, Result}; 2 + use jacquard::{ 3 + bytes::Bytes, 4 + client::BasicClient, 5 + prelude::*, 6 + smol_str::SmolStr, 7 + types::{cid::Cid, ident::AtIdentifier}, 8 + }; 9 + use std::{ 10 + sync::{Arc, Mutex}, 11 + time::Duration, 12 + }; 13 + use weaver_api::com_atproto::sync::get_blob::GetBlob; 14 + 15 + #[derive(Clone)] 16 + pub struct BlobCache { 17 + client: Arc<BasicClient>, 18 + cache: mini_moka::sync::Cache<Cid<'static>, Bytes>, 19 + map: mini_moka::sync::Cache<SmolStr, Cid<'static>>, 20 + } 21 + 22 + impl BlobCache { 23 + pub fn new(client: Arc<BasicClient>) -> Self { 24 + let cache = mini_moka::sync::Cache::builder() 25 + .max_capacity(100) 26 + .time_to_idle(Duration::from_secs(1200)) 27 + .build(); 28 + let map = mini_moka::sync::Cache::builder() 29 + .max_capacity(500) 30 + .time_to_idle(Duration::from_secs(1200)) 31 + .build(); 32 + 33 + Self { client, cache, map } 34 + } 35 + 36 + pub async fn cache( 37 + &self, 38 + ident: AtIdentifier<'static>, 39 + cid: Cid<'static>, 40 + name: Option<SmolStr>, 41 + ) -> Result<()> { 42 + let (repo_did, pds_url) = match ident { 43 + AtIdentifier::Did(did) => { 44 + let pds = self.client.pds_for_did(&did).await?; 45 + (did.clone(), pds) 46 + } 47 + AtIdentifier::Handle(handle) => self.client.pds_for_handle(&handle).await?, 48 + }; 49 + let blob = self 50 + .client 51 + .xrpc(pds_url) 52 + .send(&GetBlob::new().cid(cid.clone()).did(repo_did).build()) 53 + .await? 54 + .buffer() 55 + .clone(); 56 + 57 + self.cache.insert(cid.clone(), blob); 58 + if let Some(name) = name { 59 + self.map.insert(name, cid); 60 + } 61 + 62 + Ok(()) 63 + } 64 + 65 + pub fn get_cid(&self, cid: &Cid<'static>) -> Option<Bytes> { 66 + self.cache.get(cid) 67 + } 68 + 69 + pub fn get_named(&self, name: &SmolStr) -> Option<Bytes> { 70 + self.map.get(name).and_then(|cid| self.cache.get(&cid)) 71 + } 72 + }
+57
crates/weaver-server/src/components/css.rs
··· 1 + #[allow(unused_imports)] 2 + use crate::fetch; 3 + use dioxus::prelude::*; 4 + #[allow(unused_imports)] 5 + use dioxus::{fullstack::extract::Extension, fullstack::get_server_url, CapturedError}; 6 + use jacquard::smol_str::SmolStr; 7 + #[allow(unused_imports)] 8 + use std::sync::Arc; 9 + #[allow(unused_imports)] 10 + use weaver_renderer::theme::Theme; 11 + 12 + #[component] 13 + pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element { 14 + rsx! { 15 + document::Stylesheet { 16 + href: "{get_server_url()}/css/{ident}/{notebook}" 17 + } 18 + } 19 + } 20 + 21 + #[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::CachedFetcher>>)] 22 + pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<String> { 23 + use jacquard::client::AgentSessionExt; 24 + use jacquard::types::ident::AtIdentifier; 25 + use jacquard::{from_data, CowStr}; 26 + 27 + use weaver_api::sh_weaver::notebook::book::Book; 28 + use weaver_renderer::css::{generate_base_css, generate_syntax_css}; 29 + use weaver_renderer::theme::defaultTheme; 30 + 31 + let ident = AtIdentifier::new_owned(ident)?; 32 + let theme = if let Some(notebook) = fetcher.get_notebook(ident, notebook).await? { 33 + let book: Book = from_data(&notebook.0.record).unwrap(); 34 + if let Some(theme) = book.theme { 35 + if let Ok(theme) = fetcher.client.get_record::<Theme>(&theme.uri).await { 36 + theme 37 + .into_output() 38 + .map(|t| t.value) 39 + .unwrap_or(defaultTheme()) 40 + } else { 41 + defaultTheme() 42 + } 43 + } else { 44 + defaultTheme() 45 + } 46 + } else { 47 + defaultTheme() 48 + }; 49 + let mut css = generate_base_css(&theme); 50 + css.push_str( 51 + &generate_syntax_css(&theme) 52 + .await 53 + .map_err(|e| CapturedError::from_display(e)) 54 + .unwrap_or_default(), 55 + ); 56 + Ok(css) 57 + }
-2
crates/weaver-server/src/components/cssblob.rs
··· 1 - use dioxus::prelude::*; 2 - use weaver_common::jacquard::smol_str::SmolStr;
+2 -1
crates/weaver-server/src/components/mod.rs
··· 2 2 //! They can be used to defined common UI elements like buttons, forms, and modals. In this template, we define a Hero 3 3 //! component and an Echo component for fullstack apps to be used in our app. 4 4 5 - mod cssblob; 5 + mod css; 6 + pub use css::NotebookCss; 6 7 7 8 mod entry; 8 9 pub use entry::{Entry, EntryCard};
+70 -2
crates/weaver-server/src/fetch.rs
··· 12 12 13 13 #[derive(Clone)] 14 14 pub struct CachedFetcher { 15 - client: Arc<BasicClient>, 16 - 15 + pub client: Arc<BasicClient>, 16 + #[cfg(not(feature = "server"))] 17 17 book_cache: Arc< 18 18 Mutex< 19 19 mini_moka::unsync::Cache< ··· 22 22 >, 23 23 >, 24 24 >, 25 + #[cfg(not(feature = "server"))] 25 26 entry_cache: Arc< 26 27 Mutex< 27 28 mini_moka::unsync::Cache< ··· 30 31 >, 31 32 >, 32 33 >, 34 + #[cfg(feature = "server")] 35 + book_cache: Arc< 36 + Mutex< 37 + mini_moka::sync::Cache< 38 + (AtIdentifier<'static>, SmolStr), 39 + Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>, 40 + >, 41 + >, 42 + >, 43 + #[cfg(feature = "server")] 44 + entry_cache: Arc< 45 + Mutex< 46 + mini_moka::sync::Cache< 47 + (AtIdentifier<'static>, SmolStr), 48 + Arc<(BookEntryView<'static>, Entry<'static>)>, 49 + >, 50 + >, 51 + >, 33 52 } 34 53 35 54 impl CachedFetcher { 55 + #[cfg(not(feature = "server"))] 36 56 pub fn new(client: Arc<BasicClient>) -> Self { 37 57 let book_cache = mini_moka::unsync::Cache::builder() 38 58 .max_capacity(100) ··· 50 70 } 51 71 } 52 72 73 + #[cfg(feature = "server")] 74 + pub fn new(client: Arc<BasicClient>) -> Self { 75 + let book_cache = mini_moka::sync::Cache::builder() 76 + .max_capacity(100) 77 + .time_to_idle(Duration::from_secs(1200)) 78 + .build(); 79 + let entry_cache = mini_moka::sync::Cache::builder() 80 + .max_capacity(100) 81 + .time_to_idle(Duration::from_secs(600)) 82 + .build(); 83 + 84 + Self { 85 + client, 86 + book_cache: Arc::new(Mutex::new(book_cache)), 87 + entry_cache: Arc::new(Mutex::new(entry_cache)), 88 + } 89 + } 90 + 53 91 pub async fn get_notebook( 54 92 &self, 55 93 ident: AtIdentifier<'static>, ··· 112 150 } 113 151 } 114 152 153 + #[cfg(not(feature = "server"))] 115 154 pub fn list_recent_entries(&self) -> Vec<Arc<(BookEntryView<'static>, Entry<'static>)>> { 116 155 if let Ok(entry_cache) = self.entry_cache.lock() { 117 156 let mut entries = Vec::new(); ··· 124 163 } 125 164 } 126 165 166 + #[cfg(not(feature = "server"))] 127 167 pub fn list_recent_notebooks( 128 168 &self, 129 169 ) -> Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>> { 130 170 if let Ok(book_cache) = self.book_cache.lock() { 131 171 let mut entries = Vec::new(); 132 172 for (_, entry) in book_cache.iter() { 173 + entries.push(entry.clone()); 174 + } 175 + entries 176 + } else { 177 + Vec::new() 178 + } 179 + } 180 + 181 + #[cfg(feature = "server")] 182 + pub fn list_recent_entries(&self) -> Vec<Arc<(BookEntryView<'static>, Entry<'static>)>> { 183 + if let Ok(entry_cache) = self.entry_cache.lock() { 184 + let mut entries = Vec::new(); 185 + for entry in entry_cache.iter() { 186 + entries.push(entry.clone()); 187 + } 188 + entries 189 + } else { 190 + Vec::new() 191 + } 192 + } 193 + 194 + #[cfg(feature = "server")] 195 + pub fn list_recent_notebooks( 196 + &self, 197 + ) -> Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>> { 198 + if let Ok(book_cache) = self.book_cache.lock() { 199 + let mut entries = Vec::new(); 200 + for entry in book_cache.iter() { 133 201 entries.push(entry.clone()); 134 202 } 135 203 entries
+18 -1
crates/weaver-server/src/main.rs
··· 8 8 use std::sync::Arc; 9 9 use views::{Home, Navbar, Notebook, NotebookIndex, NotebookPage}; 10 10 11 + mod blobcache; 11 12 /// Define a components module that contains all shared components for our app. 12 13 mod components; 13 14 mod fetch; ··· 53 54 // Run `serve()` on the server only 54 55 #[cfg(feature = "server")] 55 56 dioxus::serve(|| async move { 57 + use axum::{extract::Request, middleware, middleware::Next}; 56 58 // Create a new router for our app using the `router` function 57 - let mut router = dioxus::server::router(App); 59 + let mut router = dioxus::server::router(App).layer(middleware::from_fn( 60 + |mut req: Request, next: Next| async move { 61 + // Attach some extra state to the request 62 + 63 + use crate::fetch::CachedFetcher; 64 + use std::convert::Infallible; 65 + use std::sync::Arc; 66 + req.extensions_mut() 67 + .insert(Arc::new(CachedFetcher::new(Arc::new( 68 + BasicClient::unauthenticated(), 69 + )))); 70 + 71 + // And then return the response with `next.run() 72 + Ok::<_, Infallible>(next.run(req).await) 73 + }, 74 + )); 58 75 59 76 // .. customize the router, adding layers and new routes 60 77
+4 -10
crates/weaver-server/src/views/notebook.rs
··· 1 - use crate::{fetch, Route}; 1 + use crate::{components::NotebookCss, fetch, Route}; 2 2 use dioxus::prelude::*; 3 3 use jacquard::{ 4 - client::BasicClient, 5 - smol_str::SmolStr, 6 - types::{ident::AtIdentifier, tid::Tid}, 7 - CowStr, 4 + smol_str::{SmolStr, ToSmolStr}, 5 + types::ident::AtIdentifier, 8 6 }; 9 - use std::sync::Arc; 10 - 11 - const BLOG_CSS: Asset = asset!("/assets/styling/blog.css"); 12 7 13 8 /// The Blog page component that will be rendered when the current route is `[Route::Blog]` 14 9 /// ··· 16 11 /// re-run and the rendered HTML will be updated. 17 12 #[component] 18 13 pub fn Notebook(ident: AtIdentifier<'static>, book_title: SmolStr) -> Element { 19 - let fetcher = use_context::<fetch::CachedFetcher>(); 20 14 rsx! { 21 - document::Link { rel: "stylesheet", href: BLOG_CSS } 15 + NotebookCss { ident: ident.to_smolstr(), notebook: book_title } 22 16 Outlet::<Route> {} 23 17 } 24 18 }
-4
crates/weaver-server/src/views/notebookpage.rs
··· 2 2 use dioxus::prelude::*; 3 3 use jacquard::types::tid::Tid; 4 4 5 - const BLOG_CSS: Asset = asset!("/assets/styling/blog.css"); 6 - 7 5 /// The Blog page component that will be rendered when the current route is `[Route::Blog]` 8 6 /// 9 7 /// The component takes a `id` prop of type `i32` from the route enum. Whenever the id changes, the component function will be ··· 11 9 #[component] 12 10 pub fn NotebookPage(id: Tid, children: Element) -> Element { 13 11 rsx! { 14 - document::Link { rel: "stylesheet", href: BLOG_CSS } 15 - 16 12 div { 17 13 id: "blog", 18 14
+22 -6
lexicons/embed/video.json
··· 19 19 "type": "object", 20 20 "required": ["video"], 21 21 "properties": { 22 + "name": { 23 + "type": "string", 24 + "maxLength": 128 25 + }, 22 26 "video": { 23 27 "type": "blob", 24 28 "description": "The mp4 video file. May be up to 100mb, formerly limited to 50mb.", ··· 36 40 "maxGraphemes": 1000, 37 41 "maxLength": 10000 38 42 }, 39 - "aspectRatio": { 40 - "type": "ref", 41 - "ref": "app.bsky.embed.defs#aspectRatio" 43 + "dimensions": { 44 + "type": "union", 45 + "refs": [ 46 + "app.bsky.embed.defs#aspectRatio", 47 + "sh.weaver.embed.defs#percentSize", 48 + "sh.weaver.embed.defs#pixelSize" 49 + ] 42 50 } 43 51 } 44 52 }, ··· 69 77 "maxGraphemes": 1000, 70 78 "maxLength": 10000 71 79 }, 72 - "aspectRatio": { 73 - "type": "ref", 74 - "ref": "app.bsky.embed.defs#aspectRatio" 80 + "name": { 81 + "type": "string", 82 + "maxLength": 128 83 + }, 84 + "dimensions": { 85 + "type": "union", 86 + "refs": [ 87 + "app.bsky.embed.defs#aspectRatio", 88 + "sh.weaver.embed.defs#percentSize", 89 + "sh.weaver.embed.defs#pixelSize" 90 + ] 75 91 } 76 92 } 77 93 }
+4
lexicons/notebook/book.json
··· 26 26 "ref": "com.atproto.repo.strongRef" 27 27 } 28 28 }, 29 + "theme": { 30 + "type": "ref", 31 + "ref": "com.atproto.repo.strongRef" 32 + }, 29 33 "createdAt": { 30 34 "type": "string", 31 35 "format": "datetime",
+96
lexicons/notebook/theme.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.weaver.notebook.theme", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Theme for a notebook", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["colours", "fonts", "spacing", "codeTheme"], 12 + "properties": { 13 + "colours": { 14 + "type": "object", 15 + "required": ["background", "foreground", "primary", "secondary", "link", "link_hover"], 16 + "properties": { 17 + "background": { 18 + "type": "string" 19 + }, 20 + "foreground": { 21 + "type": "string" 22 + }, 23 + "primary": { 24 + "type": "string" 25 + }, 26 + "secondary": { 27 + "type": "string" 28 + }, 29 + "link": { 30 + "type": "string" 31 + }, 32 + "link_hover": { 33 + "type": "string" 34 + } 35 + } 36 + }, 37 + "fonts": { 38 + "type": "object", 39 + "required": ["body", "heading", "monospace"], 40 + "properties": { 41 + "body": { 42 + "type": "string" 43 + }, 44 + "heading": { 45 + "type": "string" 46 + }, 47 + "monospace": { 48 + "type": "string" 49 + } 50 + } 51 + }, 52 + "spacing": { 53 + "type": "object", 54 + "required": ["baseSize", "lineHeight", "scale"], 55 + "properties": { 56 + "baseSize": { 57 + "type": "string" 58 + }, 59 + "lineHeight": { 60 + "type": "string" 61 + }, 62 + "scale": { 63 + "type": "string" 64 + } 65 + } 66 + }, 67 + "codeTheme": { 68 + "type": "union", 69 + "refs": ["#codeThemeName", "#codeThemeFile"] 70 + } 71 + } 72 + } 73 + }, 74 + "codeThemeName": { 75 + "type": "string" 76 + }, 77 + "codeThemeFile": { 78 + "type": "object", 79 + "required": ["name", "did", "content"], 80 + "properties": { 81 + "name": { 82 + "type": "string" 83 + }, 84 + "did": { 85 + "type": "string", 86 + "format": "did" 87 + }, 88 + "content": { 89 + "type": "blob", 90 + "accept": ["*/*"], 91 + "maxSize": 20000 92 + } 93 + } 94 + } 95 + } 96 + }