From 7d9376d607a925d0e99a2b4955b7b7031ccd2ba4 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sat, 19 Apr 2025 12:33:11 -0700 Subject: [PATCH] Add view original functionality --- server/src/bin/letterbox-server.rs | 22 ++++++++++++++- shared/src/lib.rs | 7 +++++ web/src/view/mod.rs | 43 ++++++++++++++++++++---------- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/server/src/bin/letterbox-server.rs b/server/src/bin/letterbox-server.rs index 8e7a37f..aade4bc 100644 --- a/server/src/bin/letterbox-server.rs +++ b/server/src/bin/letterbox-server.rs @@ -29,7 +29,7 @@ use serde::Deserialize; use sqlx::postgres::PgPool; use tokio::{net::TcpListener, sync::Mutex}; use tower_http::trace::{DefaultMakeSpan, TraceLayer}; -use tracing::info; +use tracing::{info, warn}; // Make our own error that wraps `anyhow::Error`. struct AppError(letterbox_server::ServerError); @@ -142,6 +142,25 @@ async fn view_cid( Ok(inline_attachment_response(attachment)) } +async fn view_original( + State(AppState { nm, .. }): State, + extract::Path(id): extract::Path, +) -> Result { + info!("view_original {id}"); + let mid = if id.starts_with("id:") { + id.to_string() + } else { + format!("id:{}", id) + }; + let files = nm.files(&mid)?; + let Some(path) = files.first() else { + warn!("failed to find files for message {mid}"); + return Ok((StatusCode::NOT_FOUND, mid).into_response()); + }; + let str = std::fs::read_to_string(&path)?; + Ok(str.into_response()) +} + async fn graphiql() -> impl IntoResponse { response::Html( GraphiQLSource::build() @@ -260,6 +279,7 @@ async fn main() -> Result<(), Box> { get(download_attachment), ) .route("/view/attachment/{id}/{idx}/{*rest}", get(view_attachment)) + .route("/original/{id}", get(view_original)) .route("/cid/{id}/{cid}", get(view_cid)) .route("/ws", any(start_ws)) .route_service("/graphql/ws", GraphQLSubscription::new(schema.clone())) diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 5b97d7a..9d1ec3e 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -20,6 +20,13 @@ pub enum WebsocketMessage { pub mod urls { pub const MOUNT_POINT: &'static str = "/api"; + pub fn view_original(host: Option<&str>, id: &str) -> String { + if let Some(host) = host { + format!("//{host}/api/original/{id}") + } else { + format!("/api/original/{id}") + } + } pub fn cid_prefix(host: Option<&str>, cid: &str) -> String { if let Some(host) = host { format!("//{host}/api/cid/{cid}/") diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index de5806d..71d04fe 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -694,6 +694,8 @@ fn render_open_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node< .collect(); let show_x_original_to = !*to_xinu.borrow() && msg.x_original_to.is_some(); let show_delivered_to = !*to_xinu.borrow() && !show_x_original_to && msg.delivered_to.is_some(); + let host = seed::window().location().host().expect("couldn't get host"); + let href = letterbox_shared::urls::view_original(Some(&host), &msg.id); div![ C!["flex", "p-4", "bg-neutral-800"], div![avatar], @@ -775,20 +777,33 @@ fn render_open_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node< C!["text-right"], msg.timestamp .map(|ts| div![C!["text-xs", "text-nowrap"], human_age(ts)]), - i![C![ - "mx-4", - "read-status", - "far", - if is_unread { - "fa-envelope" - } else { - "fa-envelope-open" - }, - ]], - ev(Ev::Click, move |e| { - e.stop_propagation(); - Msg::SetUnread(id, !is_unread) - }), + div![ + C!["p-2"], + i![C![ + "mx-4", + "read-status", + "far", + if is_unread { + "fa-envelope" + } else { + "fa-envelope-open" + }, + ]], + ev(Ev::Click, move |e| { + e.stop_propagation(); + Msg::SetUnread(id, !is_unread) + }), + ], + div![ + C!["text-xs"], + span![a![ + attrs! { + At::Href=>href, + At::Target=>"_blank", + }, + "View original" + ]] + ] ] ] }