diff --git a/Cargo.lock b/Cargo.lock index 5175161..8b085ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8754,6 +8754,7 @@ dependencies = [ "tokio-util", "unicode-normalization", "url", + "weaver-api", "weaver-common", "yaml-rust2", ] @@ -8762,6 +8763,7 @@ dependencies = [ name = "weaver-server" version = "0.1.0" dependencies = [ + "axum", "dashmap 6.1.0", "dioxus", "dioxus-primitives", @@ -8772,6 +8774,7 @@ dependencies = [ "moka", "weaver-api", "weaver-common", + "weaver-renderer", ] [[package]] diff --git a/crates/weaver-api/lexicons/sh_weaver_embed_video.json b/crates/weaver-api/lexicons/sh_weaver_embed_video.json index e534dfa..341f74d 100644 --- a/crates/weaver-api/lexicons/sh_weaver_embed_video.json +++ b/crates/weaver-api/lexicons/sh_weaver_embed_video.json @@ -50,10 +50,6 @@ "maxLength": 10000, "maxGraphemes": 1000 }, - "aspectRatio": { - "type": "ref", - "ref": "app.bsky.embed.defs#aspectRatio" - }, "captions": { "type": "array", "items": { @@ -62,6 +58,18 @@ }, "maxLength": 20 }, + "dimensions": { + "type": "union", + "refs": [ + "app.bsky.embed.defs#aspectRatio", + "sh.weaver.embed.defs#percentSize", + "sh.weaver.embed.defs#pixelSize" + ] + }, + "name": { + "type": "string", + "maxLength": 128 + }, "video": { "type": "blob", "description": "The mp4 video file. May be up to 100mb, formerly limited to 50mb.", @@ -84,14 +92,22 @@ "maxLength": 10000, "maxGraphemes": 1000 }, - "aspectRatio": { - "type": "ref", - "ref": "app.bsky.embed.defs#aspectRatio" - }, "cid": { "type": "string", "format": "cid" }, + "dimensions": { + "type": "union", + "refs": [ + "app.bsky.embed.defs#aspectRatio", + "sh.weaver.embed.defs#percentSize", + "sh.weaver.embed.defs#pixelSize" + ] + }, + "name": { + "type": "string", + "maxLength": 128 + }, "playlist": { "type": "string", "format": "uri" diff --git a/crates/weaver-api/lexicons/sh_weaver_notebook_book.json b/crates/weaver-api/lexicons/sh_weaver_notebook_book.json index bae28af..6038db2 100644 --- a/crates/weaver-api/lexicons/sh_weaver_notebook_book.json +++ b/crates/weaver-api/lexicons/sh_weaver_notebook_book.json @@ -36,6 +36,10 @@ "type": "ref", "ref": "sh.weaver.notebook.defs#tags" }, + "theme": { + "type": "ref", + "ref": "com.atproto.repo.strongRef" + }, "title": { "type": "ref", "ref": "sh.weaver.notebook.defs#title" diff --git a/crates/weaver-api/lexicons/sh_weaver_notebook_theme.json b/crates/weaver-api/lexicons/sh_weaver_notebook_theme.json new file mode 100644 index 0000000..c7fea76 --- /dev/null +++ b/crates/weaver-api/lexicons/sh_weaver_notebook_theme.json @@ -0,0 +1,125 @@ +{ + "lexicon": 1, + "id": "sh.weaver.notebook.theme", + "defs": { + "codeThemeFile": { + "type": "object", + "required": [ + "name", + "did", + "content" + ], + "properties": { + "content": { + "type": "blob", + "accept": [ + "*/*" + ], + "maxSize": 20000 + }, + "did": { + "type": "string", + "format": "did" + }, + "name": { + "type": "string" + } + } + }, + "codeThemeName": { + "type": "string" + }, + "main": { + "type": "record", + "description": "Theme for a notebook", + "key": "tid", + "record": { + "type": "object", + "required": [ + "colours", + "fonts", + "spacing", + "codeTheme" + ], + "properties": { + "codeTheme": { + "type": "union", + "refs": [ + "#codeThemeName", + "#codeThemeFile" + ] + }, + "colours": { + "type": "object", + "required": [ + "background", + "foreground", + "primary", + "secondary", + "link", + "link_hover" + ], + "properties": { + "background": { + "type": "string" + }, + "foreground": { + "type": "string" + }, + "link": { + "type": "string" + }, + "link_hover": { + "type": "string" + }, + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + } + } + }, + "fonts": { + "type": "object", + "required": [ + "body", + "heading", + "monospace" + ], + "properties": { + "body": { + "type": "string" + }, + "heading": { + "type": "string" + }, + "monospace": { + "type": "string" + } + } + }, + "spacing": { + "type": "object", + "required": [ + "baseSize", + "lineHeight", + "scale" + ], + "properties": { + "baseSize": { + "type": "string" + }, + "lineHeight": { + "type": "string" + }, + "scale": { + "type": "string" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/weaver-api/src/sh_weaver/embed/video.rs b/crates/weaver-api/src/sh_weaver/embed/video.rs index 76f0919..0012428 100644 --- a/crates/weaver-api/src/sh_weaver/embed/video.rs +++ b/crates/weaver-api/src/sh_weaver/embed/video.rs @@ -272,17 +272,6 @@ fn lexicon_doc_sh_weaver_embed_video() -> ::jacquard_lexicon::lexicon::LexiconDo known_values: None, }), ); - map.insert( - ::jacquard_common::smol_str::SmolStr::new_static( - "aspectRatio", - ), - ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { - description: None, - r#ref: ::jacquard_common::CowStr::new_static( - "app.bsky.embed.defs#aspectRatio", - ), - }), - ); map.insert( ::jacquard_common::smol_str::SmolStr::new_static("captions"), ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { @@ -295,6 +284,35 @@ fn lexicon_doc_sh_weaver_embed_video() -> ::jacquard_lexicon::lexicon::LexiconDo max_length: Some(20usize), }), ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "dimensions", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: None, + refs: vec![ + ::jacquard_common::CowStr::new_static("app.bsky.embed.defs#aspectRatio"), + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#percentSize"), + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#pixelSize") + ], + closed: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("name"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: Some(128usize), + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); map.insert( ::jacquard_common::smol_str::SmolStr::new_static("video"), ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { @@ -336,27 +354,45 @@ fn lexicon_doc_sh_weaver_embed_video() -> ::jacquard_lexicon::lexicon::LexiconDo known_values: None, }), ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("cid"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Cid, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); map.insert( ::jacquard_common::smol_str::SmolStr::new_static( - "aspectRatio", + "dimensions", ), - ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { description: None, - r#ref: ::jacquard_common::CowStr::new_static( - "app.bsky.embed.defs#aspectRatio", - ), + refs: vec![ + ::jacquard_common::CowStr::new_static("app.bsky.embed.defs#aspectRatio"), + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#percentSize"), + ::jacquard_common::CowStr::new_static("sh.weaver.embed.defs#pixelSize") + ], + closed: None, }), ); map.insert( - ::jacquard_common::smol_str::SmolStr::new_static("cid"), + ::jacquard_common::smol_str::SmolStr::new_static("name"), ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { description: None, - format: Some( - ::jacquard_lexicon::lexicon::LexStringFormat::Cid, - ), + format: None, default: None, min_length: None, - max_length: None, + max_length: Some(128usize), min_graphemes: None, max_graphemes: None, r#enum: None, @@ -582,10 +618,13 @@ pub struct Video<'a> { pub alt: Option>, #[serde(skip_serializing_if = "std::option::Option::is_none")] #[serde(borrow)] - pub aspect_ratio: Option>, + pub captions: Option>>, #[serde(skip_serializing_if = "std::option::Option::is_none")] #[serde(borrow)] - pub captions: Option>>, + pub dimensions: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub name: Option>, /// The mp4 video file. May be up to 100mb, formerly limited to 50mb. #[serde(borrow)] pub video: jacquard_common::types::blob::BlobRef<'a>, @@ -628,8 +667,9 @@ pub struct VideoBuilder<'a, S: video_state::State> { _phantom_state: ::core::marker::PhantomData S>, __unsafe_private_named: ( ::core::option::Option>, - ::core::option::Option>, ::core::option::Option>>, + ::core::option::Option>, + ::core::option::Option>, ::core::option::Option>, ), _phantom: ::core::marker::PhantomData<&'a ()>, @@ -647,7 +687,7 @@ impl<'a> VideoBuilder<'a, video_state::Empty> { pub fn new() -> Self { VideoBuilder { _phantom_state: ::core::marker::PhantomData, - __unsafe_private_named: (None, None, None, None), + __unsafe_private_named: (None, None, None, None, None), _phantom: ::core::marker::PhantomData, } } @@ -667,18 +707,18 @@ impl<'a, S: video_state::State> VideoBuilder<'a, S> { } impl<'a, S: video_state::State> VideoBuilder<'a, S> { - /// Set the `aspectRatio` field (optional) - pub fn aspect_ratio( + /// Set the `captions` field (optional) + pub fn captions( mut self, - value: impl Into>>, + value: impl Into>>>, ) -> Self { self.__unsafe_private_named.1 = value.into(); self } - /// Set the `aspectRatio` field to an Option value (optional) - pub fn maybe_aspect_ratio( + /// Set the `captions` field to an Option value (optional) + pub fn maybe_captions( mut self, - value: Option>, + value: Option>>, ) -> Self { self.__unsafe_private_named.1 = value; self @@ -686,20 +726,30 @@ impl<'a, S: video_state::State> VideoBuilder<'a, S> { } impl<'a, S: video_state::State> VideoBuilder<'a, S> { - /// Set the `captions` field (optional) - pub fn captions( - mut self, - value: impl Into>>>, - ) -> Self { + /// Set the `dimensions` field (optional) + pub fn dimensions(mut self, value: impl Into>>) -> Self { self.__unsafe_private_named.2 = value.into(); self } - /// Set the `captions` field to an Option value (optional) - pub fn maybe_captions( + /// Set the `dimensions` field to an Option value (optional) + pub fn maybe_dimensions(mut self, value: Option>) -> Self { + self.__unsafe_private_named.2 = value; + self + } +} + +impl<'a, S: video_state::State> VideoBuilder<'a, S> { + /// Set the `name` field (optional) + pub fn name( mut self, - value: Option>>, + value: impl Into>>, ) -> Self { - self.__unsafe_private_named.2 = value; + self.__unsafe_private_named.3 = value.into(); + self + } + /// Set the `name` field to an Option value (optional) + pub fn maybe_name(mut self, value: Option>) -> Self { + self.__unsafe_private_named.3 = value; self } } @@ -714,7 +764,7 @@ where mut self, value: impl Into>, ) -> VideoBuilder<'a, video_state::SetVideo> { - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); VideoBuilder { _phantom_state: ::core::marker::PhantomData, __unsafe_private_named: self.__unsafe_private_named, @@ -732,9 +782,10 @@ where pub fn build(self) -> Video<'a> { Video { alt: self.__unsafe_private_named.0, - aspect_ratio: self.__unsafe_private_named.1, - captions: self.__unsafe_private_named.2, - video: self.__unsafe_private_named.3.unwrap(), + captions: self.__unsafe_private_named.1, + dimensions: self.__unsafe_private_named.2, + name: self.__unsafe_private_named.3, + video: self.__unsafe_private_named.4.unwrap(), extra_data: Default::default(), } } @@ -748,14 +799,36 @@ where ) -> Video<'a> { Video { alt: self.__unsafe_private_named.0, - aspect_ratio: self.__unsafe_private_named.1, - captions: self.__unsafe_private_named.2, - video: self.__unsafe_private_named.3.unwrap(), + captions: self.__unsafe_private_named.1, + dimensions: self.__unsafe_private_named.2, + name: self.__unsafe_private_named.3, + video: self.__unsafe_private_named.4.unwrap(), extra_data: Some(extra_data), } } } +#[jacquard_derive::open_union] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] +#[serde(tag = "$type")] +#[serde(bound(deserialize = "'de: 'a"))] +pub enum VideoDimensions<'a> { + #[serde(rename = "app.bsky.embed.defs#aspectRatio")] + AspectRatio(Box>), + #[serde(rename = "sh.weaver.embed.defs#percentSize")] + PercentSize(Box>), + #[serde(rename = "sh.weaver.embed.defs#pixelSize")] + PixelSize(Box>), +} + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Video<'a> { fn nsid() -> &'static str { "sh.weaver.embed.video" @@ -811,6 +884,18 @@ impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Video<'a> { }); } } + if let Some(ref value) = self.name { + #[allow(unused_comparisons)] + if ::len(value.as_ref()) > 128usize { + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { + path: ::jacquard_lexicon::validation::ValidationPath::from_field( + "name", + ), + max: 128usize, + actual: ::len(value.as_ref()), + }); + } + } Ok(()) } } @@ -830,11 +915,14 @@ pub struct View<'a> { #[serde(skip_serializing_if = "std::option::Option::is_none")] #[serde(borrow)] pub alt: Option>, + #[serde(borrow)] + pub cid: jacquard_common::types::string::Cid<'a>, #[serde(skip_serializing_if = "std::option::Option::is_none")] #[serde(borrow)] - pub aspect_ratio: Option>, + pub dimensions: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] #[serde(borrow)] - pub cid: jacquard_common::types::string::Cid<'a>, + pub name: Option>, #[serde(borrow)] pub playlist: jacquard_common::types::string::Uri<'a>, #[serde(skip_serializing_if = "std::option::Option::is_none")] @@ -891,8 +979,9 @@ pub struct ViewBuilder<'a, S: view_state::State> { _phantom_state: ::core::marker::PhantomData S>, __unsafe_private_named: ( ::core::option::Option>, - ::core::option::Option>, ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, ::core::option::Option>, ::core::option::Option>, ), @@ -911,7 +1000,7 @@ impl<'a> ViewBuilder<'a, view_state::Empty> { pub fn new() -> Self { ViewBuilder { _phantom_state: ::core::marker::PhantomData, - __unsafe_private_named: (None, None, None, None, None), + __unsafe_private_named: (None, None, None, None, None, None), _phantom: ::core::marker::PhantomData, } } @@ -930,25 +1019,6 @@ impl<'a, S: view_state::State> ViewBuilder<'a, S> { } } -impl<'a, S: view_state::State> ViewBuilder<'a, S> { - /// Set the `aspectRatio` field (optional) - pub fn aspect_ratio( - mut self, - value: impl Into>>, - ) -> Self { - self.__unsafe_private_named.1 = value.into(); - self - } - /// Set the `aspectRatio` field to an Option value (optional) - pub fn maybe_aspect_ratio( - mut self, - value: Option>, - ) -> Self { - self.__unsafe_private_named.1 = value; - self - } -} - impl<'a, S> ViewBuilder<'a, S> where S: view_state::State, @@ -959,7 +1029,7 @@ where mut self, value: impl Into>, ) -> ViewBuilder<'a, view_state::SetCid> { - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); ViewBuilder { _phantom_state: ::core::marker::PhantomData, __unsafe_private_named: self.__unsafe_private_named, @@ -968,6 +1038,35 @@ where } } +impl<'a, S: view_state::State> ViewBuilder<'a, S> { + /// Set the `dimensions` field (optional) + pub fn dimensions(mut self, value: impl Into>>) -> Self { + self.__unsafe_private_named.2 = value.into(); + self + } + /// Set the `dimensions` field to an Option value (optional) + pub fn maybe_dimensions(mut self, value: Option>) -> Self { + self.__unsafe_private_named.2 = value; + self + } +} + +impl<'a, S: view_state::State> ViewBuilder<'a, S> { + /// Set the `name` field (optional) + pub fn name( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.3 = value.into(); + self + } + /// Set the `name` field to an Option value (optional) + pub fn maybe_name(mut self, value: Option>) -> Self { + self.__unsafe_private_named.3 = value; + self + } +} + impl<'a, S> ViewBuilder<'a, S> where S: view_state::State, @@ -978,7 +1077,7 @@ where mut self, value: impl Into>, ) -> ViewBuilder<'a, view_state::SetPlaylist> { - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); + self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); ViewBuilder { _phantom_state: ::core::marker::PhantomData, __unsafe_private_named: self.__unsafe_private_named, @@ -993,7 +1092,7 @@ impl<'a, S: view_state::State> ViewBuilder<'a, S> { mut self, value: impl Into>>, ) -> Self { - self.__unsafe_private_named.4 = value.into(); + self.__unsafe_private_named.5 = value.into(); self } /// Set the `thumbnail` field to an Option value (optional) @@ -1001,7 +1100,7 @@ impl<'a, S: view_state::State> ViewBuilder<'a, S> { mut self, value: Option>, ) -> Self { - self.__unsafe_private_named.4 = value; + self.__unsafe_private_named.5 = value; self } } @@ -1016,10 +1115,11 @@ where pub fn build(self) -> View<'a> { View { alt: self.__unsafe_private_named.0, - aspect_ratio: self.__unsafe_private_named.1, - cid: self.__unsafe_private_named.2.unwrap(), - playlist: self.__unsafe_private_named.3.unwrap(), - thumbnail: self.__unsafe_private_named.4, + cid: self.__unsafe_private_named.1.unwrap(), + dimensions: self.__unsafe_private_named.2, + name: self.__unsafe_private_named.3, + playlist: self.__unsafe_private_named.4.unwrap(), + thumbnail: self.__unsafe_private_named.5, extra_data: Default::default(), } } @@ -1033,15 +1133,37 @@ where ) -> View<'a> { View { alt: self.__unsafe_private_named.0, - aspect_ratio: self.__unsafe_private_named.1, - cid: self.__unsafe_private_named.2.unwrap(), - playlist: self.__unsafe_private_named.3.unwrap(), - thumbnail: self.__unsafe_private_named.4, + cid: self.__unsafe_private_named.1.unwrap(), + dimensions: self.__unsafe_private_named.2, + name: self.__unsafe_private_named.3, + playlist: self.__unsafe_private_named.4.unwrap(), + thumbnail: self.__unsafe_private_named.5, extra_data: Some(extra_data), } } } +#[jacquard_derive::open_union] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] +#[serde(tag = "$type")] +#[serde(bound(deserialize = "'de: 'a"))] +pub enum ViewDimensions<'a> { + #[serde(rename = "app.bsky.embed.defs#aspectRatio")] + AspectRatio(Box>), + #[serde(rename = "sh.weaver.embed.defs#percentSize")] + PercentSize(Box>), + #[serde(rename = "sh.weaver.embed.defs#pixelSize")] + PixelSize(Box>), +} + impl<'a> ::jacquard_lexicon::schema::LexiconSchema for View<'a> { fn nsid() -> &'static str { "sh.weaver.embed.video" @@ -1085,6 +1207,18 @@ impl<'a> ::jacquard_lexicon::schema::LexiconSchema for View<'a> { } } } + if let Some(ref value) = self.name { + #[allow(unused_comparisons)] + if ::len(value.as_ref()) > 128usize { + return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { + path: ::jacquard_lexicon::validation::ValidationPath::from_field( + "name", + ), + max: 128usize, + actual: ::len(value.as_ref()), + }); + } + } Ok(()) } } \ No newline at end of file diff --git a/crates/weaver-api/src/sh_weaver/notebook.rs b/crates/weaver-api/src/sh_weaver/notebook.rs index 1be79d5..9e0e32f 100644 --- a/crates/weaver-api/src/sh_weaver/notebook.rs +++ b/crates/weaver-api/src/sh_weaver/notebook.rs @@ -10,6 +10,7 @@ pub mod book; pub mod chapter; pub mod entry; pub mod page; +pub mod theme; #[jacquard_derive::lexicon] #[derive( diff --git a/crates/weaver-api/src/sh_weaver/notebook/book.rs b/crates/weaver-api/src/sh_weaver/notebook/book.rs index 994aec6..89d9a7b 100644 --- a/crates/weaver-api/src/sh_weaver/notebook/book.rs +++ b/crates/weaver-api/src/sh_weaver/notebook/book.rs @@ -30,6 +30,9 @@ pub struct Book<'a> { pub tags: Option>, #[serde(skip_serializing_if = "std::option::Option::is_none")] #[serde(borrow)] + pub theme: Option>, + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] pub title: Option>, } @@ -85,6 +88,7 @@ pub struct BookBuilder<'a, S: book_state::State> { ::core::option::Option, ::core::option::Option>>, ::core::option::Option>, + ::core::option::Option>, ::core::option::Option>, ), _phantom: ::core::marker::PhantomData<&'a ()>, @@ -102,7 +106,7 @@ impl<'a> BookBuilder<'a, book_state::Empty> { pub fn new() -> Self { BookBuilder { _phantom_state: ::core::marker::PhantomData, - __unsafe_private_named: (None, None, None, None, None), + __unsafe_private_named: (None, None, None, None, None, None), _phantom: ::core::marker::PhantomData, } } @@ -184,13 +188,32 @@ impl<'a, S: book_state::State> BookBuilder<'a, S> { } } +impl<'a, S: book_state::State> BookBuilder<'a, S> { + /// Set the `theme` field (optional) + pub fn theme( + mut self, + value: impl Into>>, + ) -> Self { + self.__unsafe_private_named.4 = value.into(); + self + } + /// Set the `theme` field to an Option value (optional) + pub fn maybe_theme( + mut self, + value: Option>, + ) -> Self { + self.__unsafe_private_named.4 = value; + self + } +} + impl<'a, S: book_state::State> BookBuilder<'a, S> { /// Set the `title` field (optional) pub fn title( mut self, value: impl Into>>, ) -> Self { - self.__unsafe_private_named.4 = value.into(); + self.__unsafe_private_named.5 = value.into(); self } /// Set the `title` field to an Option value (optional) @@ -198,7 +221,7 @@ impl<'a, S: book_state::State> BookBuilder<'a, S> { mut self, value: Option>, ) -> Self { - self.__unsafe_private_named.4 = value; + self.__unsafe_private_named.5 = value; self } } @@ -216,7 +239,8 @@ where created_at: self.__unsafe_private_named.1, entry_list: self.__unsafe_private_named.2.unwrap(), tags: self.__unsafe_private_named.3, - title: self.__unsafe_private_named.4, + theme: self.__unsafe_private_named.4, + title: self.__unsafe_private_named.5, extra_data: Default::default(), } } @@ -233,7 +257,8 @@ where created_at: self.__unsafe_private_named.1, entry_list: self.__unsafe_private_named.2.unwrap(), tags: self.__unsafe_private_named.3, - title: self.__unsafe_private_named.4, + theme: self.__unsafe_private_named.4, + title: self.__unsafe_private_named.5, extra_data: Some(extra_data), } } @@ -410,6 +435,15 @@ fn lexicon_doc_sh_weaver_notebook_book() -> ::jacquard_lexicon::lexicon::Lexicon ), }), ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("theme"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { + description: None, + r#ref: ::jacquard_common::CowStr::new_static( + "com.atproto.repo.strongRef", + ), + }), + ); map.insert( ::jacquard_common::smol_str::SmolStr::new_static("title"), ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { diff --git a/crates/weaver-api/src/sh_weaver/notebook/theme.rs b/crates/weaver-api/src/sh_weaver/notebook/theme.rs new file mode 100644 index 0000000..ed54c2e --- /dev/null +++ b/crates/weaver-api/src/sh_weaver/notebook/theme.rs @@ -0,0 +1,1059 @@ +// @generated by jacquard-lexicon. DO NOT EDIT. +// +// Lexicon: sh.weaver.notebook.theme +// +// This file was automatically generated from Lexicon schemas. +// Any manual changes will be overwritten on the next regeneration. + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] +#[serde(rename_all = "camelCase")] +pub struct CodeThemeFile<'a> { + #[serde(borrow)] + pub content: jacquard_common::types::blob::BlobRef<'a>, + #[serde(borrow)] + pub did: jacquard_common::types::string::Did<'a>, + #[serde(borrow)] + pub name: jacquard_common::CowStr<'a>, +} + +pub mod code_theme_file_state { + + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Name; + type Did; + type Content; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Name = Unset; + type Did = Unset; + type Content = Unset; + } + ///State transition - sets the `name` field to Set + pub struct SetName(PhantomData S>); + impl sealed::Sealed for SetName {} + impl State for SetName { + type Name = Set; + type Did = S::Did; + type Content = S::Content; + } + ///State transition - sets the `did` field to Set + pub struct SetDid(PhantomData S>); + impl sealed::Sealed for SetDid {} + impl State for SetDid { + type Name = S::Name; + type Did = Set; + type Content = S::Content; + } + ///State transition - sets the `content` field to Set + pub struct SetContent(PhantomData S>); + impl sealed::Sealed for SetContent {} + impl State for SetContent { + type Name = S::Name; + type Did = S::Did; + type Content = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `name` field + pub struct name(()); + ///Marker type for the `did` field + pub struct did(()); + ///Marker type for the `content` field + pub struct content(()); + } +} + +/// Builder for constructing an instance of this type +pub struct CodeThemeFileBuilder<'a, S: code_theme_file_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> CodeThemeFile<'a> { + /// Create a new builder for this type + pub fn new() -> CodeThemeFileBuilder<'a, code_theme_file_state::Empty> { + CodeThemeFileBuilder::new() + } +} + +impl<'a> CodeThemeFileBuilder<'a, code_theme_file_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + CodeThemeFileBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> CodeThemeFileBuilder<'a, S> +where + S: code_theme_file_state::State, + S::Content: code_theme_file_state::IsUnset, +{ + /// Set the `content` field (required) + pub fn content( + mut self, + value: impl Into>, + ) -> CodeThemeFileBuilder<'a, code_theme_file_state::SetContent> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + CodeThemeFileBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> CodeThemeFileBuilder<'a, S> +where + S: code_theme_file_state::State, + S::Did: code_theme_file_state::IsUnset, +{ + /// Set the `did` field (required) + pub fn did( + mut self, + value: impl Into>, + ) -> CodeThemeFileBuilder<'a, code_theme_file_state::SetDid> { + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); + CodeThemeFileBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> CodeThemeFileBuilder<'a, S> +where + S: code_theme_file_state::State, + S::Name: code_theme_file_state::IsUnset, +{ + /// Set the `name` field (required) + pub fn name( + mut self, + value: impl Into>, + ) -> CodeThemeFileBuilder<'a, code_theme_file_state::SetName> { + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); + CodeThemeFileBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> CodeThemeFileBuilder<'a, S> +where + S: code_theme_file_state::State, + S::Name: code_theme_file_state::IsSet, + S::Did: code_theme_file_state::IsSet, + S::Content: code_theme_file_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> CodeThemeFile<'a> { + CodeThemeFile { + content: self.__unsafe_private_named.0.unwrap(), + did: self.__unsafe_private_named.1.unwrap(), + name: self.__unsafe_private_named.2.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> CodeThemeFile<'a> { + CodeThemeFile { + content: self.__unsafe_private_named.0.unwrap(), + did: self.__unsafe_private_named.1.unwrap(), + name: self.__unsafe_private_named.2.unwrap(), + extra_data: Some(extra_data), + } + } +} + +fn lexicon_doc_sh_weaver_notebook_theme() -> ::jacquard_lexicon::lexicon::LexiconDoc< + 'static, +> { + ::jacquard_lexicon::lexicon::LexiconDoc { + lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, + id: ::jacquard_common::CowStr::new_static("sh.weaver.notebook.theme"), + revision: None, + description: None, + defs: { + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("codeThemeFile"), + ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("name"), + ::jacquard_common::smol_str::SmolStr::new_static("did"), + ::jacquard_common::smol_str::SmolStr::new_static("content") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("content"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { + description: None, + accept: None, + max_size: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("did"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: Some( + ::jacquard_lexicon::lexicon::LexStringFormat::Did, + ), + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("name"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("codeThemeName"), + ::jacquard_lexicon::lexicon::LexUserType::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("main"), + ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { + description: Some( + ::jacquard_common::CowStr::new_static("Theme for a notebook"), + ), + key: Some(::jacquard_common::CowStr::new_static("tid")), + record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("colours"), + ::jacquard_common::smol_str::SmolStr::new_static("fonts"), + ::jacquard_common::smol_str::SmolStr::new_static("spacing"), + ::jacquard_common::smol_str::SmolStr::new_static("codeTheme") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "codeTheme", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { + description: None, + refs: vec![ + ::jacquard_common::CowStr::new_static("#codeThemeName"), + ::jacquard_common::CowStr::new_static("#codeThemeFile") + ], + closed: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("colours"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("background"), + ::jacquard_common::smol_str::SmolStr::new_static("foreground"), + ::jacquard_common::smol_str::SmolStr::new_static("primary"), + ::jacquard_common::smol_str::SmolStr::new_static("secondary"), + ::jacquard_common::smol_str::SmolStr::new_static("link"), + ::jacquard_common::smol_str::SmolStr::new_static("link_hover") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "background", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "foreground", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("link"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "link_hover", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("primary"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "secondary", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("fonts"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("body"), + ::jacquard_common::smol_str::SmolStr::new_static("heading"), + ::jacquard_common::smol_str::SmolStr::new_static("monospace") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("body"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("heading"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "monospace", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("spacing"), + ::jacquard_lexicon::lexicon::LexObjectProperty::Object(::jacquard_lexicon::lexicon::LexObject { + description: None, + required: Some( + vec![ + ::jacquard_common::smol_str::SmolStr::new_static("baseSize"), + ::jacquard_common::smol_str::SmolStr::new_static("lineHeight"), + ::jacquard_common::smol_str::SmolStr::new_static("scale") + ], + ), + nullable: None, + properties: { + #[allow(unused_mut)] + let mut map = ::std::collections::BTreeMap::new(); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "baseSize", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static( + "lineHeight", + ), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map.insert( + ::jacquard_common::smol_str::SmolStr::new_static("scale"), + ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { + description: None, + format: None, + default: None, + min_length: None, + max_length: None, + min_graphemes: None, + max_graphemes: None, + r#enum: None, + r#const: None, + known_values: None, + }), + ); + map + }, + }), + ); + map + }, + }), + }), + ); + map + }, + } +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CodeThemeFile<'a> { + fn nsid() -> &'static str { + "sh.weaver.notebook.theme" + } + fn def_name() -> &'static str { + "codeThemeFile" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_sh_weaver_notebook_theme() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} + +pub type CodeThemeName<'a> = jacquard_common::CowStr<'a>; +/// Theme for a notebook +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] +#[serde(rename_all = "camelCase")] +pub struct Theme<'a> { + #[serde(borrow)] + pub code_theme: ThemeCodeTheme<'a>, + #[serde(borrow)] + pub colours: ThemeColours<'a>, + #[serde(borrow)] + pub fonts: ThemeFonts<'a>, + #[serde(borrow)] + pub spacing: ThemeSpacing<'a>, +} + +pub mod theme_state { + + pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; + #[allow(unused)] + use ::core::marker::PhantomData; + mod sealed { + pub trait Sealed {} + } + /// State trait tracking which required fields have been set + pub trait State: sealed::Sealed { + type Colours; + type Fonts; + type Spacing; + type CodeTheme; + } + /// Empty state - all required fields are unset + pub struct Empty(()); + impl sealed::Sealed for Empty {} + impl State for Empty { + type Colours = Unset; + type Fonts = Unset; + type Spacing = Unset; + type CodeTheme = Unset; + } + ///State transition - sets the `colours` field to Set + pub struct SetColours(PhantomData S>); + impl sealed::Sealed for SetColours {} + impl State for SetColours { + type Colours = Set; + type Fonts = S::Fonts; + type Spacing = S::Spacing; + type CodeTheme = S::CodeTheme; + } + ///State transition - sets the `fonts` field to Set + pub struct SetFonts(PhantomData S>); + impl sealed::Sealed for SetFonts {} + impl State for SetFonts { + type Colours = S::Colours; + type Fonts = Set; + type Spacing = S::Spacing; + type CodeTheme = S::CodeTheme; + } + ///State transition - sets the `spacing` field to Set + pub struct SetSpacing(PhantomData S>); + impl sealed::Sealed for SetSpacing {} + impl State for SetSpacing { + type Colours = S::Colours; + type Fonts = S::Fonts; + type Spacing = Set; + type CodeTheme = S::CodeTheme; + } + ///State transition - sets the `code_theme` field to Set + pub struct SetCodeTheme(PhantomData S>); + impl sealed::Sealed for SetCodeTheme {} + impl State for SetCodeTheme { + type Colours = S::Colours; + type Fonts = S::Fonts; + type Spacing = S::Spacing; + type CodeTheme = Set; + } + /// Marker types for field names + #[allow(non_camel_case_types)] + pub mod members { + ///Marker type for the `colours` field + pub struct colours(()); + ///Marker type for the `fonts` field + pub struct fonts(()); + ///Marker type for the `spacing` field + pub struct spacing(()); + ///Marker type for the `code_theme` field + pub struct code_theme(()); + } +} + +/// Builder for constructing an instance of this type +pub struct ThemeBuilder<'a, S: theme_state::State> { + _phantom_state: ::core::marker::PhantomData S>, + __unsafe_private_named: ( + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ::core::option::Option>, + ), + _phantom: ::core::marker::PhantomData<&'a ()>, +} + +impl<'a> Theme<'a> { + /// Create a new builder for this type + pub fn new() -> ThemeBuilder<'a, theme_state::Empty> { + ThemeBuilder::new() + } +} + +impl<'a> ThemeBuilder<'a, theme_state::Empty> { + /// Create a new builder with all fields unset + pub fn new() -> Self { + ThemeBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: (None, None, None, None), + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> ThemeBuilder<'a, S> +where + S: theme_state::State, + S::CodeTheme: theme_state::IsUnset, +{ + /// Set the `codeTheme` field (required) + pub fn code_theme( + mut self, + value: impl Into>, + ) -> ThemeBuilder<'a, theme_state::SetCodeTheme> { + self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); + ThemeBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> ThemeBuilder<'a, S> +where + S: theme_state::State, + S::Colours: theme_state::IsUnset, +{ + /// Set the `colours` field (required) + pub fn colours( + mut self, + value: impl Into>, + ) -> ThemeBuilder<'a, theme_state::SetColours> { + self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); + ThemeBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> ThemeBuilder<'a, S> +where + S: theme_state::State, + S::Fonts: theme_state::IsUnset, +{ + /// Set the `fonts` field (required) + pub fn fonts( + mut self, + value: impl Into>, + ) -> ThemeBuilder<'a, theme_state::SetFonts> { + self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); + ThemeBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> ThemeBuilder<'a, S> +where + S: theme_state::State, + S::Spacing: theme_state::IsUnset, +{ + /// Set the `spacing` field (required) + pub fn spacing( + mut self, + value: impl Into>, + ) -> ThemeBuilder<'a, theme_state::SetSpacing> { + self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); + ThemeBuilder { + _phantom_state: ::core::marker::PhantomData, + __unsafe_private_named: self.__unsafe_private_named, + _phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, S> ThemeBuilder<'a, S> +where + S: theme_state::State, + S::Colours: theme_state::IsSet, + S::Fonts: theme_state::IsSet, + S::Spacing: theme_state::IsSet, + S::CodeTheme: theme_state::IsSet, +{ + /// Build the final struct + pub fn build(self) -> Theme<'a> { + Theme { + code_theme: self.__unsafe_private_named.0.unwrap(), + colours: self.__unsafe_private_named.1.unwrap(), + fonts: self.__unsafe_private_named.2.unwrap(), + spacing: self.__unsafe_private_named.3.unwrap(), + extra_data: Default::default(), + } + } + /// Build the final struct with custom extra_data + pub fn build_with_data( + self, + extra_data: std::collections::BTreeMap< + jacquard_common::smol_str::SmolStr, + jacquard_common::types::value::Data<'a>, + >, + ) -> Theme<'a> { + Theme { + code_theme: self.__unsafe_private_named.0.unwrap(), + colours: self.__unsafe_private_named.1.unwrap(), + fonts: self.__unsafe_private_named.2.unwrap(), + spacing: self.__unsafe_private_named.3.unwrap(), + extra_data: Some(extra_data), + } + } +} + +impl<'a> Theme<'a> { + pub fn uri( + uri: impl Into>, + ) -> Result< + jacquard_common::types::uri::RecordUri<'a, ThemeRecord>, + jacquard_common::types::uri::UriError, + > { + jacquard_common::types::uri::RecordUri::try_from_uri( + jacquard_common::types::string::AtUri::new_cow(uri.into())?, + ) + } +} + +#[jacquard_derive::open_union] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] +#[serde(tag = "$type")] +#[serde(bound(deserialize = "'de: 'a"))] +pub enum ThemeCodeTheme<'a> { + #[serde(rename = "sh.weaver.notebook.theme#codeThemeName")] + CodeThemeName(Box>), + #[serde(rename = "sh.weaver.notebook.theme#codeThemeFile")] + CodeThemeFile(Box>), +} + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic, + Default +)] +#[serde(rename_all = "camelCase")] +pub struct ThemeColours<'a> { + #[serde(borrow)] + pub background: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub foreground: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub link: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub link_hover: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub primary: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub secondary: jacquard_common::CowStr<'a>, +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeColours<'a> { + fn nsid() -> &'static str { + "sh.weaver.notebook.theme" + } + fn def_name() -> &'static str { + "ThemeColours" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_sh_weaver_notebook_theme() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic, + Default +)] +#[serde(rename_all = "camelCase")] +pub struct ThemeFonts<'a> { + #[serde(borrow)] + pub body: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub heading: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub monospace: jacquard_common::CowStr<'a>, +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeFonts<'a> { + fn nsid() -> &'static str { + "sh.weaver.notebook.theme" + } + fn def_name() -> &'static str { + "ThemeFonts" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_sh_weaver_notebook_theme() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} + +#[jacquard_derive::lexicon] +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic, + Default +)] +#[serde(rename_all = "camelCase")] +pub struct ThemeSpacing<'a> { + #[serde(borrow)] + pub base_size: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub line_height: jacquard_common::CowStr<'a>, + #[serde(borrow)] + pub scale: jacquard_common::CowStr<'a>, +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for ThemeSpacing<'a> { + fn nsid() -> &'static str { + "sh.weaver.notebook.theme" + } + fn def_name() -> &'static str { + "ThemeSpacing" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_sh_weaver_notebook_theme() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} + +/// Typed wrapper for GetRecord response with this collection's record type. +#[derive( + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + PartialEq, + Eq, + jacquard_derive::IntoStatic +)] +#[serde(rename_all = "camelCase")] +pub struct ThemeGetRecordOutput<'a> { + #[serde(skip_serializing_if = "std::option::Option::is_none")] + #[serde(borrow)] + pub cid: std::option::Option>, + #[serde(borrow)] + pub uri: jacquard_common::types::string::AtUri<'a>, + #[serde(borrow)] + pub value: Theme<'a>, +} + +impl From> for Theme<'_> { + fn from(output: ThemeGetRecordOutput<'_>) -> Self { + use jacquard_common::IntoStatic; + output.value.into_static() + } +} + +impl jacquard_common::types::collection::Collection for Theme<'_> { + const NSID: &'static str = "sh.weaver.notebook.theme"; + type Record = ThemeRecord; +} + +/// Marker type for deserializing records from this collection. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ThemeRecord; +impl jacquard_common::xrpc::XrpcResp for ThemeRecord { + const NSID: &'static str = "sh.weaver.notebook.theme"; + const ENCODING: &'static str = "application/json"; + type Output<'de> = ThemeGetRecordOutput<'de>; + type Err<'de> = jacquard_common::types::collection::RecordError<'de>; +} + +impl jacquard_common::types::collection::Collection for ThemeRecord { + const NSID: &'static str = "sh.weaver.notebook.theme"; + type Record = ThemeRecord; +} + +impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Theme<'a> { + fn nsid() -> &'static str { + "sh.weaver.notebook.theme" + } + fn def_name() -> &'static str { + "main" + } + fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { + lexicon_doc_sh_weaver_notebook_theme() + } + fn validate( + &self, + ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { + Ok(()) + } +} \ No newline at end of file diff --git a/crates/weaver-renderer/Cargo.toml b/crates/weaver-renderer/Cargo.toml index 41cb948..34b9e39 100644 --- a/crates/weaver-renderer/Cargo.toml +++ b/crates/weaver-renderer/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] n0-future.workspace = true weaver-common = { path = "../weaver-common" } +weaver-api = { path = "../weaver-api" } markdown-weaver = { workspace = true } compact_string = "0.1.0" http = "1.3.1" diff --git a/crates/weaver-renderer/src/css.rs b/crates/weaver-renderer/src/css.rs index 658a355..b721cf2 100644 --- a/crates/weaver-renderer/src/css.rs +++ b/crates/weaver-renderer/src/css.rs @@ -4,6 +4,11 @@ use std::io::Cursor; use syntect::highlighting::{Theme as SyntectTheme, ThemeSet}; use syntect::html::{ClassStyle, css_for_theme_with_class_style}; use syntect::parsing::SyntaxSet; +use weaver_api::com_atproto::sync::get_blob::GetBlob; +use weaver_api::sh_weaver::notebook::theme::ThemeCodeTheme; +use weaver_common::jacquard::client::BasicClient; +use weaver_common::jacquard::prelude::*; +use weaver_common::jacquard::xrpc::XrpcExt; // Embed rose-pine themes at compile time const ROSE_PINE_THEME: &str = include_str!("../themes/rose-pine.tmTheme"); @@ -187,62 +192,76 @@ hr {{ margin: 2rem 0; }} "#, - theme.colors.background, - theme.colors.foreground, - theme.colors.link, - theme.colors.link_hover, - theme.colors.primary, - theme.colors.secondary, + theme.colours.background, + theme.colours.foreground, + theme.colours.link, + theme.colours.link_hover, + theme.colours.primary, + theme.colours.secondary, theme.fonts.body, theme.fonts.heading, theme.fonts.monospace, - theme.spacing.base_font_size, + theme.spacing.base_size, theme.spacing.line_height, theme.spacing.scale, ) } -pub fn generate_syntax_css(theme: &Theme, _syntax_set: &SyntaxSet) -> miette::Result { - let syntect_theme = if let Some(custom_path) = &theme.custom_syntect_theme_path { - // Load custom theme from file - ThemeSet::get_theme(custom_path) - .into_diagnostic() - .map_err(|e| { - miette::miette!("Failed to load custom theme from {:?}: {}", custom_path, e) - })? - } else { - // Check for embedded themes first - match theme.syntect_theme_name.as_str() { - "rose-pine" => { - let mut cursor = Cursor::new(ROSE_PINE_THEME.as_bytes()); - ThemeSet::load_from_reader(&mut cursor) - .into_diagnostic() - .map_err(|e| { - miette::miette!("Failed to load embedded rose-pine theme: {}", e) - })? - } - "rose-pine-dawn" => { - let mut cursor = Cursor::new(ROSE_PINE_DAWN_THEME.as_bytes()); - ThemeSet::load_from_reader(&mut cursor) - .into_diagnostic() - .map_err(|e| { - miette::miette!("Failed to load embedded rose-pine-dawn theme: {}", e) - })? - } - _ => { - // Fall back to syntect's built-in themes - let theme_set = ThemeSet::load_defaults(); - theme_set - .themes - .get(theme.syntect_theme_name.as_str()) - .ok_or_else(|| { - miette::miette!( - "Theme '{}' not found in defaults", - theme.syntect_theme_name - ) - })? - .clone() +pub async fn generate_syntax_css(theme: &Theme<'_>) -> miette::Result { + let syntect_theme = match &theme.code_theme { + ThemeCodeTheme::CodeThemeName(name) => { + match name.as_str() { + "rose-pine" => { + let mut cursor = Cursor::new(ROSE_PINE_THEME.as_bytes()); + ThemeSet::load_from_reader(&mut cursor) + .into_diagnostic() + .map_err(|e| { + miette::miette!("Failed to load embedded rose-pine theme: {}", e) + })? + } + "rose-pine-dawn" => { + let mut cursor = Cursor::new(ROSE_PINE_DAWN_THEME.as_bytes()); + ThemeSet::load_from_reader(&mut cursor) + .into_diagnostic() + .map_err(|e| { + miette::miette!("Failed to load embedded rose-pine-dawn theme: {}", e) + })? + } + _ => { + // Fall back to syntect's built-in themes + let theme_set = ThemeSet::load_defaults(); + theme_set + .themes + .get(name.as_str()) + .ok_or_else(|| miette::miette!("Theme '{}' not found in defaults", name))? + .clone() + } } + } + ThemeCodeTheme::CodeThemeFile(file) => { + let client = BasicClient::unauthenticated(); + let pds = client.pds_for_did(&file.did).await?; + let blob = client + .xrpc(pds) + .send( + &GetBlob::new() + .did(file.did.clone()) + .cid(file.content.blob().cid().clone()) + .build(), + ) + .await? + .buffer() + .clone(); + let mut cursor = Cursor::new(blob); + ThemeSet::load_from_reader(&mut cursor) + .into_diagnostic() + .map_err(|e| miette::miette!("Failed to download theme: {}", e))? + } + _ => { + let mut cursor = Cursor::new(ROSE_PINE_THEME.as_bytes()); + ThemeSet::load_from_reader(&mut cursor) + .into_diagnostic() + .map_err(|e| miette::miette!("Failed to load embedded rose-pine theme: {}", e))? } }; diff --git a/crates/weaver-renderer/src/static_site.rs b/crates/weaver-renderer/src/static_site.rs index df6cba4..09db108 100644 --- a/crates/weaver-renderer/src/static_site.rs +++ b/crates/weaver-renderer/src/static_site.rs @@ -16,6 +16,7 @@ use crate::{ document::{CssMode, write_document_footer, write_document_head}, writer::StaticPageWriter, }, + theme::defaultTheme, utils::flatten_dir_to_just_one_parent, walker::{WalkOptions, vault_contents}, }; @@ -149,9 +150,6 @@ where // Generate CSS files for multi-file mode self.generate_css_files().await?; - let mut writers = - Vec::with_capacity(self.context.dir_contents.clone().unwrap_or_default().len()); - for file in self .context .dir_contents @@ -162,84 +160,73 @@ where .filter(|file| file.starts_with(&self.context.start_at)) { let context = self.context.clone(); - let file = file.clone(); - // we'll see if this is a problem or not - writers.push(n0_future::task::spawn(async move { - let relative_path = file - .strip_prefix(context.start_at.clone()) - .expect("file should always be nested under root") - .to_path_buf(); - - // Check if this is a markdown file - let is_markdown = file - .extension() - .and_then(|ext| ext.to_str()) - .map(|ext| ext == "md" || ext == "markdown") - .unwrap_or(false); - - if !is_markdown { - // Copy non-markdown files directly - let output_path = if context - .options - .contains(StaticSiteOptions::FLATTEN_STRUCTURE) - { - let path_str = relative_path.to_string_lossy(); - let (parent, fname) = flatten_dir_to_just_one_parent(&path_str); - let parent = if parent.is_empty() { "entry" } else { parent }; - context - .destination - .join(String::from(parent)) - .join(String::from(fname)) - } else { - context.destination.join(relative_path.clone()) - }; - - // Create parent directory if needed - if let Some(parent) = output_path.parent() { - tokio::fs::create_dir_all(parent).await.into_diagnostic()?; - } - - tokio::fs::copy(&file, &output_path) - .await - .into_diagnostic()?; - return Ok(()); - } - - // Process markdown files - // Check if this is the designated index file - if let Some(index) = &context.index_file { - if &relative_path == index { - let output_path = context.destination.join("index.html"); - return write_page(context.clone(), file, output_path).await; - } - } - - if context + let relative_path = file + .strip_prefix(context.start_at.clone()) + .expect("file should always be nested under root") + .to_path_buf(); + + // Check if this is a markdown file + let is_markdown = file + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext == "md" || ext == "markdown") + .unwrap_or(false); + + if !is_markdown { + // Copy non-markdown files directly + let output_path = if context .options .contains(StaticSiteOptions::FLATTEN_STRUCTURE) { let path_str = relative_path.to_string_lossy(); let (parent, fname) = flatten_dir_to_just_one_parent(&path_str); let parent = if parent.is_empty() { "entry" } else { parent }; - let output_path = context + context .destination .join(String::from(parent)) - .join(String::from(fname)); - - write_page(context.clone(), file.clone(), output_path).await?; + .join(String::from(fname)) } else { - let output_path = context.destination.join(relative_path.clone()); + context.destination.join(relative_path.clone()) + }; - write_page(context.clone(), file.clone(), output_path).await?; + // Create parent directory if needed + if let Some(parent) = output_path.parent() { + tokio::fs::create_dir_all(parent).await.into_diagnostic()?; } - Ok::<(), miette::Report>(()) - })); - } - // def want to scope these so we wait until they all complete before we return - // and then we def want the errors, or at least the first error - for fut in n0_future::join_all(writers).await.into_iter() { - fut.into_diagnostic()??; + tokio::fs::copy(&file, &output_path) + .await + .into_diagnostic()?; + return Ok(()); + } + + // Process markdown files + // Check if this is the designated index file + if let Some(index) = &context.index_file { + if &relative_path == index { + let output_path = context.destination.join("index.html"); + return write_page(context.clone(), file, output_path).await; + } + } + + if context + .options + .contains(StaticSiteOptions::FLATTEN_STRUCTURE) + { + let path_str = relative_path.to_string_lossy(); + let (parent, fname) = flatten_dir_to_just_one_parent(&path_str); + let parent = if parent.is_empty() { "entry" } else { parent }; + let output_path = context + .destination + .join(String::from(parent)) + .join(String::from(fname)); + + write_page(context.clone(), file.clone(), output_path).await?; + } else { + let output_path = context.destination.join(relative_path.clone()); + + write_page(context.clone(), file.clone(), output_path).await?; + } } // Generate default index if requested and no custom index specified @@ -257,14 +244,13 @@ where async fn generate_css_files(&self) -> Result<(), miette::Report> { use crate::css::{generate_base_css, generate_syntax_css}; - use crate::theme::Theme; let css_dir = self.context.destination.join("css"); tokio::fs::create_dir_all(&css_dir) .await .into_diagnostic()?; - let default_theme = Theme::default(); + let default_theme = defaultTheme(); let theme = self.context.theme.as_deref().unwrap_or(&default_theme); // Write base.css @@ -274,7 +260,7 @@ where .into_diagnostic()?; // Write syntax.css - let syntax_css = generate_syntax_css(theme, &self.context.syntax_set)?; + let syntax_css = generate_syntax_css(theme).await?; tokio::fs::write(css_dir.join("syntax.css"), syntax_css) .await .into_diagnostic()?; diff --git a/crates/weaver-renderer/src/static_site/context.rs b/crates/weaver-renderer/src/static_site/context.rs index 88904fc..259250e 100644 --- a/crates/weaver-renderer/src/static_site/context.rs +++ b/crates/weaver-renderer/src/static_site/context.rs @@ -39,7 +39,7 @@ pub struct StaticSiteContext { pub client: Option, agent: Option>>, - pub theme: Option>, + pub theme: Option>>, pub katex_source: Option, pub syntax_set: Arc, pub index_file: Option, @@ -143,7 +143,7 @@ impl StaticSiteContext { } } - pub fn with_theme(mut self, theme: Theme) -> Self { + pub fn with_theme(mut self, theme: Theme<'static>) -> Self { self.theme = Some(Arc::new(theme)); self } diff --git a/crates/weaver-renderer/src/static_site/document.rs b/crates/weaver-renderer/src/static_site/document.rs index d91fdc9..dfbe539 100644 --- a/crates/weaver-renderer/src/static_site/document.rs +++ b/crates/weaver-renderer/src/static_site/document.rs @@ -1,6 +1,6 @@ use crate::css::{generate_base_css, generate_syntax_css}; use crate::static_site::context::{KaTeXSource, StaticSiteContext}; -use crate::theme::Theme; +use crate::theme::{Theme, defaultTheme}; use miette::IntoDiagnostic; use weaver_common::jacquard::client::AgentSession; @@ -98,7 +98,7 @@ pub async fn write_document_head( .into_diagnostic()?; } CssMode::Inline => { - let default_theme = Theme::default(); + let default_theme = defaultTheme(); let theme = context.theme.as_deref().unwrap_or(&default_theme); writer.write_all(b" \n").await.into_diagnostic()?; writer.write_all(b"