Compare commits

...

7 Commits

Author SHA1 Message Date
cupcakearmy 0c33f2f34c feat: enable strict CSP with nonce-based script-src
Replaces the disabled CSP middleware with a working implementation:
- Generates a per-request nonce for script-src
- Injects nonce into the inline SvelteKit bootstrap script
- Uses 'strict-dynamic' so dynamically imported modules are trusted
- SPA fallback serves index.html with CSP header
2026-06-25 21:34:25 +01:00
cupcakearmy 6b29c6f069 feat: add REDIS_PREFIX env var for shared Redis instance namespace 2026-06-25 21:21:17 +01:00
cupcakearmy 40f3559533 chore:bump version 2026-06-25 21:01:10 +01:00
cupcakearmy ac686e1935 Merge pull request #209 from unambient/typo-fix
fix typo in localization key
2026-06-25 21:42:38 +02:00
cupcakearmy 2cb984405f Merge pull request #208 from fwa-wup/main
fix #207
2026-06-25 21:40:46 +02:00
maddie 8a000ce131 change localization instances of note_to_big to note_too_big 2026-06-09 10:43:05 +00:00
Fabian W 38987efc8a fix #207 2026-06-09 12:17:58 +02:00
20 changed files with 69 additions and 38 deletions
+3 -1
View File
@@ -80,6 +80,7 @@ of the notes even if it tried to.
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | | `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. | | `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. |
| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. | | `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. |
| `REDIS_PREFIX` | `""` | Optional prefix for all Redis keys. Useful when sharing a Redis instance with other apps via ACL namespaces. |
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` | | `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | | `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | | `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
@@ -89,6 +90,7 @@ of the notes even if it tried to.
| `THEME_HOME_LINK` | `true` | Show the `/home` link in the footer. Defaults to `true`. | | `THEME_HOME_LINK` | `true` | Show the `/home` link in the footer. Defaults to `true`. |
| `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. | | `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. |
| `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both. | | `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both. |
## Deployment ## Deployment
> ️ `https` is required otherwise browsers will not support the cryptographic functions. > ️ `https` is required otherwise browsers will not support the cryptographic functions.
@@ -102,7 +104,7 @@ Docker is the easiest way. There is the [official image here](https://hub.docker
```yaml ```yaml
# docker-compose.yml # docker-compose.yml
version: '3.8' version: "3.8"
services: services:
redis: redis:
+1 -1
View File
@@ -252,7 +252,7 @@ dependencies = [
[[package]] [[package]]
name = "cryptgeon" name = "cryptgeon"
version = "2.9.2" version = "2.9.3"
dependencies = [ dependencies = [
"axum", "axum",
"bs62", "bs62",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "cryptgeon" name = "cryptgeon"
version = "2.9.2" version = "2.9.3"
authors = ["cupcakearmy <hi@nicco.io>"] authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2024" edition = "2024"
rust-version = "1.95" rust-version = "1.95"
+4
View File
@@ -34,6 +34,10 @@ pub static ref ID_LENGTH: u32 = std::env::var("ID_LENGTH")
.unwrap_or("32".to_string()) .unwrap_or("32".to_string())
.parse() .parse()
.unwrap(); .unwrap();
pub static ref REDIS_PREFIX: String = std::env::var("REDIS_PREFIX")
.unwrap_or("".to_string())
.parse()
.unwrap();
pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES") pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES")
.unwrap_or("true".to_string()) .unwrap_or("true".to_string())
.parse() .parse()
+27 -8
View File
@@ -1,16 +1,35 @@
use axum::{body::Body, extract::Request, http::HeaderValue, middleware::Next, response::Response}; use axum::{
http::HeaderValue,
response::{Html, IntoResponse, Response},
};
use ring::rand::SecureRandom;
use std::sync::OnceLock;
const CUSTOM_HEADER_NAME: &str = "Content-Security-Policy"; const CSP_POLICY: &str = "default-src 'self'; script-src 'nonce-{nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self'";
const CUSTOM_HEADER_VALUE: &str = "default-src 'self'; script-src 'report-sample' 'self'; style-src 'report-sample' 'self'; object-src 'none'; base-uri 'self'; connect-src 'self' data:; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; worker-src 'none';";
lazy_static! { fn index_html() -> &'static str {
static ref HEADER_VALUE: HeaderValue = HeaderValue::from_static(CUSTOM_HEADER_VALUE); static HTML: OnceLock<String> = OnceLock::new();
HTML.get_or_init(|| {
let path = format!("{}index.html", *crate::config::FRONTEND_PATH);
std::fs::read_to_string(&path).expect("Failed to read index.html for CSP injection")
})
} }
pub async fn add_csp_header(request: Request<Body>, next: Next) -> Response { fn generate_nonce() -> String {
let mut response = next.run(request).await; let rng = ring::rand::SystemRandom::new();
let mut bytes = [0u8; 32];
rng.fill(&mut bytes).expect("Failed to generate CSP nonce");
bs62::encode_data(&bytes)
}
pub async fn spa_fallback() -> Response {
let nonce = generate_nonce();
let csp = CSP_POLICY.replace("{nonce}", &nonce);
let html = index_html().replace("<script>", &format!("<script nonce=\"{}\">", nonce));
let mut response = Html(html).into_response();
response response
.headers_mut() .headers_mut()
.append(CUSTOM_HEADER_NAME, HEADER_VALUE.clone()); .insert("Content-Security-Policy", HeaderValue::from_str(&csp).unwrap());
response response
} }
+5 -7
View File
@@ -12,7 +12,7 @@ use tower::Layer;
use tower_http::{ use tower_http::{
compression::CompressionLayer, compression::CompressionLayer,
normalize_path::NormalizePathLayer, normalize_path::NormalizePathLayer,
services::{ServeDir, ServeFile}, services::ServeDir,
}; };
#[macro_use] #[macro_use]
@@ -50,14 +50,12 @@ async fn main() {
.merge(health_routes) .merge(health_routes)
.merge(status_routes); .merge(status_routes);
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
let serve_dir =
ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index));
let app = Router::new() let app = Router::new()
.nest("/api", api_routes) .nest("/api", api_routes)
.fallback_service(serve_dir) .fallback_service(
// Disabled for now, as svelte inlines scripts ServeDir::new(config::FRONTEND_PATH.to_string())
// .layer(middleware::from_fn(csp::add_csp_header)) .not_found_service(axum::Router::new().fallback(csp::spa_fallback)),
)
.layer(DefaultBodyLimit::max(*config::LIMIT)) .layer(DefaultBodyLimit::max(*config::LIMIT))
.layer( .layer(
CompressionLayer::new() CompressionLayer::new()
+12 -4
View File
@@ -1,6 +1,7 @@
use redis; use redis;
use redis::Commands; use redis::Commands;
use crate::config;
use crate::note::now; use crate::note::now;
use crate::note::Note; use crate::note::Note;
@@ -11,6 +12,10 @@ lazy_static! {
.unwrap(); .unwrap();
} }
fn prefixed(id: &String) -> String {
format!("{}{}", config::REDIS_PREFIX.as_str(), id)
}
fn get_connection() -> Result<redis::Connection, &'static str> { fn get_connection() -> Result<redis::Connection, &'static str> {
let client = let client =
redis::Client::open(REDIS_CLIENT.to_string()).map_err(|_| "Unable to connect to redis")?; redis::Client::open(REDIS_CLIENT.to_string()).map_err(|_| "Unable to connect to redis")?;
@@ -28,15 +33,16 @@ pub fn can_reach_redis() -> bool {
} }
pub fn set(id: &String, note: &Note) -> Result<(), &'static str> { pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
let key = prefixed(id);
let serialized = serde_json::to_string(&note.clone()).unwrap(); let serialized = serde_json::to_string(&note.clone()).unwrap();
let mut conn = get_connection()?; let mut conn = get_connection()?;
conn.set::<_, _, ()>(id, serialized) conn.set::<_, _, ()>(key.as_str(), serialized)
.map_err(|_| "Unable to set note in redis")?; .map_err(|_| "Unable to set note in redis")?;
match note.expiration { match note.expiration {
Some(e) => { Some(e) => {
let seconds = e - now(); let seconds = e - now();
conn.expire::<_, ()>(id, seconds as i64) conn.expire::<_, ()>(key.as_str(), seconds as i64)
.map_err(|_| "Unable to set expiration on note")? .map_err(|_| "Unable to set expiration on note")?
} }
None => {} None => {}
@@ -45,8 +51,9 @@ pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
} }
pub fn get(id: &String) -> Result<Option<Note>, &'static str> { pub fn get(id: &String) -> Result<Option<Note>, &'static str> {
let key = prefixed(id);
let mut conn = get_connection()?; let mut conn = get_connection()?;
let value: Option<String> = conn.get(id).map_err(|_| "Could not load note in redis")?; let value: Option<String> = conn.get(key.as_str()).map_err(|_| "Could not load note in redis")?;
match value { match value {
None => return Ok(None), None => return Ok(None),
Some(s) => { Some(s) => {
@@ -57,7 +64,8 @@ pub fn get(id: &String) -> Result<Option<Note>, &'static str> {
} }
pub fn del(id: &String) -> Result<(), &'static str> { pub fn del(id: &String) -> Result<(), &'static str> {
let key = prefixed(id);
let mut conn = get_connection()?; let mut conn = get_connection()?;
conn.del::<_, ()>(id).map_err(|_| "Unable to delete note in redis")?; conn.del::<_, ()>(key.as_str()).map_err(|_| "Unable to delete note in redis")?;
Ok(()) Ok(())
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "cryptgeon", "name": "cryptgeon",
"version": "2.9.2", "version": "2.9.3",
"homepage": "https://github.com/cupcakearmy/cryptgeon", "homepage": "https://github.com/cupcakearmy/cryptgeon",
"repository": { "repository": {
"type": "git", "type": "git",
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "Nová poznámka", "new_note": "Nová poznámka",
"new_note_notice": "<b>Dostupnost:</b><br />Poznámka není zaručeně uchována, protože je uložena pouze v paměti RAM. Pokud se paměť zaplní, nejstarší poznámky budou automaticky smazány.<br />(Obvykle to nebývá problém, ale je dobré o tom vědět.)", "new_note_notice": "<b>Dostupnost:</b><br />Poznámka není zaručeně uchována, protože je uložena pouze v paměti RAM. Pokud se paměť zaplní, nejstarší poznámky budou automaticky smazány.<br />(Obvykle to nebývá problém, ale je dobré o tom vědět.)",
"errors": { "errors": {
"note_to_big": "Poznámku nelze vytvořit. Je příliš velká.", "note_too_big": "Poznámku nelze vytvořit. Je příliš velká.",
"note_error": "Poznámku nelze vytvořit. Zkuste to prosím znovu.", "note_error": "Poznámku nelze vytvořit. Zkuste to prosím znovu.",
"max": "Max: {n}", "max": "Max: {n}",
"empty_content": "Poznámka je prázdná." "empty_content": "Poznámka je prázdná."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "Neue Notiz", "new_note": "Neue Notiz",
"new_note_notice": "<b>Wichtiger Hinweis zur Verfügbarkeit:</b><br />Es kann nicht garantiert werden, dass diese Notiz gespeichert wird, da diese <b>ausschließlich im Speicher</b> gehalten werden. Ist dieser voll, werden die ältesten Notizen entfernt.<br />(Wahrscheinlich gibt es keine derartigen Probleme, seien Sie nur vorgewarnt).", "new_note_notice": "<b>Wichtiger Hinweis zur Verfügbarkeit:</b><br />Es kann nicht garantiert werden, dass diese Notiz gespeichert wird, da diese <b>ausschließlich im Speicher</b> gehalten werden. Ist dieser voll, werden die ältesten Notizen entfernt.<br />(Wahrscheinlich gibt es keine derartigen Probleme, seien Sie nur vorgewarnt).",
"errors": { "errors": {
"note_to_big": "Notiz konnte nicht erstellt werden, da sie zu groß ist.", "note_too_big": "Notiz konnte nicht erstellt werden, da sie zu groß ist.",
"note_error": "Notiz konnte nicht erstellt werden. Bitte versuchen Sie es erneut.", "note_error": "Notiz konnte nicht erstellt werden. Bitte versuchen Sie es erneut.",
"max": "max: {n}", "max": "max: {n}",
"empty_content": "Notiz ist leer." "empty_content": "Notiz ist leer."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "new note", "new_note": "new note",
"new_note_notice": "<b>availability:</b><br />the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes will be removed.<br />(you probably will be fine, just be warned.)", "new_note_notice": "<b>availability:</b><br />the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes will be removed.<br />(you probably will be fine, just be warned.)",
"errors": { "errors": {
"note_to_big": "could not create note. note is too big", "note_too_big": "could not create note. note is too big",
"note_error": "could not create note. please try again.", "note_error": "could not create note. please try again.",
"max": "max: {n}", "max": "max: {n}",
"empty_content": "note is empty." "empty_content": "note is empty."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "nueva nota", "new_note": "nueva nota",
"new_note_notice": "<b>disponibilidad:</b><br />no se garantiza que la nota se almacene, ya que todo se guarda en la memoria RAM, si se llena se eliminarán las notas más antiguas.<br />(probablemente estará bien, solo está advertido.)", "new_note_notice": "<b>disponibilidad:</b><br />no se garantiza que la nota se almacene, ya que todo se guarda en la memoria RAM, si se llena se eliminarán las notas más antiguas.<br />(probablemente estará bien, solo está advertido.)",
"errors": { "errors": {
"note_to_big": "no se pudo crear la nota. la nota es demasiado grande", "note_too_big": "no se pudo crear la nota. la nota es demasiado grande",
"note_error": "No se ha podido crear la nota. Por favor, inténtelo de nuevo.", "note_error": "No se ha podido crear la nota. Por favor, inténtelo de nuevo.",
"max": "max: {n}", "max": "max: {n}",
"empty_content": "la nota está vacía." "empty_content": "la nota está vacía."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "nouvelle note", "new_note": "nouvelle note",
"new_note_notice": "<b>disponibilité :</b><br />il n'est pas garanti que la note reste stockée car tout est conservé dans la mémoire vive; si elle se remplit, les notes les plus anciennes seront supprimées.<br />(tout ira probablement bien, soyez juste averti.)", "new_note_notice": "<b>disponibilité :</b><br />il n'est pas garanti que la note reste stockée car tout est conservé dans la mémoire vive; si elle se remplit, les notes les plus anciennes seront supprimées.<br />(tout ira probablement bien, soyez juste averti.)",
"errors": { "errors": {
"note_to_big": "Impossible de créer une note. La note est trop grande.", "note_too_big": "Impossible de créer une note. La note est trop grande.",
"note_error": "n'a pas pu créer de note. Veuillez réessayer.", "note_error": "n'a pas pu créer de note. Veuillez réessayer.",
"max": "max: {n}", "max": "max: {n}",
"empty_content": "La note est vide." "empty_content": "La note est vide."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "nuova nota", "new_note": "nuova nota",
"new_note_notice": "<b>disponibilità:</b><br />la nota non è garantita per essere memorizzata come tutto è tenuto in ram, se si riempie le note più vecchie saranno rimosse.<br />(probabilmente andrà bene, basta essere avvertiti).", "new_note_notice": "<b>disponibilità:</b><br />la nota non è garantita per essere memorizzata come tutto è tenuto in ram, se si riempie le note più vecchie saranno rimosse.<br />(probabilmente andrà bene, basta essere avvertiti).",
"errors": { "errors": {
"note_to_big": "impossibile creare una nota. la nota è troppo grande", "note_too_big": "impossibile creare una nota. la nota è troppo grande",
"note_error": "Impossibile creare la nota. Riprova.", "note_error": "Impossibile creare la nota. Riprova.",
"max": "max: {n}", "max": "max: {n}",
"empty_content": "la nota è vuota." "empty_content": "la nota è vuota."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "新しいメモ", "new_note": "新しいメモ",
"new_note_notice": "<b>可用性: </b> <br />すべてが RAM に保持されるため、メモが保存されるとは限りません。いっぱいになると、最も古いメモが削除されます。 <br /> (大丈夫だと思いますが、ご了承ください。)", "new_note_notice": "<b>可用性: </b> <br />すべてが RAM に保持されるため、メモが保存されるとは限りません。いっぱいになると、最も古いメモが削除されます。 <br /> (大丈夫だと思いますが、ご了承ください。)",
"errors": { "errors": {
"note_to_big": "メモを作成できませんでした。メモが大きすぎる", "note_too_big": "メモを作成できませんでした。メモが大きすぎる",
"note_error": "メモを作成できませんでした。もう一度お試しください。", "note_error": "メモを作成できませんでした。もう一度お試しください。",
"max": "最大ファイルサイズ: {n}", "max": "最大ファイルサイズ: {n}",
"empty_content": "メモは空です。" "empty_content": "メモは空です。"
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "nowa notatka", "new_note": "nowa notatka",
"new_note_notice": "<b>dostępność:</b><br />nie ma gwarancji, że notatka będzie przechowywana, ponieważ wszystko jest przechowywane w pamięci RAM, jeśli się zapełni, najstarsze notatki zostaną usunięte.<br />(prawdopodobnie nic się nie stanie, ale warto ostrzec.)", "new_note_notice": "<b>dostępność:</b><br />nie ma gwarancji, że notatka będzie przechowywana, ponieważ wszystko jest przechowywane w pamięci RAM, jeśli się zapełni, najstarsze notatki zostaną usunięte.<br />(prawdopodobnie nic się nie stanie, ale warto ostrzec.)",
"errors": { "errors": {
"note_to_big": "nie można utworzyć notatki. notatka jest za duża", "note_too_big": "nie można utworzyć notatki. notatka jest za duża",
"note_error": "nie można utworzyć notatki. spróbuj ponownie.", "note_error": "nie można utworzyć notatki. spróbuj ponownie.",
"max": "maks.: {n}", "max": "maks.: {n}",
"empty_content": "notatka jest pusta." "empty_content": "notatka jest pusta."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "новая заметка", "new_note": "новая заметка",
"new_note_notice": "<b>доступность:</b><br />сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.<br />( вероятно, все будет в порядке, просто будьте осторожны.)", "new_note_notice": "<b>доступность:</b><br />сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.<br />( вероятно, все будет в порядке, просто будьте осторожны.)",
"errors": { "errors": {
"note_to_big": "нельзя создать новую заметку. заметка слишком большая", "note_too_big": "нельзя создать новую заметку. заметка слишком большая",
"note_error": "нельзя создать новую заметку. пожалуйста попробуйте позже.", "note_error": "нельзя создать новую заметку. пожалуйста попробуйте позже.",
"max": "макс: {n}", "max": "макс: {n}",
"empty_content": "пустая заметка." "empty_content": "пустая заметка."
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "新筆記", "new_note": "新筆記",
"new_note_notice": "<b>可用性:</b><br />筆記不保證被儲存,因為所有內容都保留在 RAM 中,如果 RAM 填滿,最舊的筆記將被移除。<br />(您可能會沒事,只是提醒一下。)", "new_note_notice": "<b>可用性:</b><br />筆記不保證被儲存,因為所有內容都保留在 RAM 中,如果 RAM 填滿,最舊的筆記將被移除。<br />(您可能會沒事,只是提醒一下。)",
"errors": { "errors": {
"note_to_big": "無法創建筆記。筆記過大", "note_too_big": "無法創建筆記。筆記過大",
"note_error": "無法創建筆記。請再試一次。", "note_error": "無法創建筆記。請再試一次。",
"max": "最大值:{n}", "max": "最大值:{n}",
"empty_content": "筆記內容為空。" "empty_content": "筆記內容為空。"
+1 -1
View File
@@ -25,7 +25,7 @@
"new_note": "新建密信", "new_note": "新建密信",
"new_note_notice": "<b>提醒:</b><br>密信保存在内存中,如果内存满了,则最早的密信将被删除以释放内存,因此不保证该密信的可用性。一般不会出现这种情况,无需担心。", "new_note_notice": "<b>提醒:</b><br>密信保存在内存中,如果内存满了,则最早的密信将被删除以释放内存,因此不保证该密信的可用性。一般不会出现这种情况,无需担心。",
"errors": { "errors": {
"note_to_big": "创建失败,密信过大。", "note_too_big": "创建失败,密信过大。",
"note_error": "创建失败,请稍后重试。", "note_error": "创建失败,请稍后重试。",
"max": "次数上限:{n}", "max": "次数上限:{n}",
"empty_content": "密信不能为空。" "empty_content": "密信不能为空。"
@@ -60,7 +60,6 @@
}) })
async function handlePaste(e: ClipboardEvent) { async function handlePaste(e: ClipboardEvent) {
e.preventDefault()
const data = e.clipboardData const data = e.clipboardData
if (!data) return if (!data) return
@@ -79,6 +78,7 @@
} }
if (raw.length === 0) return if (raw.length === 0) return
e.preventDefault()
const seen = new Set<string>() const seen = new Set<string>()
const pasted: File[] = [] const pasted: File[] = []
@@ -149,7 +149,7 @@
notify.success($t('home.messages.note_created')) notify.success($t('home.messages.note_created'))
} catch (e) { } catch (e) {
if (e instanceof PayloadToLargeError) { if (e instanceof PayloadToLargeError) {
notify.error($t('home.errors.note_to_big')) notify.error($t('home.errors.note_too_big'))
} else if (e instanceof EmptyContentError) { } else if (e instanceof EmptyContentError) {
notify.error($t('home.errors.empty_content')) notify.error($t('home.errors.empty_content'))
} else { } else {