diff --git a/web/.cargo/config.toml b/web/.cargo/config.toml new file mode 100644 index 0000000..efe33ef --- /dev/null +++ b/web/.cargo/config.toml @@ -0,0 +1,4 @@ + +[build] +rustflags = [ "--cfg=web_sys_unstable_apis" ] + diff --git a/web/Cargo.toml b/web/Cargo.toml index d036946..8bce6af 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -36,6 +36,8 @@ wasm-opt = ['-Os'] [dependencies.web-sys] version = "0.3.58" features = [ + "Clipboard", "MediaQueryList", + "Navigator", "Window" ] diff --git a/web/src/state.rs b/web/src/state.rs index f1622fe..12b696a 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -452,6 +452,17 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { } } Msg::MultiMsg(msgs) => msgs.into_iter().for_each(|msg| update(msg, model, orders)), + Msg::CopyToClipboard(text) => { + let clipboard = seed::window() + .navigator() + .clipboard() + .expect("couldn't get clipboard"); + orders.perform_cmd(async move { + wasm_bindgen_futures::JsFuture::from(clipboard.write_text(&text)) + .await + .expect("failed to copy to clipboard"); + }); + } } } // `Model` describes our app state. @@ -551,4 +562,6 @@ pub enum Msg { MessageCollapse(String), MessageExpand(String), MultiMsg(Vec), + + CopyToClipboard(String), } diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index 5327d6b..3ba1234 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -6,7 +6,7 @@ use std::{ use chrono::{DateTime, Datelike, Duration, Local, Utc}; use human_format::{Formatter, Scales}; use itertools::Itertools; -use log::error; +use log::{error, info}; use seed::{prelude::*, *}; use seed_hooks::{state_access::CloneState, topo, use_state}; @@ -122,14 +122,16 @@ fn pretty_authors(authors: &str) -> impl Iterator> + '_ { if one_person { return Some(span![ attrs! { - At::Title => author.trim()}, + At::Title => author.trim() + }, author ]); } author.split_whitespace().nth(0).map(|first| { span![ attrs! { - At::Title => author.trim()}, + At::Title => author.trim() + }, first ] }) @@ -516,7 +518,17 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { p![ strong![from], br![], - small![from_detail], + small![ + &from_detail, + " ", + from_detail.map(|detail| span![ + i![C!["far", "fa-copy"]], + ev(Ev::Click, move |e| { + e.stop_propagation(); + Msg::CopyToClipboard(detail.to_string()) + }) + ]) + ], table![ IF!(!msg.to.is_empty() => tr![ @@ -526,19 +538,31 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { msg.to.iter().enumerate().map(|(i, to)| small![ if i>0 { ", " }else { "" }, - match to { - ShowThreadQueryThreadMessagesTo { - name: Some(name), - addr:Some(addr), - } => format!("{name} <{addr}>"), - ShowThreadQueryThreadMessagesTo { - name: Some(name), - addr:None - } => format!("{name}"), - ShowThreadQueryThreadMessagesTo { - addr: Some(addr), .. - } => format!("{addr}"), - _ => String::from("UNKNOWN"), + { + let to = match to { + ShowThreadQueryThreadMessagesTo { + name: Some(name), + addr:Some(addr), + } => format!("{name} <{addr}>"), + ShowThreadQueryThreadMessagesTo { + name: Some(name), + addr:None + } => format!("{name}"), + ShowThreadQueryThreadMessagesTo { + addr: Some(addr), .. + } => format!("{addr}"), + _ => String::from("UNKNOWN"), + }; + span![ + &to, " ", + span![ + i![C!["far", "fa-copy"]], + ev(Ev::Click, move |e| { + e.stop_propagation(); + Msg::CopyToClipboard(to) + }) + ] + ] } ]) @@ -551,21 +575,32 @@ fn render_open_header(msg: &ShowThreadQueryThreadMessages) -> Node { msg.cc.iter().enumerate().map(|(i, cc)| small![ if i>0 { ", " }else { "" }, - match cc { - ShowThreadQueryThreadMessagesCc { - name: Some(name), - addr:Some(addr), - } => format!("{name} <{addr}>"), - ShowThreadQueryThreadMessagesCc { - name: Some(name), - addr:None - } => format!("{name}"), - ShowThreadQueryThreadMessagesCc { - addr: Some(addr), .. - } => format!("<{addr}>"), - _ => String::from("UNKNOWN"), + { + let cc = match cc { + ShowThreadQueryThreadMessagesCc { + name: Some(name), + addr:Some(addr), + } => format!("{name} <{addr}>"), + ShowThreadQueryThreadMessagesCc { + name: Some(name), + addr:None + } => format!("{name}"), + ShowThreadQueryThreadMessagesCc { + addr: Some(addr), .. + } => format!("<{addr}>"), + _ => String::from("UNKNOWN"), + }; + span![ + &cc, " ", + span![ + i![C!["far", "fa-copy"]], + ev(Ev::Click, move |e| { + e.stop_propagation(); + Msg::CopyToClipboard(cc) + }) + ] + ] } - ]) ] ]),