From 5fe0ef6edf656e5ecb03239411bfaabdb8d1b4fa Mon Sep 17 00:00:00 2001 From: FliegendeWurst Date: Thu, 9 Jan 2025 17:33:02 +0100 Subject: [PATCH] Allow setting paste title --- src/db.rs | 24 +++++++++++++++----- src/migrations/0007-add-title-column.sql | 1 + src/pages.rs | 8 +++++-- src/routes/form.rs | 3 +++ src/routes/json.rs | 2 ++ src/routes/mod.rs | 5 +++++ src/routes/paste.rs | 19 ++++++++++++---- src/themes/style.css | 28 +++++++++++++++++++----- templates/base.html | 4 ++-- templates/index.html | 3 +++ templates/paste.html | 5 +++++ 11 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 src/migrations/0007-add-title-column.sql diff --git a/src/db.rs b/src/db.rs index 2a7cf08..044a4be 100644 --- a/src/db.rs +++ b/src/db.rs @@ -43,6 +43,7 @@ static MIGRATIONS: LazyLock = LazyLock::new(|| { ), M::up(include_str!("migrations/0005-drop-text-column.sql")), M::up(include_str!("migrations/0006-add-nonce-column.sql")), + M::up(include_str!("migrations/0007-add-title-column.sql")), ]) }); @@ -86,6 +87,8 @@ pub mod write { pub uid: Option, /// Optional password to encrypt the entry pub password: Option, + /// Title + pub title: Option, } /// A compressed entry to be inserted. @@ -163,6 +166,8 @@ pub mod read { pub uid: Option, /// Nonce for this entry pub nonce: Option>, + /// Title + pub title: Option, } /// Potentially decrypted but still compressed entry @@ -173,6 +178,8 @@ pub mod read { must_be_deleted: bool, /// User identifier that inserted the entry uid: Option, + /// Title + title: Option, } /// An entry read from the database. @@ -183,6 +190,8 @@ pub mod read { pub must_be_deleted: bool, /// User identifier that inserted the entry pub uid: Option, + /// Title + pub title: Option, } impl DatabaseEntry { @@ -196,6 +205,7 @@ pub mod read { data: self.data, must_be_deleted: self.must_be_deleted, uid: self.uid, + title: self.title, }), (Some(nonce), Some(password)) => { let encrypted = Encrypted::new(self.data, nonce); @@ -204,6 +214,7 @@ pub mod read { data: decrypted, must_be_deleted: self.must_be_deleted, uid: self.uid, + title: self.title, }) } } @@ -225,6 +236,7 @@ pub mod read { text, uid: self.uid, must_be_deleted: self.must_be_deleted, + title: self.title, }) } } @@ -255,18 +267,19 @@ impl Database { spawn_blocking(move || match entry.expires { None => conn.lock().execute( - "INSERT INTO entries (id, uid, data, burn_after_reading, nonce) VALUES (?1, ?2, ?3, ?4, ?5)", - params![id, entry.uid, data, entry.burn_after_reading, nonce], + "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, title) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![id, entry.uid, data, entry.burn_after_reading, nonce, entry.title], ), Some(expires) => conn.lock().execute( - "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, expires) VALUES (?1, ?2, ?3, ?4, ?5, datetime('now', ?6))", + "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, expires, title) VALUES (?1, ?2, ?3, ?4, ?5, datetime('now', ?6), ?7)", params![ id, entry.uid, data, entry.burn_after_reading, nonce, - format!("{expires} seconds") + format!("{expires} seconds"), + entry.title, ], ), }) @@ -282,7 +295,7 @@ impl Database { let entry = spawn_blocking(move || { conn.lock().query_row( - "SELECT data, burn_after_reading, uid, nonce, expires < datetime('now') FROM entries WHERE id=?1", + "SELECT data, burn_after_reading, uid, nonce, expires < datetime('now'), title FROM entries WHERE id=?1", params![id_as_u32], |row| { Ok(read::DatabaseEntry { @@ -291,6 +304,7 @@ impl Database { uid: row.get(2)?, nonce: row.get(3)?, expired: row.get::<_, Option>(4)?.unwrap_or(false), + title: row.get::<_, Option>(5)?, }) }, ) diff --git a/src/migrations/0007-add-title-column.sql b/src/migrations/0007-add-title-column.sql new file mode 100644 index 0000000..c63626a --- /dev/null +++ b/src/migrations/0007-add-title-column.sql @@ -0,0 +1 @@ +ALTER TABLE entries ADD COLUMN title TEXT; diff --git a/src/pages.rs b/src/pages.rs index 50461c1..6477dbb 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -134,11 +134,12 @@ pub struct Paste<'a> { ext: String, can_delete: bool, html: String, + title: String, } impl Paste<'_> { /// Construct new paste view from cache `key` and paste `html`. - pub fn new(key: CacheKey, html: Html, can_delete: bool) -> Self { + pub fn new(key: CacheKey, html: Html, can_delete: bool, title: String) -> Self { let html = html.into_inner(); Self { @@ -148,6 +149,7 @@ impl Paste<'_> { ext: key.ext, can_delete, html, + title, } } } @@ -202,11 +204,12 @@ pub struct Qr<'a> { ext: String, can_delete: bool, code: qrcodegen::QrCode, + title: String, } impl Qr<'_> { /// Construct new QR code view from `code`. - pub fn new(code: qrcodegen::QrCode, key: CacheKey) -> Self { + pub fn new(code: qrcodegen::QrCode, key: CacheKey, title: String) -> Self { Self { meta: &env::METADATA, base_path: &env::BASE_PATH, @@ -214,6 +217,7 @@ impl Qr<'_> { ext: key.ext, code, can_delete: false, + title, } } diff --git a/src/routes/form.rs b/src/routes/form.rs index 56693fd..846759e 100644 --- a/src/routes/form.rs +++ b/src/routes/form.rs @@ -16,12 +16,14 @@ pub struct Entry { pub extension: Option, pub expires: String, pub password: String, + pub title: String, } impl From for write::Entry { fn from(entry: Entry) -> Self { let burn_after_reading = Some(entry.expires == "burn"); let password = (!entry.password.is_empty()).then_some(entry.password); + let title = (!entry.title.is_empty()).then_some(entry.title); let expires = match entry.expires.parse::() { Err(_) => None, @@ -35,6 +37,7 @@ impl From for write::Entry { burn_after_reading, uid: None, password, + title, } } } diff --git a/src/routes/json.rs b/src/routes/json.rs index a6962af..698e02a 100644 --- a/src/routes/json.rs +++ b/src/routes/json.rs @@ -17,6 +17,7 @@ pub struct Entry { pub expires: Option, pub burn_after_reading: Option, pub password: Option, + pub title: Option, } #[derive(Deserialize, Serialize)] @@ -33,6 +34,7 @@ impl From for write::Entry { burn_after_reading: entry.burn_after_reading, uid: None, password: entry.password, + title: entry.title, } } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 322c963..387d05e 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -54,6 +54,7 @@ mod tests { extension: Some("rs".to_string()), expires: "0".to_string(), password: "".to_string(), + title: "".to_string(), }; let res = client.post(BASE_PATH.path()).form(&data).send().await?; @@ -115,6 +116,7 @@ mod tests { extension: None, expires: "burn".to_string(), password: "".to_string(), + title: "".to_string(), }; let res = client.post(BASE_PATH.path()).form(&data).send().await?; @@ -154,6 +156,7 @@ mod tests { extension: None, expires: "burn".to_string(), password: password.to_string(), + title: "".to_string(), }; let res = client.post(BASE_PATH.path()).form(&data).send().await?; @@ -271,6 +274,7 @@ mod tests { extension: None, expires: "0".to_string(), password: "".to_string(), + title: "".to_string(), }; let res = client.post(BASE_PATH.path()).form(&data).send().await?; @@ -311,6 +315,7 @@ mod tests { extension: None, expires: "0".to_string(), password: "".to_string(), + title: "".to_string(), }; let res = client.post(BASE_PATH.path()).form(&data).send().await?; diff --git a/src/routes/paste.rs b/src/routes/paste.rs index 9dbce8f..ee3ad49 100644 --- a/src/routes/paste.rs +++ b/src/routes/paste.rs @@ -66,13 +66,14 @@ async fn get_qr( state: AppState, key: CacheKey, headers: HeaderMap, + title: String, ) -> Result, pages::ErrorResponse<'static>> { let id = key.id(); let qr_code = tokio::task::spawn_blocking(move || qr_code_from(state, &headers, &id)) .await .map_err(Error::from)??; - Ok(pages::Qr::new(qr_code, key)) + Ok(pages::Qr::new(qr_code, key, title)) } fn get_download( @@ -116,13 +117,17 @@ async fn get_html( if let Some(html) = state.cache.get(&key) { tracing::trace!(?key, "found cached item"); - return Ok(pages::Paste::new(key, html, can_delete).into_response()); + return Ok( + pages::Paste::new(key, html, can_delete, entry.title.unwrap_or_default()) + .into_response(), + ); } // TODO: turn this upside-down, i.e. cache it but only return a cached version if we were able // to decrypt the content. Highlighting is probably still much slower than decryption. let can_be_cached = !entry.must_be_deleted; let ext = key.ext.clone(); + let title = entry.title.clone().unwrap_or_default(); let html = Html::from(entry, ext).await?; if can_be_cached && !is_protected { @@ -130,7 +135,7 @@ async fn get_html( state.cache.put(key.clone(), html.clone()); } - Ok(pages::Paste::new(key, html, can_delete).into_response()) + Ok(pages::Paste::new(key, html, can_delete, title).into_response()) } pub async fn get( @@ -161,7 +166,13 @@ pub async fn get( match query.fmt { Some(Format::Raw) => return Ok(entry.text.into_response()), - Some(Format::Qr) => return Ok(get_qr(state, key, headers).await.into_response()), + Some(Format::Qr) => { + return Ok( + get_qr(state, key, headers, entry.title.clone().unwrap_or_default()) + .await + .into_response(), + ) + } None => (), } diff --git a/src/themes/style.css b/src/themes/style.css index 9788e15..dd43412 100644 --- a/src/themes/style.css +++ b/src/themes/style.css @@ -49,7 +49,7 @@ body { header { display: flex; - justify-content: flex-end; + justify-content: space-between; align-items: center; padding: 0 1em 0 1em; user-select: none; @@ -62,14 +62,17 @@ main { } #nav-title { - margin-right: auto; - margin-top: 16px; - margin-bottom: 16px; + /* to cut off very long titles */ + overflow: hidden; + display: flex; + flex-direction: row; + align-items: baseline; } header ul { margin: 0; padding: 0; + display: flex; } header li { @@ -92,6 +95,21 @@ header .navigation:hover { cursor: pointer; } +h1 { + display: inline-block; + margin-top: 0px; + margin-bottom: 0px; + font-size: 1.125rem; + max-width: 80vw; + text-overflow: ellipsis; + overflow: hidden; + vertical-align: text-bottom; + padding-left: 16px; + padding-right: 16px; + text-wrap-mode: nowrap; + user-select: text; +} + button { font-family: "JetBrains Mono", monospace; font-size: 1.125rem; @@ -189,7 +207,7 @@ a, a:visited, a:hover { grid-row: 1/2; } -.expiration-list, .password, .paste-button { +.expiration-list, .password, .paste-button, .title { margin-top: 2em; } diff --git a/templates/base.html b/templates/base.html index 0ed6f0c..190f604 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,7 +4,7 @@ - {{ meta.title }} + {{ meta.title }}{% block title_content %}{% endblock %} @@ -14,7 +14,7 @@
- home + home{% block title %}{% endblock %}
+
+
+
diff --git a/templates/paste.html b/templates/paste.html index ed5d901..e4b63c7 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -1,5 +1,10 @@ {% extends "base.html" %} +{% block title %} + {% if title.len() > 0 %}

{{ title }}

{% endif %} +{% endblock %} +{% block title_content %}{% if title.len() > 0 %}: {{ title }}{% endif %}{% endblock %} + {% block head %}