diff --git a/README.md b/README.md index 0d1fca1..31668f5 100644 --- a/README.md +++ b/README.md @@ -80,14 +80,17 @@ 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_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_. | +| `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` | | `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | | `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | | `THEME_PAGE_TITLE` | `""` | Custom text the page title | | `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable | | `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. 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_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 > ℹ️ `https` is required otherwise browsers will not support the cryptographic functions. @@ -101,7 +104,7 @@ Docker is the easiest way. There is the [official image here](https://hub.docker ```yaml # docker-compose.yml -version: '3.8' +version: "3.8" services: redis: diff --git a/README_ES.md b/README_ES.md index e2e9642..f3d798c 100644 --- a/README_ES.md +++ b/README_ES.md @@ -72,6 +72,7 @@ se usa para guardar y recuperar la nota. Después la nota es encriptada con la < | `THEME_TEXT` | `""` | Texto personalizado para reemplazar la descripción bajo el logo. | | `THEME_PAGE_TITLE` | `""` | Texto personalizado para el título | | `THEME_FAVICON` | `""` | Url personalizada para el favicon. Debe ser accesible públicamente. | +| `THEME_HOME_LINK` | `true` | Mostrar el enlace `/home` en el pie de página. El valor predeterminado es `true`. | ## Despliegue diff --git a/README_zh-CN.md b/README_zh-CN.md index ea7200d..999b3df 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -56,6 +56,7 @@ _加密鸽_ 是一个受 [_PrivNote_](https://privnote.com)项目启发的安全 | `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块 | | `THEME_IMAGE` | `""` | 自定义 Logo 图片,你在这里填写的的图片链接必须是可以公开访问的。 | | `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 | +| `THEME_HOME_LINK` | `true` | 是否在页脚显示 `/home` 链接。默认为 `true`。 | ## 部署 diff --git a/mise.toml b/mise.toml index d92acf3..b3f1828 100644 --- a/mise.toml +++ b/mise.toml @@ -6,3 +6,6 @@ watchexec = "latest" [settings] idiomatic_version_file_enable_tools = ["node"] + +[env] +SIZE_LIMIT = "100mb" diff --git a/packages/backend/Cargo.lock b/packages/backend/Cargo.lock index 42c6b03..2be872b 100644 --- a/packages/backend/Cargo.lock +++ b/packages/backend/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "2.9.2" +version = "2.9.3" dependencies = [ "axum", "bs62", diff --git a/packages/backend/Cargo.toml b/packages/backend/Cargo.toml index 7e051e6..768231d 100644 --- a/packages/backend/Cargo.toml +++ b/packages/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "2.9.2" +version = "2.9.3" authors = ["cupcakearmy "] edition = "2024" rust-version = "1.95" diff --git a/packages/backend/src/config.rs b/packages/backend/src/config.rs index 318b66f..9b1d1ec 100644 --- a/packages/backend/src/config.rs +++ b/packages/backend/src/config.rs @@ -34,6 +34,10 @@ pub static ref ID_LENGTH: u32 = std::env::var("ID_LENGTH") .unwrap_or("32".to_string()) .parse() .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") .unwrap_or("true".to_string()) .parse() @@ -70,4 +74,8 @@ lazy_static! { .unwrap_or("true".to_string()) .parse() .unwrap(); + pub static ref THEME_HOME_LINK: bool = std::env::var("THEME_HOME_LINK") + .unwrap_or("true".to_string()) + .parse() + .unwrap(); } diff --git a/packages/backend/src/status/mod.rs b/packages/backend/src/status/mod.rs index fcec5d2..e15941a 100644 --- a/packages/backend/src/status/mod.rs +++ b/packages/backend/src/status/mod.rs @@ -20,6 +20,7 @@ pub struct Status { pub theme_page_title: String, pub theme_favicon: String, pub theme_new_note_notice: bool, + pub theme_home_link: bool, } pub async fn get_status() -> (StatusCode, Json) { @@ -33,6 +34,7 @@ pub async fn get_status() -> (StatusCode, Json) { imprint_url: config::IMPRINT_URL.to_string(), imprint_html: config::IMPRINT_HTML.to_string(), theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE, + theme_home_link: *config::THEME_HOME_LINK, theme_image: config::THEME_IMAGE.to_string(), theme_text: config::THEME_TEXT.to_string(), theme_page_title: config::THEME_PAGE_TITLE.to_string(), diff --git a/packages/backend/src/store.rs b/packages/backend/src/store.rs index 53fe82b..af437eb 100644 --- a/packages/backend/src/store.rs +++ b/packages/backend/src/store.rs @@ -1,6 +1,7 @@ use redis; use redis::Commands; +use crate::config; use crate::note::now; use crate::note::Note; @@ -11,6 +12,10 @@ lazy_static! { .unwrap(); } +fn prefixed(id: &String) -> String { + format!("{}{}", config::REDIS_PREFIX.as_str(), id) +} + fn get_connection() -> Result { let client = 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> { + let key = prefixed(id); let serialized = serde_json::to_string(¬e.clone()).unwrap(); let mut conn = get_connection()?; - conn.set::<_, _, ()>(id, serialized) + conn.set::<_, _, ()>(key.as_str(), serialized) .map_err(|_| "Unable to set note in redis")?; match note.expiration { Some(e) => { 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")? } None => {} @@ -45,8 +51,9 @@ pub fn set(id: &String, note: &Note) -> Result<(), &'static str> { } pub fn get(id: &String) -> Result, &'static str> { + let key = prefixed(id); let mut conn = get_connection()?; - let value: Option = conn.get(id).map_err(|_| "Could not load note in redis")?; + let value: Option = conn.get(key.as_str()).map_err(|_| "Could not load note in redis")?; match value { None => return Ok(None), Some(s) => { @@ -57,7 +64,8 @@ pub fn get(id: &String) -> Result, &'static str> { } pub fn del(id: &String) -> Result<(), &'static str> { + let key = prefixed(id); 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(()) } diff --git a/packages/cli/package.json b/packages/cli/package.json index c144854..71dbeb3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cryptgeon", - "version": "2.9.2", + "version": "2.9.3", "homepage": "https://github.com/cupcakearmy/cryptgeon", "repository": { "type": "git", diff --git a/packages/cli/src/shared/api.ts b/packages/cli/src/shared/api.ts index 6f8f010..f8a9936 100644 --- a/packages/cli/src/shared/api.ts +++ b/packages/cli/src/shared/api.ts @@ -120,6 +120,7 @@ export type Status = { theme_favicon: string theme_page_title: string theme_new_note_notice: boolean + theme_home_link: boolean } async function status() { diff --git a/packages/frontend/licenses.csv b/packages/frontend/licenses.csv index a762e5c..59c9359 100644 --- a/packages/frontend/licenses.csv +++ b/packages/frontend/licenses.csv @@ -1,8 +1,17 @@ -├─ MIT: 13 -├─ ISC: 2 -├─ BSD-3-Clause: 1 -├─ (MPL-2.0 OR Apache-2.0): 1 -├─ BSD-2-Clause: 1 +├─ MIT: 324 +├─ ISC: 84 +├─ Apache-2.0: 21 +├─ BlueOak-1.0.0: 14 +├─ BSD-3-Clause: 6 +├─ BSD-2-Clause: 4 +├─ OFL-1.1: 1 +├─ Python-2.0: 1 +├─ CC-BY-4.0: 1 +├─ UNKNOWN: 1 +├─ MPL-2.0: 1 +├─ CC-BY-3.0: 1 +├─ CC0-1.0: 1 +├─ (MIT AND CC-BY-3.0): 1 ├─ 0BSD: 1 -└─ Apache-2.0: 1 +└─ (MIT OR CC0-1.0): 1 diff --git a/packages/frontend/locales/cs.json b/packages/frontend/locales/cs.json index e174950..c7d7140 100644 --- a/packages/frontend/locales/cs.json +++ b/packages/frontend/locales/cs.json @@ -25,7 +25,7 @@ "new_note": "Nová poznámka", "new_note_notice": "Dostupnost:
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.
(Obvykle to nebývá problém, ale je dobré o tom vědět.)", "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.", "max": "Max: {n}", "empty_content": "Poznámka je prázdná." @@ -36,7 +36,10 @@ "advanced": { "explanation": "Ve výchozím nastavení se pro každou poznámku generuje bezpečné heslo. Pokud chcete, můžete si nastavit vlastní heslo, které nebude součástí odkazu.", "custom_password": "Vlastní heslo" - } + }, + "pasting": "Vkládání...", + "pasted_files": "Vložené soubory", + "remove": "Odstranit" }, "show": { "errors": { diff --git a/packages/frontend/locales/de.json b/packages/frontend/locales/de.json index 2b7f856..195335b 100644 --- a/packages/frontend/locales/de.json +++ b/packages/frontend/locales/de.json @@ -25,7 +25,7 @@ "new_note": "Neue Notiz", "new_note_notice": "Wichtiger Hinweis zur Verfügbarkeit:
Es kann nicht garantiert werden, dass diese Notiz gespeichert wird, da diese ausschließlich im Speicher gehalten werden. Ist dieser voll, werden die ältesten Notizen entfernt.
(Wahrscheinlich gibt es keine derartigen Probleme, seien Sie nur vorgewarnt).", "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.", "max": "max: {n}", "empty_content": "Notiz ist leer." @@ -36,7 +36,10 @@ "advanced": { "explanation": "Standardmäßig wird für jede Notiz ein generiertes, sicheres Passwort verwendet. Alternativ können Sie ein eigenes Kennwort festlegen, welches nicht im Link enthalten ist.", "custom_password": "Benutzerdefiniertes Passwort" - } + }, + "pasting": "Einfügen...", + "pasted_files": "Eingefügte Dateien", + "remove": "Entfernen" }, "show": { "errors": { diff --git a/packages/frontend/locales/en.json b/packages/frontend/locales/en.json index 5e3cbc1..cb1f13c 100644 --- a/packages/frontend/locales/en.json +++ b/packages/frontend/locales/en.json @@ -25,7 +25,7 @@ "new_note": "new note", "new_note_notice": "availability:
the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes will be removed.
(you probably will be fine, just be warned.)", "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.", "max": "max: {n}", "empty_content": "note is empty." @@ -36,7 +36,10 @@ "advanced": { "explanation": "By default, a securely generated password is used for each note. You can however also choose your own password, which is not included in the link.", "custom_password": "custom password" - } + }, + "pasting": "Pasting...", + "pasted_files": "Pasted Files", + "remove": "Remove" }, "show": { "errors": { diff --git a/packages/frontend/locales/es.json b/packages/frontend/locales/es.json index b482c22..49f3b2f 100644 --- a/packages/frontend/locales/es.json +++ b/packages/frontend/locales/es.json @@ -25,7 +25,7 @@ "new_note": "nueva nota", "new_note_notice": "disponibilidad:
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.
(probablemente estará bien, solo está advertido.)", "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.", "max": "max: {n}", "empty_content": "la nota está vacía." @@ -36,7 +36,10 @@ "advanced": { "explanation": "Por defecto, se utiliza una contraseña generada de forma segura para cada nota. No obstante, también puede elegir su propia contraseña, la cual no se incluye en el enlace.", "custom_password": "contraseña personalizada" - } + }, + "pasting": "Pegando...", + "pasted_files": "Archivos pegados", + "remove": "Eliminar" }, "show": { "errors": { diff --git a/packages/frontend/locales/fr.json b/packages/frontend/locales/fr.json index 41a7b50..9dad9aa 100644 --- a/packages/frontend/locales/fr.json +++ b/packages/frontend/locales/fr.json @@ -25,7 +25,7 @@ "new_note": "nouvelle note", "new_note_notice": "disponibilité :
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.
(tout ira probablement bien, soyez juste averti.)", "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.", "max": "max: {n}", "empty_content": "La note est vide." @@ -36,7 +36,10 @@ "advanced": { "explanation": "Par défaut, un mot de passe généré de manière sécurisée est utilisé pour chaque note. Vous pouvez toutefois choisir votre propre mot de passe, qui n'est pas inclus dans le lien.", "custom_password": "mot de passe personnalisé" - } + }, + "pasting": "Collage...", + "pasted_files": "Fichiers collés", + "remove": "Supprimer" }, "show": { "errors": { diff --git a/packages/frontend/locales/it.json b/packages/frontend/locales/it.json index b2636b9..e95d8ed 100644 --- a/packages/frontend/locales/it.json +++ b/packages/frontend/locales/it.json @@ -25,7 +25,7 @@ "new_note": "nuova nota", "new_note_notice": "disponibilità:
la nota non è garantita per essere memorizzata come tutto è tenuto in ram, se si riempie le note più vecchie saranno rimosse.
(probabilmente andrà bene, basta essere avvertiti).", "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.", "max": "max: {n}", "empty_content": "la nota è vuota." @@ -36,7 +36,10 @@ "advanced": { "explanation": "Per impostazione predefinita, per ogni nota viene utilizzata una password generata in modo sicuro. È tuttavia possibile scegliere la propria password, che non è inclusa nel link.", "custom_password": "password personalizzata" - } + }, + "pasting": "Incollando...", + "pasted_files": "File incollati", + "remove": "Rimuovi" }, "show": { "errors": { diff --git a/packages/frontend/locales/ja.json b/packages/frontend/locales/ja.json index 97833e2..f76655c 100644 --- a/packages/frontend/locales/ja.json +++ b/packages/frontend/locales/ja.json @@ -25,7 +25,7 @@ "new_note": "新しいメモ", "new_note_notice": "可用性:
すべてが RAM に保持されるため、メモが保存されるとは限りません。いっぱいになると、最も古いメモが削除されます。
(大丈夫だと思いますが、ご了承ください。)", "errors": { - "note_to_big": "メモを作成できませんでした。メモが大きすぎる", + "note_too_big": "メモを作成できませんでした。メモが大きすぎる", "note_error": "メモを作成できませんでした。もう一度お試しください。", "max": "最大ファイルサイズ: {n}", "empty_content": "メモは空です。" @@ -36,7 +36,10 @@ "advanced": { "explanation": "デフォルトでは、安全に生成されたパスワードが各ノートに使用されます。しかし、リンクに含まれない独自のパスワードを選択することもできます。", "custom_password": "カスタムパスワード" - } + }, + "pasting": "貼り付け中...", + "pasted_files": "貼り付けたファイル", + "remove": "削除" }, "show": { "errors": { diff --git a/packages/frontend/locales/pl.json b/packages/frontend/locales/pl.json index 2dfe320..2ea0cb2 100644 --- a/packages/frontend/locales/pl.json +++ b/packages/frontend/locales/pl.json @@ -6,8 +6,8 @@ "create": "utwórz", "loading": "ładowanie", "mode": "tryb", - "views": "{n, plural, =0 {wyświetleń} =1 {1 wyświetlenie} other {# wyświetleń}}", - "minutes": "{n, plural, =0 {minut} =1 {1 minuta} other {# minuty}}", + "views": "{n, plural, =0 {wyświetleń} =1 {1 wyświetlenie} other {{n} wyświetleń}}", + "minutes": "{n, plural, =0 {minut} =1 {1 minuta} other {{n} minuty}}", "max": "maks.", "share_link": "link udostępniania", "copy_clipboard": "kopiuj do schowka", @@ -25,7 +25,7 @@ "new_note": "nowa notatka", "new_note_notice": "dostępność:
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.
(prawdopodobnie nic się nie stanie, ale warto ostrzec.)", "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.", "max": "maks.: {n}", "empty_content": "notatka jest pusta." @@ -36,7 +36,10 @@ "advanced": { "explanation": "Domyślnie dla każdej notatki używane jest bezpiecznie wygenerowane hasło. Możesz jednak wybrać własne hasło, które nie jest uwzględnione w linku.", "custom_password": "własne hasło" - } + }, + "pasting": "Wklejanie...", + "pasted_files": "Wklejone pliki", + "remove": "Usuń" }, "show": { "errors": { diff --git a/packages/frontend/locales/ru.json b/packages/frontend/locales/ru.json index c2f33ac..84ea5f1 100644 --- a/packages/frontend/locales/ru.json +++ b/packages/frontend/locales/ru.json @@ -25,7 +25,7 @@ "new_note": "новая заметка", "new_note_notice": "доступность:
сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.
( вероятно, все будет в порядке, просто будьте осторожны.)", "errors": { - "note_to_big": "нельзя создать новую заметку. заметка слишком большая", + "note_too_big": "нельзя создать новую заметку. заметка слишком большая", "note_error": "нельзя создать новую заметку. пожалуйста попробуйте позже.", "max": "макс: {n}", "empty_content": "пустая заметка." @@ -36,7 +36,10 @@ "advanced": { "explanation": "По умолчанию для каждой заметки используется безопасно сгенерированный пароль. Однако вы также можете выбрать свой собственный пароль, который не включен в ссылку.", "custom_password": "пользовательский пароль" - } + }, + "pasting": "Вставка...", + "pasted_files": "Вставленные файлы", + "remove": "Удалить" }, "show": { "errors": { diff --git a/packages/frontend/locales/zh-TW.json b/packages/frontend/locales/zh-TW.json index 819aea0..24e1896 100644 --- a/packages/frontend/locales/zh-TW.json +++ b/packages/frontend/locales/zh-TW.json @@ -25,7 +25,7 @@ "new_note": "新筆記", "new_note_notice": "可用性:
筆記不保證被儲存,因為所有內容都保留在 RAM 中,如果 RAM 填滿,最舊的筆記將被移除。
(您可能會沒事,只是提醒一下。)", "errors": { - "note_to_big": "無法創建筆記。筆記過大", + "note_too_big": "無法創建筆記。筆記過大", "note_error": "無法創建筆記。請再試一次。", "max": "最大值:{n}", "empty_content": "筆記內容為空。" @@ -36,7 +36,10 @@ "advanced": { "explanation": "預設情況下,每個筆記都會使用安全生成的密碼。您也可以選擇自己的密碼,該密碼不會包含在連結中。", "custom_password": "自定義密碼" - } + }, + "pasting": "正在粘貼...", + "pasted_files": "粘貼的檔案", + "remove": "移除" }, "show": { "errors": { diff --git a/packages/frontend/locales/zh.json b/packages/frontend/locales/zh.json index be5e506..ae2b7a4 100644 --- a/packages/frontend/locales/zh.json +++ b/packages/frontend/locales/zh.json @@ -25,7 +25,7 @@ "new_note": "新建密信", "new_note_notice": "提醒:
密信保存在内存中,如果内存满了,则最早的密信将被删除以释放内存,因此不保证该密信的可用性。一般不会出现这种情况,无需担心。", "errors": { - "note_to_big": "创建失败,密信过大。", + "note_too_big": "创建失败,密信过大。", "note_error": "创建失败,请稍后重试。", "max": "次数上限:{n}", "empty_content": "密信不能为空。" @@ -36,7 +36,10 @@ "advanced": { "explanation": "默认情况下,每个笔记都使用安全生成的密码。但是,您也可以选择您自己的密码,该密码未包含在链接中。", "custom_password": "自定义密码" - } + }, + "pasting": "正在粘贴...", + "pasted_files": "粘贴的文件", + "remove": "移除" }, "show": { "errors": { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index f47f64e..29a0a45 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,7 +7,7 @@ "build": "vite build", "preview": "vite preview", "check": "svelte-check --tsconfig tsconfig.json", - "licenses": "license-checker --summary > licenses.csv", + "licenses": "license-checker-rseidelsohn --summary > licenses.csv", "locale:download": "node scripts/locale.js", "test:prepare": "pnpm run build" }, @@ -20,6 +20,7 @@ "@zerodevx/svelte-toast": "^0.9.6", "adm-zip": "^0.5.17", "dotenv": "^17.4.2", + "license-checker-rseidelsohn": "^5.0.1", "svelte": "^5.55.9", "svelte-check": "^4.4.8", "svelte-intl-precompile": "^0.12.3", diff --git a/packages/frontend/src/lib/ui/PastedFilesPreview.svelte b/packages/frontend/src/lib/ui/PastedFilesPreview.svelte new file mode 100644 index 0000000..ba72838 --- /dev/null +++ b/packages/frontend/src/lib/ui/PastedFilesPreview.svelte @@ -0,0 +1,155 @@ + + +{#if files.length > 0} +
+

{$t('home.pasted_files')}

+
+ {#each files as entry, index} +
+ {#if isImage(entry.type)} + {entry.name} + {:else} +
+
+ {entry.name.split('.').pop()?.toUpperCase() || entry.type} +
+
+ {/if} +
{entry.name}
+
+ +
+
+ {/each} +
+
+{/if} + + diff --git a/packages/frontend/src/lib/ui/ShowNote.svelte b/packages/frontend/src/lib/ui/ShowNote.svelte index 61eb1ec..1a3bee0 100644 --- a/packages/frontend/src/lib/ui/ShowNote.svelte +++ b/packages/frontend/src/lib/ui/ShowNote.svelte @@ -80,6 +80,15 @@ {file.type} - {prettyBytes(file.size)} + {#if file.type.startsWith('image/')} + {#key file.name} + {file.name} + {/key} + {/if} {/each} {/if} @@ -130,4 +139,13 @@ margin-bottom: 0.5rem; word-wrap: break-word; } + + .preview { + display: block; + max-width: 100%; + max-height: 60vh; + margin-bottom: 0.5rem; + border-radius: 0.25rem; + border: 2px solid var(--ui-bg-1); + } diff --git a/packages/frontend/src/lib/views/Create.svelte b/packages/frontend/src/lib/views/Create.svelte index a79aad6..a299a1e 100644 --- a/packages/frontend/src/lib/views/Create.svelte +++ b/packages/frontend/src/lib/views/Create.svelte @@ -10,6 +10,7 @@ import FileUpload from '$lib/ui/FileUpload.svelte' import Loader from '$lib/ui/Loader.svelte' import MaxSize from '$lib/ui/MaxSize.svelte' + import PastedFilesPreview from '$lib/ui/PastedFilesPreview.svelte' import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte' import Switch from '$lib/ui/Switch.svelte' import TextArea from '$lib/ui/TextArea.svelte' @@ -29,6 +30,7 @@ let customPassword: string | null = $state(null) let description = $state('') let loading: string | null = $state(null) + let isPasting = $state(false) $effect(() => { if (!advanced) { @@ -57,6 +59,62 @@ } }) + async function handlePaste(e: ClipboardEvent) { + const data = e.clipboardData + if (!data) return + + const raw: File[] = [] + + if (data.files.length) { + raw.push(...Array.from(data.files)) + } + + for (let i = 0; i < data.items.length; i++) { + const item = data.items[i] + if (item.kind === 'file') { + const file = item.getAsFile() + if (file) raw.push(file) + } + } + + if (raw.length === 0) return + e.preventDefault() + + const seen = new Set() + const pasted: File[] = [] + for (const f of raw) { + const key = `${f.name}|${f.size}` + if (!seen.has(key)) { + seen.add(key) + pasted.push(f) + } + } + + isPasting = true + + const dtos: FileDTO[] = await Promise.all( + pasted.map(async (file) => { + const ext = file.name.includes('.') ? '' : `.${file.type.split('/')[1] || 'bin'}` + const name = + file.name || `pasted-file-${Date.now()}-${Math.round(Math.random() * 1000)}${ext}` + const renamed = new File([file], name, { type: file.type }) + return { + name: renamed.name, + size: renamed.size, + type: renamed.type, + contents: new Uint8Array(await renamed.arrayBuffer()), + } + }) + ) + + if (dtos.length > 0) { + if (!isFile) isFile = true + files = [...files, ...dtos] + } + + isPasting = false + } + class EmptyContentError extends Error {} async function submit(e: SubmitEvent) { @@ -91,7 +149,7 @@ notify.success($t('home.messages.note_created')) } catch (e) { 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) { notify.error($t('home.errors.empty_content')) } else { @@ -110,18 +168,28 @@

{@html $status?.theme_text || $t('home.intro')}

-
+
- {#if isFile} - - {:else} -