diff --git a/public/message.css b/public/message.css index 0df716f..6d91966 100644 --- a/public/message.css +++ b/public/message.css @@ -125,3 +125,17 @@ hr { border: 0; background: var(--m-line); } + +body[data-paper] { + max-width: none; + font-family: Arial, sans-serif; + font-size: 13px; +} + +body[data-paper] p { + margin: 1em 0; +} + +body[data-paper] table { + border-collapse: separate; +} diff --git a/src-tauri/src/attachments.rs b/src-tauri/src/attachments.rs index c08ca57..afefb44 100644 --- a/src-tauri/src/attachments.rs +++ b/src-tauri/src/attachments.rs @@ -392,28 +392,22 @@ fn free_path(dir: &Path, filename: &str) -> PathBuf { // Commands // --------------------------------------------------------------------------------------------- -/// The reader has asked to see the pictures. -/// -/// The answer is a view and not a stored row on purpose: the `bodies` table has no column saying -/// whether images were loaded, so this is per view rather than remembered, and closing the thread -/// puts the block back. Inventing a column for it would also be inventing a policy, and the policy -/// that roams with a person is the per sender allowance on the contact card, not a flag on a body. #[tauri::command] pub async fn message_show_images( app: tauri::AppHandle, + account_id: String, message_id: String, ) -> Result { let db = db_of(&app)?; - let account_id = account_holding( - db.inner(), - "SELECT COUNT(*) FROM messages WHERE id = ?1", - &message_id, - )?; - let (raw, options) = db.with(&account_id, |conn| { + let options = sync::hydrate::render_options(conn)?; + let view = read::message_view(conn, &account_id, &message_id, &options.own_addresses)?; + if !view.images_allowed { + return Err("images are hidden for this email".into()); + } let raw = read::raw_body(conn, &message_id)? .ok_or("that message has not been fetched yet")?; - Ok((raw, sync::hydrate::render_options(conn)?)) + Ok((raw, options)) })?; let urls = wanted(&raw, &options)?; @@ -422,6 +416,9 @@ pub async fn message_show_images( let mut view = db.with(&account_id, |conn| { read::message_view(conn, &account_id, &message_id, &options.own_addresses) })?; + if !view.images_allowed { + return Ok(view); + } view.html = rendered.html; view.quoted_html = rendered.quoted_html; view.trackers = rendered.trackers; @@ -430,6 +427,29 @@ pub async fn message_show_images( Ok(view) } +#[tauri::command] +pub fn message_images_set( + app: tauri::AppHandle, + account_id: String, + message_id: String, + allowed: bool, +) -> Result { + let db = db_of(&app)?; + db.with(&account_id, |conn| { + let options = sync::hydrate::render_options(conn)?; + let mut view = read::message_view(conn, &account_id, &message_id, &options.own_addresses)?; + if view.images_allowed != allowed { + crate::state::write::set_pref( + conn, + &read::message_images_key(&view.message_id, &view.id), + if allowed { "true" } else { "false" }, + )?; + } + view.images_allowed = allowed; + Ok(view) + }) +} + /// The inline preview. Refuses anything too big to be a data URI before it fetches a byte, so the /// answer to a hundred megabyte video is a sentence rather than a spinner. #[tauri::command] diff --git a/src-tauri/src/drafts.rs b/src-tauri/src/drafts.rs index 425650f..5bd6f5b 100644 --- a/src-tauri/src/drafts.rs +++ b/src-tauri/src/drafts.rs @@ -20,10 +20,11 @@ // the queue is the `drafts` table, which already has the two columns it needs. use std::collections::HashSet; -use std::sync::{Mutex, OnceLock}; +use std::sync::OnceLock; use rusqlite::{Connection, OptionalExtension}; use serde::{Deserialize, Serialize}; +use tauri::Manager; use crate::decisions::db_of; use crate::dto::{Draft, DraftAttachment, DraftSaved, Person}; @@ -341,7 +342,10 @@ pub async fn upload( for (id, provider_draft_id, held) in store.with(|conn| due(conn, now))? { let subject = held.draft.subject.clone(); let from = crate::send::sender(&held.draft, from); - let raw = store.with(|conn| built(conn, &held.draft, &from, None, None, &[], &subject))?; + let raw = store.with(|conn| { + let threading = crate::send::threading(conn, &held.draft)?; + built(conn, &held.draft, &from, None, threading.in_reply_to.as_deref(), &threading.references, &subject) + })?; let thread_hint = store.with(|conn| thread_hint(conn, held.draft.thread_key.as_deref()))?; let put = provider @@ -370,9 +374,9 @@ pub fn thread_hint(conn: &Connection, thread_key: Option<&str>) -> Result