diff options
Diffstat (limited to 'src/components/post_editor.rs')
-rw-r--r-- | src/components/post_editor.rs | 216 |
1 files changed, 126 insertions, 90 deletions
diff --git a/src/components/post_editor.rs b/src/components/post_editor.rs index d08685a..021ba91 100644 --- a/src/components/post_editor.rs +++ b/src/components/post_editor.rs @@ -1,11 +1,16 @@ -use gettextrs::*; use crate::components::tag_pill::*; use adw::prelude::*; +use gettextrs::*; use glib::translate::IntoGlib; -use relm4::{factory::FactoryVecDeque, gtk, prelude::{Controller, DynamicIndex}, Component, ComponentParts, ComponentSender, RelmWidgetExt}; #[cfg(feature = "smart-summary")] use relm4::prelude::ComponentController; +use relm4::{ + factory::FactoryVecDeque, + gtk, + prelude::{Controller, DynamicIndex}, + Component, ComponentParts, ComponentSender, RelmWidgetExt, +}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)] #[enum_type(name = "MicropubVisibility")] @@ -18,7 +23,7 @@ impl std::fmt::Display for Visibility { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::Public => "public", - Self::Private => "private" + Self::Private => "private", }) } } @@ -29,7 +34,7 @@ pub struct Post { pub summary: Option<String>, pub tags: Vec<String>, pub content: String, - pub visibility: Visibility + pub visibility: Visibility, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -39,39 +44,36 @@ pub struct PostConversionSettings { impl Post { pub fn into_mf2(self, settings: PostConversionSettings) -> microformats::types::Item { - use microformats::types::{Item, Class, KnownClass, PropertyValue, Fragment}; + use microformats::types::{Class, Fragment, Item, KnownClass, PropertyValue}; let mut mf2 = Item::new(vec![Class::Known(KnownClass::Entry)]); if let Some(name) = self.name { - mf2.properties.insert( - "name".to_owned(), vec![PropertyValue::Plain(name)] - ); + mf2.properties + .insert("name".to_owned(), vec![PropertyValue::Plain(name)]); } if let Some(summary) = self.summary { - mf2.properties.insert( - "summary".to_owned(), - vec![PropertyValue::Plain(summary)] - ); + mf2.properties + .insert("summary".to_owned(), vec![PropertyValue::Plain(summary)]); } if !self.tags.is_empty() { mf2.properties.insert( "category".to_string(), - self.tags.into_iter().map(PropertyValue::Plain).collect() + self.tags.into_iter().map(PropertyValue::Plain).collect(), ); } mf2.properties.insert( "visibility".to_string(), - vec![PropertyValue::Plain(self.visibility.to_string())] + vec![PropertyValue::Plain(self.visibility.to_string())], ); let content = if settings.send_html_directly { PropertyValue::Fragment(Fragment { html: self.content.clone(), value: self.content, - lang: None + lang: None, }) } else { PropertyValue::Plain(self.content) @@ -86,24 +88,35 @@ impl Post { #[tracker::track] #[derive(Debug)] pub(crate) struct PostEditor<E> { - #[no_eq] smart_summary_busy_guard: Option<gtk::gio::ApplicationBusyGuard>, + #[no_eq] + smart_summary_busy_guard: Option<gtk::gio::ApplicationBusyGuard>, sending: bool, - #[do_not_track] #[allow(dead_code)] spell_checker: spelling::Checker, - #[do_not_track] spelling_adapter: spelling::TextBufferAdapter, - - #[do_not_track] name_buffer: gtk::EntryBuffer, - #[do_not_track] summary_buffer: gtk::EntryBuffer, - #[do_not_track] content_buffer: sourceview5::Buffer, - - #[do_not_track] pending_tag_buffer: gtk::EntryBuffer, - #[do_not_track] tags: relm4::factory::FactoryVecDeque<TagPill>, + #[do_not_track] + #[allow(dead_code)] + spell_checker: spelling::Checker, + #[do_not_track] + spelling_adapter: spelling::TextBufferAdapter, + + #[do_not_track] + name_buffer: gtk::EntryBuffer, + #[do_not_track] + summary_buffer: gtk::EntryBuffer, + #[do_not_track] + content_buffer: sourceview5::Buffer, + + #[do_not_track] + pending_tag_buffer: gtk::EntryBuffer, + #[do_not_track] + tags: relm4::factory::FactoryVecDeque<TagPill>, visibility: Visibility, - #[do_not_track] narrow_layout: gtk::BoxLayout, + #[do_not_track] + narrow_layout: gtk::BoxLayout, #[cfg(feature = "smart-summary")] - #[do_not_track] smart_summary: Controller<crate::components::SmartSummaryButton>, - _err: std::marker::PhantomData<E> + #[do_not_track] + smart_summary: Controller<crate::components::SmartSummaryButton>, + _err: std::marker::PhantomData<E>, } impl<E> PostEditor<E> { @@ -120,13 +133,17 @@ impl<E> PostEditor<E> { #[allow(clippy::manual_non_exhaustive)] // false positive pub enum Input<E: std::error::Error + std::fmt::Debug + Send + 'static> { #[cfg(feature = "smart-summary")] - #[doc(hidden)] SmartSummary(crate::components::smart_summary::Output), - #[doc(hidden)] VisibilitySelected(Visibility), - #[doc(hidden)] AddTagFromBuffer, - #[doc(hidden)] RemoveTag(DynamicIndex), + #[doc(hidden)] + SmartSummary(crate::components::smart_summary::Output), + #[doc(hidden)] + VisibilitySelected(Visibility), + #[doc(hidden)] + AddTagFromBuffer, + #[doc(hidden)] + RemoveTag(DynamicIndex), Submit, SubmitDone(glib::Uri), - SubmitError(E) + SubmitError(E), } #[relm4::component(pub)] @@ -340,7 +357,7 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post fn init( init: Self::Init, root: Self::Root, - sender: ComponentSender<Self> + sender: ComponentSender<Self>, ) -> ComponentParts<Self> { #[cfg(feature = "smart-summary")] let (http, init) = init; @@ -352,17 +369,13 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post smart_summary_busy_guard: None, sending: false, - spelling_adapter: spelling::TextBufferAdapter::new( - &content_buffer, - &spell_checker - ), + spelling_adapter: spelling::TextBufferAdapter::new(&content_buffer, &spell_checker), spell_checker, name_buffer: gtk::EntryBuffer::default(), summary_buffer: gtk::EntryBuffer::default(), content_buffer, - pending_tag_buffer: gtk::EntryBuffer::default(), tags: FactoryVecDeque::builder() .launch({ @@ -375,10 +388,9 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post listbox.set_layout_manager(Some(layout)); listbox }) - .forward( - sender.input_sender(), - |del: TagPillDelete| Input::RemoveTag(del.0) - ), + .forward(sender.input_sender(), |del: TagPillDelete| { + Input::RemoveTag(del.0) + }), visibility: Visibility::Public, narrow_layout: gtk::BoxLayout::builder() @@ -403,15 +415,15 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post model.spelling_adapter.set_enabled(true); - widgets.visibility_selector.set_expression(Some( - gtk::ClosureExpression::new::<String>( + widgets + .visibility_selector + .set_expression(Some(gtk::ClosureExpression::new::<String>( [] as [gtk::Expression; 0], glib::closure::RustClosure::new(|v| { let list_item = v[0].get::<adw::EnumListItem>().unwrap(); Some(gettext(list_item.name().as_str()).into()) - }) - ) - )); + }), + ))); if let Some(post) = init { if let Some(name) = post.name { @@ -422,55 +434,68 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post } let mut tags = model.tags.guard(); - post.tags.into_iter().for_each(|t| { tags.push_back(t.into_boxed_str()); }); + post.tags.into_iter().for_each(|t| { + tags.push_back(t.into_boxed_str()); + }); model.content_buffer.set_text(&post.content); - widgets.visibility_selector.set_selected( - visibility_model.find_position(post.visibility.into_glib()) - ); + widgets + .visibility_selector + .set_selected(visibility_model.find_position(post.visibility.into_glib())); model.visibility = post.visibility; } ComponentParts { model, widgets } } - fn update_with_view(&mut self, widgets: &mut Self::Widgets, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) { + fn update_with_view( + &mut self, + widgets: &mut Self::Widgets, + msg: Self::Input, + sender: ComponentSender<Self>, + root: &Self::Root, + ) { self.reset(); match msg { #[cfg(feature = "smart-summary")] Input::SmartSummary(crate::components::SmartSummaryOutput::Start) => { widgets.content_textarea.set_sensitive(false); if self.content_buffer.char_count() == 0 { - let _ = self.smart_summary.sender().send( - crate::components::SmartSummaryInput::Cancel - ); + let _ = self + .smart_summary + .sender() + .send(crate::components::SmartSummaryInput::Cancel); } else { let text = self.content_buffer.text( &self.content_buffer.start_iter(), &self.content_buffer.end_iter(), - false + false, ); - self.set_smart_summary_busy_guard( - Some(relm4::main_adw_application().mark_busy()) - ); - if self.smart_summary.sender().send( - crate::components::SmartSummaryInput::Text(text.into()) - ).is_ok() { + self.set_smart_summary_busy_guard(Some( + relm4::main_adw_application().mark_busy(), + )); + if self + .smart_summary + .sender() + .send(crate::components::SmartSummaryInput::Text(text.into())) + .is_ok() + { self.summary_buffer.set_text(""); } } widgets.content_textarea.set_sensitive(true); - }, + } #[cfg(feature = "smart-summary")] Input::SmartSummary(crate::components::SmartSummaryOutput::Chunk(text)) => { - self.summary_buffer.insert_text(self.summary_buffer.length(), text); - }, + self.summary_buffer + .insert_text(self.summary_buffer.length(), text); + } #[cfg(feature = "smart-summary")] Input::SmartSummary(crate::components::SmartSummaryOutput::Done) => { self.set_smart_summary_busy_guard(None); - }, + } #[cfg(feature = "smart-summary")] Input::SmartSummary(crate::components::SmartSummaryOutput::Error(err)) => { self.set_smart_summary_busy_guard(None); @@ -479,44 +504,51 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post toast.set_timeout(0); toast.set_priority(adw::ToastPriority::High); root.add_toast(toast); - }, + } Input::VisibilitySelected(vis) => { log::debug!("Changed visibility: {}", vis); self.visibility = vis; - }, + } Input::AddTagFromBuffer => { let tag = String::from(self.pending_tag_buffer.text()); if !tag.is_empty() { - self.tags.guard().push_back( - tag.into_boxed_str() - ); + self.tags.guard().push_back(tag.into_boxed_str()); self.pending_tag_buffer.set_text(""); } - }, + } Input::RemoveTag(idx) => { self.tags.guard().remove(idx.current_index()); - }, + } Input::Submit => { self.sending = true; let post = if self.content_buffer.char_count() > 0 { Some(Post { name: if self.name_buffer.length() > 0 { Some(self.name_buffer.text().into()) - } else { None }, + } else { + None + }, summary: if self.summary_buffer.length() > 0 { Some(self.summary_buffer.text().into()) - } else { None }, + } else { + None + }, tags: self.tags.iter().map(|t| t.0.clone().into()).collect(), - content: self.content_buffer.text( - &self.content_buffer.start_iter(), - &self.content_buffer.end_iter(), - false - ).into(), + content: self + .content_buffer + .text( + &self.content_buffer.start_iter(), + &self.content_buffer.end_iter(), + false, + ) + .into(), visibility: self.visibility, }) - } else { None }; + } else { + None + }; let _ = sender.output(post); - }, + } Input::SubmitDone(location) => { self.name_buffer.set_text(""); self.summary_buffer.set_text(""); @@ -528,18 +560,22 @@ impl<E: std::error::Error + std::fmt::Debug + Send + 'static> Component for Post gtk::UriLauncher::new(&location.to_string()).launch( None::<&adw::ApplicationWindow>, None::<&gio::Cancellable>, - glib::clone!(#[weak] toast, move |result| { - if let Err(err) = result { - log::warn!("Error opening post URI: {}", err); - } else { - toast.dismiss() + glib::clone!( + #[weak] + toast, + move |result| { + if let Err(err) = result { + log::warn!("Error opening post URI: {}", err); + } else { + toast.dismiss() + } } - }) + ), ); }); root.add_toast(toast); - }, + } Input::SubmitError(err) => { let toast = adw::Toast::new(&gettext!("Error sending post: {}", err)); toast.set_timeout(0); |