Compare commits

..

No commits in common. "ee9b6be95efd68d250c8989732d78f1c76e876aa" and "c7740811bfdab1a4481cbafbd3ce5f042387c9c6" have entirely different histories.

19 changed files with 7061 additions and 152 deletions

7061
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,7 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
#default-members = ["server"] default-members = ["server"]
members = ["notmuch", "procmail2notmuch", "shared"] members = ["web", "server", "notmuch", "procmail2notmuch", "shared"]
#members = ["web", "server", "notmuch", "procmail2notmuch", "shared"]
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1

View File

@ -1,13 +1,9 @@
[package] [package]
name = "letterbox-notmuch" name = "notmuch"
version = "0.1.0" version = "0.0.144"
edition = "2021" edition = "2021"
exclude = ["/testdata"]
description = "Wrapper for calling notmuch cli"
license = "UNLICENSED"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
publish = ["xinu"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
log = "0.4.14" log = "0.4.14"

View File

@ -4,7 +4,7 @@ use std::{
time::Instant, time::Instant,
}; };
use letterbox_notmuch::Notmuch; use notmuch::Notmuch;
use rayon::iter::{ParallelBridge, ParallelIterator}; use rayon::iter::{ParallelBridge, ParallelIterator};
#[test] #[test]

View File

@ -1,11 +1,7 @@
[package] [package]
name = "letterbox-procmail2notmuch" name = "procmail2notmuch"
version = "0.1.0" version = "0.0.144"
edition = "2021" edition = "2021"
description = "Tool for generating notmuch rules from procmail"
license = "UNLICENSED"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
publish = ["xinu"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,12 +1,8 @@
[package] [package]
name = "letterbox-server" name = "letterbox-server"
version = "0.1.0" version = "0.0.144"
edition = "2021" edition = "2021"
default-run = "letterbox-server" default-run = "letterbox-server"
description = "Backend for letterbox"
license = "UNLICENSED"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
publish = ["xinu"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -29,6 +25,7 @@ lol_html = "2.0.0"
mailparse = "0.16.0" mailparse = "0.16.0"
maplit = "1.0.2" maplit = "1.0.2"
memmap = "0.7.0" memmap = "0.7.0"
notmuch = { path = "../notmuch" }
opentelemetry = "0.28.0" opentelemetry = "0.28.0"
regex = "1.11.1" regex = "1.11.1"
reqwest = { version = "0.12.7", features = ["blocking"] } reqwest = { version = "0.12.7", features = ["blocking"] }
@ -37,6 +34,7 @@ rocket_cors = "0.6.0"
scraper = "0.22.0" scraper = "0.22.0"
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.87" serde_json = "1.0.87"
shared = { path = "../shared" }
sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "time"] } sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "time"] }
tantivy = { version = "0.22.0", optional = true } tantivy = { version = "0.22.0", optional = true }
thiserror = "2.0.0" thiserror = "2.0.0"
@ -47,8 +45,6 @@ urlencoding = "2.1.3"
#xtracing = { path = "../../xtracing" } #xtracing = { path = "../../xtracing" }
#xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" } #xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" }
xtracing = { version = "0.2.0", registry = "xinu" } xtracing = { version = "0.2.0", registry = "xinu" }
letterbox-notmuch = { version = "0.1.0", registry = "xinu" }
letterbox-shared = { version = "0.1.0", registry = "xinu" }
[build-dependencies] [build-dependencies]
build-info-build = "0.0.39" build-info-build = "0.0.39"

View File

@ -1,13 +0,0 @@
select t.id, tt.tokid, tt.alias, length(t.token), t.token from (
select id, (ts_parse('default',
-- regexp_replace(
-- regexp_replace(summary, '<[^>]+>', ' ', 'g'),
-- '\s+',
-- ' ',
-- 'g'
-- )
summary
)).* from post) t
inner join ts_token_type('default') tt
on t.tokid = tt.tokid
where length(token) >= 2*1024;

View File

@ -1,21 +0,0 @@
use std::fs;
use url::Url;
fn main() -> anyhow::Result<()> {
println!("PWD: {}", std::env::current_dir()?.display());
let _url = "https://slashdot.org/story/25/01/24/1813201/walgreens-replaced-fridge-doors-with-smart-screens-its-now-a-200-million-fiasco?utm_source=rss1.0mainlinkanon&utm_medium=feed";
let _url = "https://hackaday.com/2025/01/24/hackaday-podcast-episode-305-caustic-clocks-practice-bones-and-brick-layers/";
let _url = "https://theonion.com/monster-devastated-to-see-film-depicting-things-he-told-guillermo-del-toro-in-confidence/";
let _url = "https://trofi.github.io/posts/330-another-nix-language-nondeterminism-example.html";
let _url = "https://blog.cloudflare.com/ddos-threat-report-for-2024-q4/";
let url = "https://trofi.github.io/posts/330-another-nix-language-nondeterminism-example.html";
let body = reqwest::blocking::get(url)?.text()?;
let output = "/tmp/h2md/output.html";
let inliner = css_inline::CSSInliner::options()
.base_url(Url::parse(url).ok())
.build();
let inlined = inliner.inline(&body)?;
fs::write(output, inlined)?;
Ok(())
}

View File

@ -8,7 +8,6 @@ use std::{error::Error, io::Cursor, str::FromStr};
use async_graphql::{extensions, http::GraphiQLSource, EmptySubscription, Schema}; use async_graphql::{extensions, http::GraphiQLSource, EmptySubscription, Schema};
use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse}; use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse};
use cacher::FilesystemCacher; use cacher::FilesystemCacher;
use letterbox_notmuch::{Notmuch, NotmuchError, ThreadSet};
#[cfg(feature = "tantivy")] #[cfg(feature = "tantivy")]
use letterbox_server::tantivy::TantivyConnection; use letterbox_server::tantivy::TantivyConnection;
use letterbox_server::{ use letterbox_server::{
@ -17,6 +16,7 @@ use letterbox_server::{
graphql::{Attachment, GraphqlSchema, Mutation, QueryRoot}, graphql::{Attachment, GraphqlSchema, Mutation, QueryRoot},
nm::{attachment_bytes, cid_attachment_bytes}, nm::{attachment_bytes, cid_attachment_bytes},
}; };
use notmuch::{Notmuch, NotmuchError, ThreadSet};
use rocket::{ use rocket::{
fairing::AdHoc, fairing::AdHoc,
http::{ContentType, Header}, http::{ContentType, Header},
@ -179,7 +179,7 @@ async fn graphql_request(
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
let _guard = xtracing::init(env!("CARGO_BIN_NAME"))?; let _guard = xtracing::init(env!("CARGO_BIN_NAME"))?;
build_info::build_info!(fn bi); build_info::build_info!(fn bi);
info!("Build Info: {}", letterbox_shared::build_version(bi)); info!("Build Info: {}", shared::build_version(bi));
let allowed_origins = AllowedOrigins::all(); let allowed_origins = AllowedOrigins::all();
let cors = rocket_cors::CorsOptions { let cors = rocket_cors::CorsOptions {
allowed_origins, allowed_origins,
@ -195,7 +195,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
let rkt = rocket::build() let rkt = rocket::build()
.mount( .mount(
letterbox_shared::urls::MOUNT_POINT, shared::urls::MOUNT_POINT,
routes![ routes![
original, original,
show_pretty, show_pretty,

View File

@ -10,7 +10,7 @@ use crate::TransformError;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ServerError { pub enum ServerError {
#[error("notmuch: {0}")] #[error("notmuch: {0}")]
NotmuchError(#[from] letterbox_notmuch::NotmuchError), NotmuchError(#[from] notmuch::NotmuchError),
#[error("flatten")] #[error("flatten")]
FlattenError, FlattenError,
#[error("mail parse error: {0}")] #[error("mail parse error: {0}")]

View File

@ -6,8 +6,8 @@ use async_graphql::{
SimpleObject, Union, SimpleObject, Union,
}; };
use cacher::FilesystemCacher; use cacher::FilesystemCacher;
use letterbox_notmuch::Notmuch;
use log::info; use log::info;
use notmuch::Notmuch;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use tokio::join; use tokio::join;
@ -283,7 +283,7 @@ pub struct QueryRoot;
impl QueryRoot { impl QueryRoot {
async fn version<'ctx>(&self, _ctx: &Context<'ctx>) -> Result<String, Error> { async fn version<'ctx>(&self, _ctx: &Context<'ctx>) -> Result<String, Error> {
build_info::build_info!(fn bi); build_info::build_info!(fn bi);
Ok(letterbox_shared::build_version(bi)) Ok(shared::build_version(bi))
} }
#[instrument(skip_all, fields(query=query))] #[instrument(skip_all, fields(query=query))]
#[instrument(skip_all, fields(query=query, request_id=request_id()))] #[instrument(skip_all, fields(query=query, request_id=request_id()))]

View File

@ -2,10 +2,10 @@ use std::collections::HashMap;
use cacher::FilesystemCacher; use cacher::FilesystemCacher;
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
use letterbox_shared::compute_color;
use log::{error, info}; use log::{error, info};
use maplit::hashmap; use maplit::hashmap;
use scraper::Selector; use scraper::Selector;
use shared::compute_color;
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use tracing::instrument; use tracing::instrument;
use url::Url; use url::Url;

View File

@ -5,10 +5,10 @@ use std::{
time::Instant, time::Instant,
}; };
use letterbox_notmuch::Notmuch;
use log::{error, info, warn}; use log::{error, info, warn};
use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail}; use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail};
use memmap::MmapOptions; use memmap::MmapOptions;
use notmuch::Notmuch;
use sqlx::PgPool; use sqlx::PgPool;
use tracing::instrument; use tracing::instrument;
@ -43,9 +43,7 @@ pub fn is_notmuch_thread_or_id(id: &str) -> bool {
} }
// TODO(wathiede): decide good error type // TODO(wathiede): decide good error type
pub fn threadset_to_messages( pub fn threadset_to_messages(thread_set: notmuch::ThreadSet) -> Result<Vec<Message>, ServerError> {
thread_set: letterbox_notmuch::ThreadSet,
) -> Result<Vec<Message>, ServerError> {
for t in thread_set.0 { for t in thread_set.0 {
for _tn in t.0 {} for _tn in t.0 {}
} }
@ -192,7 +190,7 @@ pub async fn thread(
.headers .headers
.get_first_value("date") .get_first_value("date")
.and_then(|d| mailparse::dateparse(&d).ok()); .and_then(|d| mailparse::dateparse(&d).ok());
let cid_prefix = letterbox_shared::urls::cid_prefix(None, &id); let cid_prefix = shared::urls::cid_prefix(None, &id);
let base_url = None; let base_url = None;
let mut part_addr = Vec::new(); let mut part_addr = Vec::new();
part_addr.push(id.to_string()); part_addr.push(id.to_string());

View File

@ -1,15 +1,11 @@
[package] [package]
name = "letterbox-shared" name = "shared"
version = "0.1.0" version = "0.0.144"
edition = "2021" edition = "2021"
description = "Shared module for letterbox"
license = "UNLICENSED"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
publish = ["xinu"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
build-info = "0.0.39" build-info = "0.0.39"
letterbox-notmuch = { version = "0.1.0", registry = "xinu" } notmuch = { path = "../notmuch" }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }

View File

@ -1,7 +1,7 @@
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use build_info::{BuildInfo, VersionControl}; use build_info::{BuildInfo, VersionControl};
use letterbox_notmuch::SearchSummary; use notmuch::SearchSummary;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View File

@ -1,12 +1,13 @@
[package] [package]
version = "0.1.0" version = "0.0.144"
name = "letterbox-web" name = "letterbox"
repository = "https://github.com/seed-rs/seed-quickstart"
authors = ["Bill Thiede <git@xinu.tv>"] authors = ["Bill Thiede <git@xinu.tv>"]
description = "App Description"
categories = ["category"]
license = "MIT"
readme = "./README.md"
edition = "2021" edition = "2021"
description = "Web frontend for letterbox"
license = "UNLICENSED"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
publish = ["xinu"]
[build-dependencies] [build-dependencies]
build-info-build = "0.0.39" build-info-build = "0.0.39"
@ -21,6 +22,8 @@ seed = { version = "0.10.0", features = ["routing"] }
#seed = "0.9.2" #seed = "0.9.2"
console_log = { version = "0.1.0", registry = "xinu" } console_log = { version = "0.1.0", registry = "xinu" }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
notmuch = { path = "../notmuch" }
shared = { path = "../shared" }
itertools = "0.14.0" itertools = "0.14.0"
serde_json = { version = "1.0.93", features = ["unbounded_depth"] } serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
chrono = "0.4.31" chrono = "0.4.31"
@ -34,8 +37,6 @@ wasm-bindgen = "=0.2.100"
uuid = { version = "1.13.1", features = [ uuid = { version = "1.13.1", features = [
"js", "js",
] } # direct dep to set js feature, prevents Rng issues ] } # direct dep to set js feature, prevents Rng issues
letterbox-shared = { version = "0.1.0", registry = "xinu" }
letterbox-notmuch = { version = "0.1.0", path = "../notmuch", registry = "xinu" }
[package.metadata.wasm-pack.profile.release] [package.metadata.wasm-pack.profile.release]
wasm-opt = ['-Os'] wasm-opt = ['-Os']

View File

@ -27,7 +27,7 @@ pub fn unread_query() -> &'static str {
// `init` describes what should happen when your app started. // `init` describes what should happen when your app started.
pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model { pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
let version = letterbox_shared::build_version(bi); let version = shared::build_version(bi);
info!("Build Info: {}", version); info!("Build Info: {}", version);
if url.hash().is_none() { if url.hash().is_none() {
orders.request_url(urls::search(unread_query(), 0)); orders.request_url(urls::search(unread_query(), 0));

View File

@ -3,10 +3,10 @@ use std::collections::HashSet;
use chrono::{DateTime, Datelike, Duration, Local, Utc}; use chrono::{DateTime, Datelike, Duration, Local, Utc};
use human_format::{Formatter, Scales}; use human_format::{Formatter, Scales};
use itertools::Itertools; use itertools::Itertools;
use letterbox_shared::compute_color;
use log::{debug, error, info}; use log::{debug, error, info};
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use seed_hooks::{state_access::CloneState, topo, use_state}; use seed_hooks::{state_access::CloneState, topo, use_state};
use shared::compute_color;
use web_sys::HtmlElement; use web_sys::HtmlElement;
use crate::{ use crate::{
@ -87,7 +87,6 @@ pub fn view(model: &Model) -> Node<Msg> {
}; };
div![ div![
C![ C![
"relative",
"flex", "flex",
"flex-wrap-reverse", "flex-wrap-reverse",
"bg-black", "bg-black",
@ -95,17 +94,14 @@ pub fn view(model: &Model) -> Node<Msg> {
"lg:flex-nowrap", "lg:flex-nowrap",
"w-full" "w-full"
], ],
reading_progress(model.read_completion_ratio),
div![ div![
C!["w-full", "lg:w-48", "flex-none", "flex", "flex-col"], C!["w-full", "lg:w-48", "flex-none", "flex", "flex-col"],
tags(model), tags(model),
versions(&model.versions) versions(&model.versions)
], ],
reading_progress(model.read_completion_ratio),
div![ div![
// TODO: This "overflow-hidden" is a hack because I can't figure out C!["flex-auto", "flex", "flex-col"],
// how to prevent the search input box on mobile for growing it's
// parent wider
C!["flex-auto", "flex", "flex-col", "overflow-hidden"],
view_header(&model.query, &model.refreshing_state, true), view_header(&model.query, &model.refreshing_state, true),
content, content,
view_header(&model.query, &model.refreshing_state, false), view_header(&model.query, &model.refreshing_state, false),
@ -697,12 +693,7 @@ fn render_attachements(
let default = "UNKNOWN_FILE".to_string(); let default = "UNKNOWN_FILE".to_string();
let filename = a.filename.as_ref().unwrap_or(&default); let filename = a.filename.as_ref().unwrap_or(&default);
let host = seed::window().location().host().expect("couldn't get host"); let host = seed::window().location().host().expect("couldn't get host");
let url = letterbox_shared::urls::download_attachment( let url = shared::urls::download_attachment(Some(&host), &a.id, &a.idx, filename);
Some(&host),
&a.id,
&a.idx,
filename,
);
let mut fmtr = Formatter::new(); let mut fmtr = Formatter::new();
fmtr.with_separator(" "); fmtr.with_separator(" ");
fmtr.with_scales(Scales::Binary()); fmtr.with_scales(Scales::Binary());
@ -880,7 +871,7 @@ fn view_header(
}; };
let query = Url::decode_uri_component(query).unwrap_or("".to_string()); let query = Url::decode_uri_component(query).unwrap_or("".to_string());
nav![ nav![
C!["flex", "px-4", "pt-4", "overflow-hidden"], C!["flex", "px-4", "pt-4"],
a![ a![
C![IF![is_error => "bg-red-500"], "rounded-r-none"], C![IF![is_error => "bg-red-500"], "rounded-r-none"],
C![&tw_classes::BUTTON], C![&tw_classes::BUTTON],

View File

@ -60,10 +60,3 @@ html {
.news-post .site-nautilus .primis-ad { .news-post .site-nautilus .primis-ad {
display: none !important; display: none !important;
} }
.news-post.site-slashdot .story-byline {
display: block !important;
height: initial !important;
overflow: auto !important;
position: static !important;
}