mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2024-12-22 08:16:28 +00:00
2.0.1 (#40)
* locale from lokalise * version bump * update dependencies * show size with overhead * use base64 instead of hex and refactor a bit * changelog & readme * size limit * locale * add sync for svelte * refarcor create & add loading animation * changelog
This commit is contained in:
parent
9590c9b567
commit
a5d98b76bd
25
CHANGELOG.md
25
CHANGELOG.md
@ -5,27 +5,40 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.0.1] - 2022-07-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Max file size on the client now.
|
||||||
|
- Loading information.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Changed encoding from hex to base64.
|
||||||
|
- Chinese language code.
|
||||||
|
- Notable speed improvements for big files.
|
||||||
|
|
||||||
## [2.0.0] - 2022-07-16
|
## [2.0.0] - 2022-07-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Theming for logo and description text
|
- Theming for logo and description text.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Moved to redis
|
- Moved to redis.
|
||||||
- New html sanitizing library
|
- New html sanitizing library.
|
||||||
|
|
||||||
## [2.0.0-rc.0] - 2022-07-15
|
## [2.0.0-rc.0] - 2022-07-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Theming for logo and description text
|
- Theming for logo and description text.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Moved to redis
|
- Moved to redis.
|
||||||
- New html sanitizing library
|
- New html sanitizing library.
|
||||||
|
|
||||||
## [1.5.3] - 2022-06-07
|
## [1.5.3] - 2022-06-07
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ WORKDIR /tmp
|
|||||||
RUN npm install -g pnpm@7
|
RUN npm install -g pnpm@7
|
||||||
COPY ./frontend ./
|
COPY ./frontend ./
|
||||||
RUN pnpm install
|
RUN pnpm install
|
||||||
|
RUN pnpm exec svelte-kit sync
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@ of the notes even if it tried to.
|
|||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
| ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `REDIS` | `redis://redis/` | Redis URL to connect to. |
|
| `REDIS` | `redis://redis/` | Redis URL to connect to. |
|
||||||
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/). `512 MiB` is the maximum allowed |
|
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/). <br> `512 MiB` is the maximum allowed. <br> The frontend will show that number including the ~35% encoding overhead. |
|
||||||
| `MAX_VIEWS` | `100` | Maximal number of views. |
|
| `MAX_VIEWS` | `100` | Maximal number of views. |
|
||||||
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
|
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
|
||||||
| `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. |
|
||||||
|
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@ -424,7 +424,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
authors = ["cupcakearmy <hi@nicco.io>"]
|
authors = ["cupcakearmy <hi@nicco.io>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@ -4,13 +4,17 @@
|
|||||||
"file": "Datei",
|
"file": "Datei",
|
||||||
"advanced": "erweitert",
|
"advanced": "erweitert",
|
||||||
"create": "erstellen",
|
"create": "erstellen",
|
||||||
"loading": "Läd...",
|
"loading": "läd",
|
||||||
"mode": "Modus",
|
"mode": "Modus",
|
||||||
"views": "{n, plural, =0 {Ansichten} =1 {1 Ansicht} other {# Ansichten}}",
|
"views": "{n, plural, =0 {Ansichten} =1 {1 Ansicht} other {# Ansichten}}",
|
||||||
"minutes": "{n, plural, =0 {Minuten} =1 {1 Minute} other {# Minuten}}",
|
"minutes": "{n, plural, =0 {Minuten} =1 {1 Minute} other {# Minuten}}",
|
||||||
"max": "max",
|
"max": "max",
|
||||||
"share_link": "Link teilen",
|
"share_link": "Link teilen",
|
||||||
"copy_clipboard": "in die Zwischenablage kopieren"
|
"copy_clipboard": "in die Zwischenablage kopieren",
|
||||||
|
"encrypting": "verschlüsseln",
|
||||||
|
"decrypting": "entschlüsselt",
|
||||||
|
"uploading": "hochladen",
|
||||||
|
"downloading": "wird heruntergeladen"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"intro": "Senden Sie ganz einfach <i>vollständig verschlüsselte</i>, sichere Notizen oder Dateien mit einem Klick. Erstellen Sie einfach eine Notiz und teilen Sie den Link.",
|
"intro": "Senden Sie ganz einfach <i>vollständig verschlüsselte</i>, sichere Notizen oder Dateien mit einem Klick. Erstellen Sie einfach eine Notiz und teilen Sie den Link.",
|
||||||
@ -28,7 +32,8 @@
|
|||||||
"show": {
|
"show": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"not_found": "wurde nicht gefunden oder wurde bereits gelöscht.",
|
"not_found": "wurde nicht gefunden oder wurde bereits gelöscht.",
|
||||||
"decryption_failed": "falsches Passwort. konnte nicht entziffert werden. wahrscheinlich ein defekter Link. Notiz wurde zerstört."
|
"decryption_failed": "falsches Passwort. konnte nicht entziffert werden. wahrscheinlich ein defekter Link. Notiz wurde zerstört.",
|
||||||
|
"unsupported_type": "nicht unterstützter Notiztyp."
|
||||||
},
|
},
|
||||||
"explanation": "Klicken Sie unten, um die Notiz anzuzeigen und zu löschen, wenn der Zähler sein Limit erreicht hat",
|
"explanation": "Klicken Sie unten, um die Notiz anzuzeigen und zu löschen, wenn der Zähler sein Limit erreicht hat",
|
||||||
"show_note": "Notiz anzeigen",
|
"show_note": "Notiz anzeigen",
|
||||||
|
@ -4,13 +4,17 @@
|
|||||||
"file": "file",
|
"file": "file",
|
||||||
"advanced": "advanced",
|
"advanced": "advanced",
|
||||||
"create": "create",
|
"create": "create",
|
||||||
"loading": "loading...",
|
"loading": "loading",
|
||||||
"mode": "mode",
|
"mode": "mode",
|
||||||
"views": "{n, plural, =0 {views} =1 {1 view} other {# views}}",
|
"views": "{n, plural, =0 {views} =1 {1 view} other {# views}}",
|
||||||
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
|
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
|
||||||
"max": "max",
|
"max": "max",
|
||||||
"share_link": "share link",
|
"share_link": "share link",
|
||||||
"copy_clipboard": "copy to clipboard"
|
"copy_clipboard": "copy to clipboard",
|
||||||
|
"encrypting": "encrypting",
|
||||||
|
"decrypting": "decrypting",
|
||||||
|
"uploading": "uploading",
|
||||||
|
"downloading": "downloading"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"intro": "Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and share the link.",
|
"intro": "Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and share the link.",
|
||||||
@ -28,7 +32,8 @@
|
|||||||
"show": {
|
"show": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"not_found": "note was not found or was already deleted.",
|
"not_found": "note was not found or was already deleted.",
|
||||||
"decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed."
|
"decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed.",
|
||||||
|
"unsupported_type": "unsupported note type."
|
||||||
},
|
},
|
||||||
"explanation": "click below to show and delete the note if the counter has reached it's limit",
|
"explanation": "click below to show and delete the note if the counter has reached it's limit",
|
||||||
"show_note": "show note",
|
"show_note": "show note",
|
||||||
|
@ -4,13 +4,17 @@
|
|||||||
"file": "archivo",
|
"file": "archivo",
|
||||||
"advanced": "avanzado",
|
"advanced": "avanzado",
|
||||||
"create": "crear",
|
"create": "crear",
|
||||||
"loading": "cargando...",
|
"loading": "cargando",
|
||||||
"mode": "modo",
|
"mode": "modo",
|
||||||
"views": "{n, plural, =0 {vistas} =1 {1 vista} other {# vistas}}",
|
"views": "{n, plural, =0 {vistas} =1 {1 vista} other {# vistas}}",
|
||||||
"minutes": "{n, plural, =0 {minutos} =1 {1 minuto} other {# minutos}}",
|
"minutes": "{n, plural, =0 {minutos} =1 {1 minuto} other {# minutos}}",
|
||||||
"max": "max",
|
"max": "max",
|
||||||
"share_link": "compartir enlace",
|
"share_link": "compartir enlace",
|
||||||
"copy_clipboard": "copiar al portapapeles"
|
"copy_clipboard": "copiar al portapapeles",
|
||||||
|
"encrypting": "encriptando",
|
||||||
|
"decrypting": "descifrando",
|
||||||
|
"uploading": "cargando",
|
||||||
|
"downloading": "descargando"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"intro": "Envía fácilmente notas o archivos <i>totalmente encriptados</i> y seguros con un solo clic. Solo tienes que crear una nota y compartir el enlace.",
|
"intro": "Envía fácilmente notas o archivos <i>totalmente encriptados</i> y seguros con un solo clic. Solo tienes que crear una nota y compartir el enlace.",
|
||||||
@ -28,7 +32,8 @@
|
|||||||
"show": {
|
"show": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"not_found": "la nota no se encontró o ya fue borrada.",
|
"not_found": "la nota no se encontró o ya fue borrada.",
|
||||||
"decryption_failed": "contraseña incorrecta. no se pudo descifrar. probablemente un enlace roto. la nota fue destruida."
|
"decryption_failed": "contraseña incorrecta. no se pudo descifrar. probablemente un enlace roto. la nota fue destruida.",
|
||||||
|
"unsupported_type": "tipo de nota no compatible."
|
||||||
},
|
},
|
||||||
"explanation": "pulse abajo para mostrar y borrar la nota si el contador ha llegado a su límite",
|
"explanation": "pulse abajo para mostrar y borrar la nota si el contador ha llegado a su límite",
|
||||||
"show_note": "mostrar nota",
|
"show_note": "mostrar nota",
|
||||||
|
@ -4,13 +4,17 @@
|
|||||||
"file": "fichier",
|
"file": "fichier",
|
||||||
"advanced": "avancé",
|
"advanced": "avancé",
|
||||||
"create": "créer",
|
"create": "créer",
|
||||||
"loading": "chargement...",
|
"loading": "chargement",
|
||||||
"mode": "mode",
|
"mode": "mode",
|
||||||
"views": "{n, plural, =0 {vues} =1 {1 vue} other {# vues}}",
|
"views": "{n, plural, =0 {vues} =1 {1 vue} other {# vues}}",
|
||||||
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
|
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
|
||||||
"max": "max",
|
"max": "max",
|
||||||
"share_link": "partager le lien",
|
"share_link": "partager le lien",
|
||||||
"copy_clipboard": "copier dans le presse-papiers"
|
"copy_clipboard": "copier dans le presse-papiers",
|
||||||
|
"encrypting": "cryptage",
|
||||||
|
"decrypting": "déchiffrer",
|
||||||
|
"uploading": "téléchargement",
|
||||||
|
"downloading": "téléchargement"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"intro": "Envoyez facilement des notes ou des fichiers <i>entièrement cryptés</i> et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.",
|
"intro": "Envoyez facilement des notes ou des fichiers <i>entièrement cryptés</i> et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.",
|
||||||
@ -28,7 +32,8 @@
|
|||||||
"show": {
|
"show": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"not_found": "La note n'a pas été trouvée ou a déjà été supprimée.",
|
"not_found": "La note n'a pas été trouvée ou a déjà été supprimée.",
|
||||||
"decryption_failed": "mauvais mot de passe. impossible à déchiffrer. probablement un lien brisé. la note a été détruite."
|
"decryption_failed": "mauvais mot de passe. impossible à déchiffrer. probablement un lien brisé. la note a été détruite.",
|
||||||
|
"unsupported_type": "type de note non supporté."
|
||||||
},
|
},
|
||||||
"explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.",
|
"explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.",
|
||||||
"show_note": "note de présentation",
|
"show_note": "note de présentation",
|
||||||
|
@ -4,13 +4,17 @@
|
|||||||
"file": "file",
|
"file": "file",
|
||||||
"advanced": "avanzato",
|
"advanced": "avanzato",
|
||||||
"create": "crea",
|
"create": "crea",
|
||||||
"loading": "carica...",
|
"loading": "carica",
|
||||||
"mode": "modalita",
|
"mode": "modalita",
|
||||||
"views": "{n, plural, =0 {viste} =1 {1 vista} other {# viste}}",
|
"views": "{n, plural, =0 {viste} =1 {1 vista} other {# viste}}",
|
||||||
"minutes": "{n, plural, =0 {minuti} =1 {1 minuto} other {# minuti}}",
|
"minutes": "{n, plural, =0 {minuti} =1 {1 minuto} other {# minuti}}",
|
||||||
"max": "max",
|
"max": "max",
|
||||||
"share_link": "condividi link",
|
"share_link": "condividi link",
|
||||||
"copy_clipboard": "copia negli appunti"
|
"copy_clipboard": "copia negli appunti",
|
||||||
|
"encrypting": "criptando",
|
||||||
|
"decrypting": "decifrando",
|
||||||
|
"uploading": "caricamento",
|
||||||
|
"downloading": "scaricando"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"intro": "Invia facilmente note o file <i>completamente criptati</i> e sicuri con un solo clic. Basta creare una nota e condividere il link.",
|
"intro": "Invia facilmente note o file <i>completamente criptati</i> e sicuri con un solo clic. Basta creare una nota e condividere il link.",
|
||||||
@ -28,7 +32,8 @@
|
|||||||
"show": {
|
"show": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"not_found": "non è stata trovata o è stata già cancellata.",
|
"not_found": "non è stata trovata o è stata già cancellata.",
|
||||||
"decryption_failed": "password sbagliata. non ha potuto decifrare. probabilmente un link rotto. la nota è stata distrutta."
|
"decryption_failed": "password sbagliata. non ha potuto decifrare. probabilmente un link rotto. la nota è stata distrutta.",
|
||||||
|
"unsupported_type": "tipo di nota non supportato."
|
||||||
},
|
},
|
||||||
"explanation": "clicca sotto per mostrare e cancellare la nota se il contatore ha raggiunto il suo limite",
|
"explanation": "clicca sotto per mostrare e cancellare la nota se il contatore ha raggiunto il suo limite",
|
||||||
"show_note": "mostra la nota",
|
"show_note": "mostra la nota",
|
||||||
|
@ -4,13 +4,17 @@
|
|||||||
"file": "上传文件",
|
"file": "上传文件",
|
||||||
"advanced": "高级设置",
|
"advanced": "高级设置",
|
||||||
"create": "创建",
|
"create": "创建",
|
||||||
"loading": "加载中...",
|
"loading": "加载中",
|
||||||
"mode": "模式",
|
"mode": "模式",
|
||||||
"views": "{n, plural, =0 {可查看次数} =1 {1 次查看} other {# 次查看}}",
|
"views": "{n, plural, =0 {可查看次数} =1 {1 次查看} other {# 次查看}}",
|
||||||
"minutes": "{n, plural, =0 {有效期(分钟)} =1 {1 分钟} other {# 分钟}}",
|
"minutes": "{n, plural, =0 {有效期(分钟)} =1 {1 分钟} other {# 分钟}}",
|
||||||
"max": "最大值",
|
"max": "最大值",
|
||||||
"share_link": "分享链接",
|
"share_link": "分享链接",
|
||||||
"copy_clipboard": "复制到剪切版"
|
"copy_clipboard": "复制到剪切版",
|
||||||
|
"encrypting": "加密",
|
||||||
|
"decrypting": "解密",
|
||||||
|
"uploading": "上传",
|
||||||
|
"downloading": "下载"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"intro": "一键轻松发送 <i>完全加密的</i> 密信或者文件。只需创建一个密信然后分享链接。",
|
"intro": "一键轻松发送 <i>完全加密的</i> 密信或者文件。只需创建一个密信然后分享链接。",
|
||||||
@ -28,7 +32,8 @@
|
|||||||
"show": {
|
"show": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"not_found": "该密信无法被找到或者它已经被删除了!",
|
"not_found": "该密信无法被找到或者它已经被删除了!",
|
||||||
"decryption_failed": "密钥错误!您可能不小心粘贴了一个不完整的链接或者正在尝试破解该密信!但无论如何,该密信已被销毁!"
|
"decryption_failed": "密钥错误!您可能不小心粘贴了一个不完整的链接或者正在尝试破解该密信!但无论如何,该密信已被销毁!",
|
||||||
|
"unsupported_type": "不支持的票据类型。"
|
||||||
},
|
},
|
||||||
"explanation": "点击下方的按钮可以查看密信,如果它到达了限制将会被删除",
|
"explanation": "点击下方的按钮可以查看密信,如果它到达了限制将会被删除",
|
||||||
"show_note": "查看密信",
|
"show_note": "查看密信",
|
@ -11,8 +11,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lokalise/node-api": "^7.3.1",
|
"@lokalise/node-api": "^7.3.1",
|
||||||
"@sveltejs/adapter-static": "^1.0.0-next.34",
|
"@sveltejs/adapter-static": "^1.0.0-next.37",
|
||||||
"@sveltejs/kit": "^1.0.0-next.361",
|
"@sveltejs/kit": "^1.0.0-next.377",
|
||||||
"@types/dompurify": "^2.3.3",
|
"@types/dompurify": "^2.3.3",
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.9",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.7.4",
|
||||||
"vite": "^3.0.0"
|
"vite": "^3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/fira-mono": "^4.5.8",
|
"@fontsource/fira-mono": "^4.5.8",
|
||||||
|
96
frontend/pnpm-lock.yaml
generated
96
frontend/pnpm-lock.yaml
generated
@ -3,8 +3,8 @@ lockfileVersion: 5.4
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@fontsource/fira-mono': ^4.5.8
|
'@fontsource/fira-mono': ^4.5.8
|
||||||
'@lokalise/node-api': ^7.3.1
|
'@lokalise/node-api': ^7.3.1
|
||||||
'@sveltejs/adapter-static': ^1.0.0-next.34
|
'@sveltejs/adapter-static': ^1.0.0-next.37
|
||||||
'@sveltejs/kit': ^1.0.0-next.361
|
'@sveltejs/kit': ^1.0.0-next.377
|
||||||
'@types/dompurify': ^2.3.3
|
'@types/dompurify': ^2.3.3
|
||||||
'@types/file-saver': ^2.0.5
|
'@types/file-saver': ^2.0.5
|
||||||
adm-zip: ^0.5.9
|
adm-zip: ^0.5.9
|
||||||
@ -19,7 +19,7 @@ specifiers:
|
|||||||
svelte-preprocess: ^4.10.7
|
svelte-preprocess: ^4.10.7
|
||||||
tslib: ^2.4.0
|
tslib: ^2.4.0
|
||||||
typescript: ^4.7.4
|
typescript: ^4.7.4
|
||||||
vite: ^3.0.0
|
vite: ^3.0.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/fira-mono': 4.5.8
|
'@fontsource/fira-mono': 4.5.8
|
||||||
@ -30,8 +30,8 @@ dependencies:
|
|||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@lokalise/node-api': 7.3.1
|
'@lokalise/node-api': 7.3.1
|
||||||
'@sveltejs/adapter-static': 1.0.0-next.35
|
'@sveltejs/adapter-static': 1.0.0-next.37
|
||||||
'@sveltejs/kit': 1.0.0-next.371_svelte@3.49.0+vite@3.0.0
|
'@sveltejs/kit': 1.0.0-next.377_svelte@3.49.0+vite@3.0.1
|
||||||
'@types/dompurify': 2.3.3
|
'@types/dompurify': 2.3.3
|
||||||
'@types/file-saver': 2.0.5
|
'@types/file-saver': 2.0.5
|
||||||
adm-zip: 0.5.9
|
adm-zip: 0.5.9
|
||||||
@ -42,7 +42,7 @@ devDependencies:
|
|||||||
svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji
|
svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji
|
||||||
tslib: 2.4.0
|
tslib: 2.4.0
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
vite: 3.0.0
|
vite: 3.0.1
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ packages:
|
|||||||
'@babel/compat-data': 7.18.8
|
'@babel/compat-data': 7.18.8
|
||||||
'@babel/core': 7.18.6
|
'@babel/core': 7.18.6
|
||||||
'@babel/helper-validator-option': 7.18.6
|
'@babel/helper-validator-option': 7.18.6
|
||||||
browserslist: 4.21.1
|
browserslist: 4.21.2
|
||||||
semver: 6.3.0
|
semver: 6.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@ -357,37 +357,37 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/adapter-static/1.0.0-next.35:
|
/@sveltejs/adapter-static/1.0.0-next.37:
|
||||||
resolution: {integrity: sha512-iIg5nCMJF2/s/Y7zmy9pzp+U3YDBL6OQKmwfJm2H3Afde/XlhOuNlSO6K//hxmLmvrd7Oh6Kb0MLhwVKp0cuUA==}
|
resolution: {integrity: sha512-BDFkx4CGAd6pG4e3+zYjy/eM9UDbhkRgXqavUzCO5oT8xXao5TeprY1AIbdzjMTmFjsWdeSXE9TbIsT0iikpyQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
tiny-glob: 0.2.9
|
tiny-glob: 0.2.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/kit/1.0.0-next.371_svelte@3.49.0+vite@3.0.0:
|
/@sveltejs/kit/1.0.0-next.377_svelte@3.49.0+vite@3.0.1:
|
||||||
resolution: {integrity: sha512-2MXNb0M97lsEbJx167YNb6ofGRNuNEBUJM9ntIce6usWaurh+2mMg185sA4p0Kl7gl9xSM2D9GXGT7mlaEmJGA==}
|
resolution: {integrity: sha512-DH2v2yUBUuDZ7vzjPXUd/yt1AMR3BIkZN0ubLAvS2C+q5Wbvk7ZvAJhfPZ3OYc3ZpQXe4ZGEcptOjvEYvd1lLA==}
|
||||||
engines: {node: '>=16.9'}
|
engines: {node: '>=16.9'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^3.44.0
|
svelte: ^3.44.0
|
||||||
vite: ^2.9.10
|
vite: ^3.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte': 1.0.0-next.49_svelte@3.49.0+vite@3.0.0
|
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.1
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
sade: 1.8.1
|
sade: 1.8.1
|
||||||
svelte: 3.49.0
|
svelte: 3.49.0
|
||||||
vite: 3.0.0
|
vite: 3.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- diff-match-patch
|
- diff-match-patch
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@sveltejs/vite-plugin-svelte/1.0.0-next.49_svelte@3.49.0+vite@3.0.0:
|
/@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.1:
|
||||||
resolution: {integrity: sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==}
|
resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==}
|
||||||
engines: {node: ^14.13.1 || >= 16}
|
engines: {node: ^14.18.0 || >= 16}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
diff-match-patch: ^1.0.5
|
diff-match-patch: ^1.0.5
|
||||||
svelte: ^3.44.0
|
svelte: ^3.44.0
|
||||||
vite: ^2.9.0
|
vite: ^3.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
diff-match-patch:
|
diff-match-patch:
|
||||||
optional: true
|
optional: true
|
||||||
@ -399,7 +399,7 @@ packages:
|
|||||||
magic-string: 0.26.2
|
magic-string: 0.26.2
|
||||||
svelte: 3.49.0
|
svelte: 3.49.0
|
||||||
svelte-hmr: 0.14.12_svelte@3.49.0
|
svelte-hmr: 0.14.12_svelte@3.49.0
|
||||||
vite: 3.0.0
|
vite: 3.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@ -416,7 +416,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-cache-semantics': 4.0.1
|
'@types/http-cache-semantics': 4.0.1
|
||||||
'@types/keyv': 3.1.4
|
'@types/keyv': 3.1.4
|
||||||
'@types/node': 18.0.3
|
'@types/node': 18.0.6
|
||||||
'@types/responselike': 1.0.0
|
'@types/responselike': 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@ -441,11 +441,11 @@ packages:
|
|||||||
/@types/keyv/3.1.4:
|
/@types/keyv/3.1.4:
|
||||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.0.3
|
'@types/node': 18.0.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/node/18.0.3:
|
/@types/node/18.0.6:
|
||||||
resolution: {integrity: sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==}
|
resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/pug/2.0.6:
|
/@types/pug/2.0.6:
|
||||||
@ -455,13 +455,13 @@ packages:
|
|||||||
/@types/responselike/1.0.0:
|
/@types/responselike/1.0.0:
|
||||||
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.0.3
|
'@types/node': 18.0.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/sass/1.43.1:
|
/@types/sass/1.43.1:
|
||||||
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
|
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.0.3
|
'@types/node': 18.0.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/trusted-types/2.0.2:
|
/@types/trusted-types/2.0.2:
|
||||||
@ -525,15 +525,15 @@ packages:
|
|||||||
fill-range: 7.0.1
|
fill-range: 7.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/browserslist/4.21.1:
|
/browserslist/4.21.2:
|
||||||
resolution: {integrity: sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==}
|
resolution: {integrity: sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==}
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001366
|
caniuse-lite: 1.0.30001367
|
||||||
electron-to-chromium: 1.4.188
|
electron-to-chromium: 1.4.192
|
||||||
node-releases: 2.0.6
|
node-releases: 2.0.6
|
||||||
update-browserslist-db: 1.0.4_browserslist@4.21.1
|
update-browserslist-db: 1.0.4_browserslist@4.21.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/buffer-crc32/0.2.13:
|
/buffer-crc32/0.2.13:
|
||||||
@ -549,10 +549,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==}
|
resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
clone-response: 1.0.2
|
clone-response: 1.0.3
|
||||||
get-stream: 5.2.0
|
get-stream: 5.2.0
|
||||||
http-cache-semantics: 4.1.0
|
http-cache-semantics: 4.1.0
|
||||||
keyv: 4.3.2
|
keyv: 4.3.3
|
||||||
lowercase-keys: 2.0.0
|
lowercase-keys: 2.0.0
|
||||||
normalize-url: 6.1.0
|
normalize-url: 6.1.0
|
||||||
responselike: 2.0.0
|
responselike: 2.0.0
|
||||||
@ -563,8 +563,8 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/caniuse-lite/1.0.30001366:
|
/caniuse-lite/1.0.30001367:
|
||||||
resolution: {integrity: sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==}
|
resolution: {integrity: sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chalk/2.4.2:
|
/chalk/2.4.2:
|
||||||
@ -591,8 +591,8 @@ packages:
|
|||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/clone-response/1.0.2:
|
/clone-response/1.0.3:
|
||||||
resolution: {integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==}
|
resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-response: 1.0.1
|
mimic-response: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
@ -674,8 +674,8 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/electron-to-chromium/1.4.188:
|
/electron-to-chromium/1.4.192:
|
||||||
resolution: {integrity: sha512-Zpa1+E+BVmD/orkyz1Z2dAT1XNUuVAHB3GrogfyY66dXN0ZWSsygI8+u6QTDai1ZayLcATDJpcv2Z2AZjEcr1A==}
|
resolution: {integrity: sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/end-of-stream/1.4.4:
|
/end-of-stream/1.4.4:
|
||||||
@ -1118,8 +1118,8 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/keyv/4.3.2:
|
/keyv/4.3.3:
|
||||||
resolution: {integrity: sha512-kn8WmodVBe12lmHpA6W8OY7SNh6wVR+Z+wZESF4iF5FCazaVXGWOtnbnvX0tMQ1bO+/TmOD9LziuYMvrIIs0xw==}
|
resolution: {integrity: sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
compress-brotli: 1.3.8
|
compress-brotli: 1.3.8
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
@ -1344,8 +1344,8 @@ packages:
|
|||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup/2.76.0:
|
/rollup/2.77.0:
|
||||||
resolution: {integrity: sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==}
|
resolution: {integrity: sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -1566,20 +1566,20 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/update-browserslist-db/1.0.4_browserslist@4.21.1:
|
/update-browserslist-db/1.0.4_browserslist@4.21.2:
|
||||||
resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==}
|
resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
browserslist: '>= 4.21.0'
|
browserslist: '>= 4.21.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.21.1
|
browserslist: 4.21.2
|
||||||
escalade: 3.1.1
|
escalade: 3.1.1
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite/3.0.0:
|
/vite/3.0.1:
|
||||||
resolution: {integrity: sha512-M7phQhY3+fRZa0H+1WzI6N+/onruwPTBTMvaj7TzgZ0v2TE+N2sdLKxJOfOv9CckDWt5C4HmyQP81xB4dwRKzA==}
|
resolution: {integrity: sha512-nefKSglkoEsDpYUkBuT2++L04ktcP8fz8dxLtmZdDdMyhubFSOLFw6BTh/46Fc6tIX/cibs/NVYWNrsqn0k6pQ==}
|
||||||
engines: {node: '>=14.18.0'}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
less: '*'
|
less: '*'
|
||||||
@ -1599,7 +1599,7 @@ packages:
|
|||||||
esbuild: 0.14.49
|
esbuild: 0.14.49
|
||||||
postcss: 8.4.14
|
postcss: 8.4.14
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
rollup: 2.76.0
|
rollup: 2.77.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
61
frontend/src/lib/adapters.ts
Normal file
61
frontend/src/lib/adapters.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import type { EncryptedFileDTO, FileDTO } from './api'
|
||||||
|
import { Crypto } from './crypto'
|
||||||
|
|
||||||
|
abstract class CryptAdapter<T> {
|
||||||
|
abstract encrypt(plaintext: T, key: CryptoKey): Promise<string>
|
||||||
|
abstract decrypt(ciphertext: string, key: CryptoKey): Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptTextAdapter implements CryptAdapter<string> {
|
||||||
|
async encrypt(plaintext: string, key: CryptoKey) {
|
||||||
|
return await Crypto.encrypt(new TextEncoder().encode(plaintext), key)
|
||||||
|
}
|
||||||
|
async decrypt(ciphertext: string, key: CryptoKey) {
|
||||||
|
const plaintext = await Crypto.decrypt(ciphertext, key)
|
||||||
|
return new TextDecoder().decode(plaintext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptBlobAdapter implements CryptAdapter<Blob> {
|
||||||
|
async encrypt(plaintext: Blob, key: CryptoKey) {
|
||||||
|
return await Crypto.encrypt(await plaintext.arrayBuffer(), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(ciphertext: string, key: CryptoKey) {
|
||||||
|
const plaintext = await Crypto.decrypt(ciphertext, key)
|
||||||
|
return new Blob([plaintext], { type: 'application/octet-stream' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
|
||||||
|
async encrypt(plaintext: FileDTO[], key: CryptoKey) {
|
||||||
|
const adapter = new CryptBlobAdapter()
|
||||||
|
const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
contents: await adapter.encrypt(file.contents, key),
|
||||||
|
}))
|
||||||
|
return JSON.stringify(await Promise.all(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(ciphertext: string, key: CryptoKey) {
|
||||||
|
const adapter = new CryptBlobAdapter()
|
||||||
|
const data: EncryptedFileDTO[] = JSON.parse(ciphertext)
|
||||||
|
const files: FileDTO[] = await Promise.all(
|
||||||
|
data.map(async (file) => ({
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
contents: await adapter.decrypt(file.contents, key),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Adapters = {
|
||||||
|
Text: new CryptTextAdapter(),
|
||||||
|
Blob: new CryptBlobAdapter(),
|
||||||
|
Files: new CryptFilesAdapter(),
|
||||||
|
}
|
@ -11,6 +11,10 @@ export type NotePublic = Pick<Note, 'contents' | 'meta'>
|
|||||||
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
|
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
|
||||||
|
|
||||||
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
|
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
|
||||||
|
contents: Blob
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EncryptedFileDTO = Omit<FileDTO, 'contents'> & {
|
||||||
contents: string
|
contents: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,13 +19,31 @@ export class Hex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALG = 'AES-GCM'
|
export class ArrayBufferUtils {
|
||||||
|
static async toString(buffer: ArrayBuffer): Promise<string> {
|
||||||
|
const reader = new window.FileReader()
|
||||||
|
reader.readAsDataURL(new Blob([buffer]))
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
reader.onloadend = () => resolve(reader.result as string)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function getRandomBytes(size = 16): Uint8Array {
|
static async fromString(s: string): Promise<ArrayBuffer> {
|
||||||
|
return fetch(s)
|
||||||
|
.then((r) => r.blob())
|
||||||
|
.then((b) => b.arrayBuffer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Crypto {
|
||||||
|
private static ALG = 'AES-GCM'
|
||||||
|
private static DELIMITER = ':::'
|
||||||
|
|
||||||
|
public static getRandomBytes(size: number): Uint8Array {
|
||||||
return window.crypto.getRandomValues(new Uint8Array(size))
|
return window.crypto.getRandomValues(new Uint8Array(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getKeyFromString(password: string) {
|
public static getKeyFromString(password: string) {
|
||||||
return window.crypto.subtle.importKey(
|
return window.crypto.subtle.importKey(
|
||||||
'raw',
|
'raw',
|
||||||
new TextEncoder().encode(password),
|
new TextEncoder().encode(password),
|
||||||
@ -34,8 +52,7 @@ export function getKeyFromString(password: string) {
|
|||||||
['deriveBits', 'deriveKey']
|
['deriveBits', 'deriveKey']
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
public static async getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) {
|
||||||
export async function getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) {
|
|
||||||
const iterations = 100_000
|
const iterations = 100_000
|
||||||
return window.crypto.subtle.deriveKey(
|
return window.crypto.subtle.deriveKey(
|
||||||
{
|
{
|
||||||
@ -45,27 +62,36 @@ export async function getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) {
|
|||||||
hash: 'SHA-512',
|
hash: 'SHA-512',
|
||||||
},
|
},
|
||||||
key,
|
key,
|
||||||
{ name: ALG, length: 256 },
|
{ name: this.ALG, length: 256 },
|
||||||
true,
|
true,
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function encrypt(plaintext: string, key: CryptoKey) {
|
public static async encrypt(plaintext: ArrayBuffer, key: CryptoKey): Promise<string> {
|
||||||
const salt = getRandomBytes(16)
|
const salt = this.getRandomBytes(16)
|
||||||
const derived = await getDerivedForKey(key, salt)
|
const derived = await this.getDerivedForKey(key, salt)
|
||||||
const iv = getRandomBytes(16)
|
const iv = this.getRandomBytes(16)
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted: ArrayBuffer = await window.crypto.subtle.encrypt(
|
||||||
{ name: ALG, iv },
|
{ name: this.ALG, iv },
|
||||||
derived,
|
derived,
|
||||||
new TextEncoder().encode(plaintext)
|
plaintext
|
||||||
)
|
)
|
||||||
return [salt, iv, encrypted].map(Hex.encode).join(':')
|
const data = [
|
||||||
|
Hex.encode(salt),
|
||||||
|
Hex.encode(iv),
|
||||||
|
await ArrayBufferUtils.toString(encrypted),
|
||||||
|
].join(this.DELIMITER)
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function decrypt(ciphertext: string, key: CryptoKey) {
|
public static async decrypt(ciphertext: string, key: CryptoKey): Promise<ArrayBuffer> {
|
||||||
const [salt, iv, encrypted] = ciphertext.split(':').map(Hex.decode)
|
const splitted = ciphertext.split(this.DELIMITER)
|
||||||
const derived = await getDerivedForKey(key, salt)
|
const salt = Hex.decode(splitted[0])
|
||||||
const plaintext = await window.crypto.subtle.decrypt({ name: ALG, iv }, derived, encrypted)
|
const iv = Hex.decode(splitted[1])
|
||||||
return new TextDecoder().decode(plaintext)
|
const encrypted = await ArrayBufferUtils.fromString(splitted[2])
|
||||||
|
const derived = await this.getDerivedForKey(key, salt)
|
||||||
|
const plaintext = await window.crypto.subtle.decrypt({ name: this.ALG, iv }, derived, encrypted)
|
||||||
|
return plaintext
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
export class Files {
|
|
||||||
static toString(f: File | Blob): Promise<string> {
|
|
||||||
const reader = new window.FileReader()
|
|
||||||
reader.readAsDataURL(f)
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
reader.onloadend = () => resolve(reader.result as string)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static async fromString(s: string): Promise<Blob> {
|
|
||||||
return fetch(s).then((r) => r.blob())
|
|
||||||
}
|
|
||||||
}
|
|
47
frontend/src/lib/ui/AdvancedParameters.svelte
Normal file
47
frontend/src/lib/ui/AdvancedParameters.svelte
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
|
import type { Note } from '$lib/api'
|
||||||
|
import { status } from '$lib/stores/status'
|
||||||
|
import Switch from '$lib/ui/Switch.svelte'
|
||||||
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
|
|
||||||
|
export let note: Note
|
||||||
|
export let timeExpiration = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="fields">
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label={$t('common.views', { values: { n: 0 } })}
|
||||||
|
bind:value={note.views}
|
||||||
|
disabled={timeExpiration}
|
||||||
|
max={$status?.max_views}
|
||||||
|
validate={(v) =>
|
||||||
|
($status && v < $status?.max_views) ||
|
||||||
|
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
|
||||||
|
/>
|
||||||
|
<div class="middle-switch">
|
||||||
|
<Switch label={$t('common.mode')} bind:value={timeExpiration} color={false} />
|
||||||
|
</div>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
label={$t('common.minutes', { values: { n: 0 } })}
|
||||||
|
bind:value={note.expiration}
|
||||||
|
disabled={!timeExpiration}
|
||||||
|
max={$status?.max_expiration}
|
||||||
|
validate={(v) =>
|
||||||
|
($status && v < $status?.max_expiration) ||
|
||||||
|
$t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.middle-switch {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,38 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { FileDTO } from '$lib/api'
|
|
||||||
import { Files } from '$lib/files'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import Button from './Button.svelte'
|
|
||||||
import MaxSize from './MaxSize.svelte'
|
import type { FileDTO } from '$lib/api'
|
||||||
|
import Button from '$lib/ui/Button.svelte'
|
||||||
|
import MaxSize from '$lib/ui/MaxSize.svelte'
|
||||||
|
|
||||||
export let label: string = ''
|
export let label: string = ''
|
||||||
let files: File[] = []
|
export let files: FileDTO[] = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ file: string }>()
|
function fileToDTO(file: File): FileDTO {
|
||||||
|
return {
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
contents: file,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onInput(e: Event) {
|
async function onInput(e: Event) {
|
||||||
const input = e.target as HTMLInputElement
|
const input = e.target as HTMLInputElement
|
||||||
if (input?.files?.length) {
|
if (input?.files?.length) {
|
||||||
files = [...files, ...Array.from(input.files)]
|
files = [...files, ...Array.from(input.files).map(fileToDTO)]
|
||||||
const data: FileDTO[] = await Promise.all(
|
|
||||||
files.map(async (file) => ({
|
|
||||||
name: file.name,
|
|
||||||
type: file.type,
|
|
||||||
size: file.size,
|
|
||||||
contents: await Files.toString(file),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
dispatch('file', JSON.stringify(data))
|
|
||||||
} else {
|
|
||||||
dispatch('file', '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear(e: Event) {
|
function clear(e: Event) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
files = []
|
files = []
|
||||||
dispatch('file', '')
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -57,7 +51,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<b>{$t('file_upload.no_files_selected')}</b>
|
<b>{$t('file_upload.no_files_selected')}</b>
|
||||||
<br />
|
<br />
|
||||||
<small>{$t('common.max')}: <MaxSize /></small>
|
<small>
|
||||||
|
{$t('common.max')}: <MaxSize />
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
41
frontend/src/lib/ui/Loader.svelte
Normal file
41
frontend/src/lib/ui/Loader.svelte
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
xml:space="preserve"
|
||||||
|
>
|
||||||
|
<rect fill="none" stroke="currentColor" stroke-width="4" x="25" y="25" width="50" height="50">
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
dur="0.5s"
|
||||||
|
from="0 50 50"
|
||||||
|
to="180 50 50"
|
||||||
|
type="rotate"
|
||||||
|
id="strokeBox"
|
||||||
|
attributeType="XML"
|
||||||
|
begin="rectBox.end"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
<rect x="27" y="27" fill="currentColor" width="46" height="50">
|
||||||
|
<animate
|
||||||
|
attributeName="height"
|
||||||
|
dur="1.3s"
|
||||||
|
attributeType="XML"
|
||||||
|
from="50"
|
||||||
|
to="0"
|
||||||
|
id="rectBox"
|
||||||
|
fill="freeze"
|
||||||
|
begin="0s;strokeBox.end"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
height: 2em;
|
||||||
|
position: relative;
|
||||||
|
top: 0.6em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
After Width: | Height: | Size: 784 B |
@ -1,12 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { status } from '$lib/stores/status'
|
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { _ } from 'svelte-intl-precompile'
|
import { _ } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
|
import { status } from '$lib/stores/status'
|
||||||
|
|
||||||
|
// Due to encoding overhead (~35%) with base64
|
||||||
|
// https://en.wikipedia.org/wiki/Base64
|
||||||
|
const overhead = 1 / 1.35
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{#if $status !== null}
|
{#if $status !== null}
|
||||||
{prettyBytes($status.max_size, { binary: true })}
|
{prettyBytes($status.max_size * overhead, { binary: true })}
|
||||||
{:else}
|
{:else}
|
||||||
{$_('common.loading')}
|
{$_('common.loading')}
|
||||||
{/if}
|
{/if}
|
||||||
|
36
frontend/src/lib/ui/NoteResult.svelte
Normal file
36
frontend/src/lib/ui/NoteResult.svelte
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export type NoteResult = {
|
||||||
|
password: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
|
import Button from '$lib/ui/Button.svelte'
|
||||||
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
|
|
||||||
|
export let result: NoteResult
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
label={$t('common.share_link')}
|
||||||
|
value="{window.location.origin}/note/{result.id}#{result.password}"
|
||||||
|
copy
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<p>
|
||||||
|
{@html $t('home.new_note_notice')}
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<Button on:click={reset}>{$t('home.new_note')}</Button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
@ -1,20 +1,24 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { FileDTO, NotePublic } from '$lib/api'
|
|
||||||
import { Files } from '$lib/files'
|
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import Button from './Button.svelte'
|
|
||||||
|
|
||||||
export let note: NotePublic
|
import type { FileDTO, NotePublic } from '$lib/api'
|
||||||
|
import Button from '$lib/ui/Button.svelte'
|
||||||
|
|
||||||
|
export let note: DecryptedNote
|
||||||
|
|
||||||
const RE_URL = /[A-Za-z]+:\/\/([A-Z a-z0-9\-._~:\/?#\[\]@!$&'()*+,;%=])+/g
|
const RE_URL = /[A-Za-z]+:\/\/([A-Z a-z0-9\-._~:\/?#\[\]@!$&'()*+,;%=])+/g
|
||||||
let files: FileDTO[] = []
|
let files: FileDTO[] = []
|
||||||
|
|
||||||
$: if (note.meta.type === 'file') {
|
$: if (note.meta.type === 'file') {
|
||||||
files = JSON.parse(note.contents) as FileDTO[]
|
files = note.contents
|
||||||
}
|
}
|
||||||
|
|
||||||
$: download = () => {
|
$: download = () => {
|
||||||
@ -24,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function downloadFile(file: FileDTO) {
|
async function downloadFile(file: FileDTO) {
|
||||||
const f = new File([await Files.fromString(file.contents)], file.name, {
|
const f = new File([file.contents], file.name, {
|
||||||
type: file.type,
|
type: file.type,
|
||||||
})
|
})
|
||||||
saveAs(f)
|
saveAs(f)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getRandomBytes, Hex } from '$lib/crypto'
|
|
||||||
import copyToClipboard from 'copy-to-clipboard'
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import { fade } from 'svelte/transition'
|
import { fade } from 'svelte/transition'
|
||||||
import Icon from './Icon.svelte'
|
|
||||||
|
import { Crypto, Hex } from '$lib/crypto'
|
||||||
|
import Icon from '$lib/ui/Icon.svelte'
|
||||||
|
|
||||||
export let label: string = ''
|
export let label: string = ''
|
||||||
export let value: any
|
export let value: any
|
||||||
@ -32,7 +33,7 @@
|
|||||||
notify($t('home.copied_to_clipboard'))
|
notify($t('home.copied_to_clipboard'))
|
||||||
}
|
}
|
||||||
function randomFN() {
|
function randomFN() {
|
||||||
value = Hex.encode(getRandomBytes(20))
|
value = Hex.encode(Crypto.getRandomBytes(20))
|
||||||
}
|
}
|
||||||
|
|
||||||
function notify(msg: string, delay: number = 2000) {
|
function notify(msg: string, delay: number = 2000) {
|
||||||
|
@ -1,29 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Note } from '$lib/api'
|
|
||||||
import { create,PayloadToLargeError } from '$lib/api'
|
|
||||||
import { encrypt,getKeyFromString,getRandomBytes,Hex } from '$lib/crypto'
|
|
||||||
import { status } from '$lib/stores/status'
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
|
||||||
import FileUpload from '$lib/ui/FileUpload.svelte'
|
|
||||||
import MaxSize from '$lib/ui/MaxSize.svelte'
|
|
||||||
import Switch from '$lib/ui/Switch.svelte'
|
|
||||||
import TextArea from '$lib/ui/TextArea.svelte'
|
|
||||||
import TextInput from '$lib/ui/TextInput.svelte'
|
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import { blur } from 'svelte/transition'
|
import { blur } from 'svelte/transition'
|
||||||
|
|
||||||
|
import { Adapters } from '$lib/adapters'
|
||||||
|
import type { FileDTO, Note } from '$lib/api'
|
||||||
|
import { create, PayloadToLargeError } from '$lib/api'
|
||||||
|
import { Crypto, Hex } from '$lib/crypto'
|
||||||
|
import { status } from '$lib/stores/status'
|
||||||
|
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
|
||||||
|
import Button from '$lib/ui/Button.svelte'
|
||||||
|
import FileUpload from '$lib/ui/FileUpload.svelte'
|
||||||
|
import Loader from '$lib/ui/Loader.svelte'
|
||||||
|
import MaxSize from '$lib/ui/MaxSize.svelte'
|
||||||
|
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
|
||||||
|
import Switch from '$lib/ui/Switch.svelte'
|
||||||
|
import TextArea from '$lib/ui/TextArea.svelte'
|
||||||
|
|
||||||
let note: Note = {
|
let note: Note = {
|
||||||
contents: '',
|
contents: '',
|
||||||
meta: { type: 'text' },
|
meta: { type: 'text' },
|
||||||
views: 1,
|
views: 1,
|
||||||
expiration: 60,
|
expiration: 60,
|
||||||
}
|
}
|
||||||
let result: { password: string; id: string } | null = null
|
let files: FileDTO[]
|
||||||
|
let result: NoteResult | null = null
|
||||||
let advanced = false
|
let advanced = false
|
||||||
let file = false
|
let isFile = false
|
||||||
let timeExpiration = false
|
let timeExpiration = false
|
||||||
let message = ''
|
let description = ''
|
||||||
let loading = false
|
let loading: string | null = null
|
||||||
let error: string | null = null
|
let error: string | null = null
|
||||||
|
|
||||||
$: if (!advanced) {
|
$: if (!advanced) {
|
||||||
@ -32,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
message = $t('home.explanation', {
|
description = $t('home.explanation', {
|
||||||
values: {
|
values: {
|
||||||
type: $t(timeExpiration ? 'common.minutes' : 'common.views', {
|
type: $t(timeExpiration ? 'common.minutes' : 'common.views', {
|
||||||
values: { n: (timeExpiration ? note.expiration : note.views) ?? '?' },
|
values: { n: (timeExpiration ? note.expiration : note.views) ?? '?' },
|
||||||
@ -41,9 +46,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$: note.meta.type = file ? 'file' : 'text'
|
$: note.meta.type = isFile ? 'file' : 'text'
|
||||||
|
|
||||||
$: if (!file) {
|
$: if (!isFile) {
|
||||||
note.contents = ''
|
note.contents = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,17 +57,26 @@
|
|||||||
async function submit() {
|
async function submit() {
|
||||||
try {
|
try {
|
||||||
error = null
|
error = null
|
||||||
loading = true
|
loading = $t('common.encrypting')
|
||||||
const password = Hex.encode(getRandomBytes(32))
|
|
||||||
const key = await getKeyFromString(password)
|
const password = Hex.encode(Crypto.getRandomBytes(32))
|
||||||
if (note.contents === '') throw new EmptyContentError()
|
const key = await Crypto.getKeyFromString(password)
|
||||||
|
|
||||||
const data: Note = {
|
const data: Note = {
|
||||||
contents: await encrypt(note.contents, key),
|
contents: '',
|
||||||
meta: note.meta,
|
meta: note.meta,
|
||||||
}
|
}
|
||||||
|
if (isFile) {
|
||||||
|
if (files.length === 0) throw new EmptyContentError()
|
||||||
|
data.contents = await Adapters.Files.encrypt(files, key)
|
||||||
|
} else {
|
||||||
|
if (note.contents === '') throw new EmptyContentError()
|
||||||
|
data.contents = await Adapters.Text.encrypt(note.contents, key)
|
||||||
|
}
|
||||||
if (timeExpiration) data.expiration = parseInt(note.expiration as any)
|
if (timeExpiration) data.expiration = parseInt(note.expiration as any)
|
||||||
else data.views = parseInt(note.views as any)
|
else data.views = parseInt(note.views as any)
|
||||||
|
|
||||||
|
loading = $t('common.uploading')
|
||||||
const response = await create(data)
|
const response = await create(data)
|
||||||
result = {
|
result = {
|
||||||
password: password,
|
password: password,
|
||||||
@ -74,46 +88,31 @@
|
|||||||
} else if (e instanceof EmptyContentError) {
|
} else if (e instanceof EmptyContentError) {
|
||||||
error = $t('home.errors.empty_content')
|
error = $t('home.errors.empty_content')
|
||||||
} else {
|
} else {
|
||||||
|
console.error(e)
|
||||||
error = $t('home.errors.note_error')
|
error = $t('home.errors.note_error')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if result}
|
{#if result}
|
||||||
<TextInput
|
<Result {result} />
|
||||||
type="text"
|
|
||||||
readonly
|
|
||||||
label={$t('common.share_link')}
|
|
||||||
value="{window.location.origin}/note/{result.id}#{result.password}"
|
|
||||||
copy
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<p>
|
|
||||||
{@html $t('home.new_note_notice')}
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<Button on:click={reset}>{$t('home.new_note')}</Button>
|
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p>
|
||||||
{@html $status?.theme_text || $t('home.intro')}
|
{@html $status?.theme_text || $t('home.intro')}
|
||||||
</p>
|
</p>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<fieldset disabled={loading}>
|
<fieldset disabled={loading !== null}>
|
||||||
{#if file}
|
{#if isFile}
|
||||||
<FileUpload label={$t('common.file')} on:file={(f) => (note.contents = f.detail)} />
|
<FileUpload label={$t('common.file')} bind:files />
|
||||||
{:else}
|
{:else}
|
||||||
<TextArea label={$t('common.note')} bind:value={note.contents} placeholder="..." />
|
<TextArea label={$t('common.note')} bind:value={note.contents} placeholder="..." />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<Switch class="file" label={$t('common.file')} bind:value={file} />
|
<Switch class="file" label={$t('common.file')} bind:value={isFile} />
|
||||||
{#if $status?.allow_advanced}
|
{#if $status?.allow_advanced}
|
||||||
<Switch label={$t('common.advanced')} bind:value={advanced} />
|
<Switch label={$t('common.advanced')} bind:value={advanced} />
|
||||||
{/if}
|
{/if}
|
||||||
@ -132,40 +131,16 @@
|
|||||||
<p>
|
<p>
|
||||||
<br />
|
<br />
|
||||||
{#if loading}
|
{#if loading}
|
||||||
{$t('common.loading')}
|
{loading} <Loader />
|
||||||
{:else}
|
{:else}
|
||||||
{message}
|
{description}
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{#if advanced}
|
{#if advanced}
|
||||||
<div transition:blur={{ duration: 250 }}>
|
<div transition:blur={{ duration: 250 }}>
|
||||||
<br />
|
<br />
|
||||||
<div class="fields">
|
<AdvancedParameters bind:note bind:timeExpiration />
|
||||||
<TextInput
|
|
||||||
type="number"
|
|
||||||
label={$t('common.views', { values: { n: 0 } })}
|
|
||||||
bind:value={note.views}
|
|
||||||
disabled={timeExpiration}
|
|
||||||
max={$status?.max_views}
|
|
||||||
validate={(v) =>
|
|
||||||
($status && v < $status?.max_views) ||
|
|
||||||
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
|
|
||||||
/>
|
|
||||||
<div class="middle-switch">
|
|
||||||
<Switch label={$t('common.mode')} bind:value={timeExpiration} color={false} />
|
|
||||||
</div>
|
|
||||||
<TextInput
|
|
||||||
type="number"
|
|
||||||
label={$t('common.minutes', { values: { n: 0 } })}
|
|
||||||
bind:value={note.expiration}
|
|
||||||
disabled={!timeExpiration}
|
|
||||||
max={$status?.max_expiration}
|
|
||||||
validate={(v) =>
|
|
||||||
($status && v < $status?.max_expiration) ||
|
|
||||||
$t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -187,15 +162,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.middle-switch {
|
|
||||||
margin: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-text {
|
.error-text {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fields {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import { init, waitLocale, getLocaleFromNavigator } from 'svelte-intl-precompile'
|
import { getLocaleFromNavigator, init, waitLocale } from 'svelte-intl-precompile'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { registerAll } from '$locales'
|
import { registerAll } from '$locales'
|
||||||
registerAll()
|
registerAll()
|
||||||
|
@ -12,47 +12,67 @@
|
|||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
import type { NotePublic } from '$lib/api'
|
import { Adapters } from '$lib/adapters'
|
||||||
import { get, info } from '$lib/api'
|
import { get, info } from '$lib/api'
|
||||||
import { decrypt, getKeyFromString } from '$lib/crypto'
|
import { Crypto } from '$lib/crypto'
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
import ShowNote from '$lib/ui/ShowNote.svelte'
|
import Loader from '$lib/ui/Loader.svelte'
|
||||||
|
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
|
||||||
|
|
||||||
export let id: string
|
export let id: string
|
||||||
|
|
||||||
let password: string
|
let password: string
|
||||||
let note: NotePublic | null = null
|
let note: DecryptedNote | null = null
|
||||||
let exists = false
|
let exists = false
|
||||||
|
|
||||||
let loading = true
|
let loading: string | null = null
|
||||||
let error = false
|
let error: string | null = null
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
// Check if note exists
|
||||||
try {
|
try {
|
||||||
loading = true
|
loading = $t('common.loading')
|
||||||
error = false
|
|
||||||
password = window.location.hash.slice(1)
|
password = window.location.hash.slice(1)
|
||||||
await info(id)
|
await info(id)
|
||||||
exists = true
|
exists = true
|
||||||
} catch {
|
} catch {
|
||||||
exists = false
|
exists = false
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual contents of the note and decrypt it.
|
||||||
|
*/
|
||||||
async function show() {
|
async function show() {
|
||||||
try {
|
try {
|
||||||
error = false
|
error = null
|
||||||
loading = true
|
loading = $t('common.downloading')
|
||||||
const data = note || (await get(id)) // Don't get the content twice on wrong password.
|
const data = await get(id)
|
||||||
const key = await getKeyFromString(password)
|
loading = $t('common.decrypting')
|
||||||
data.contents = await decrypt(data.contents, key)
|
const key = await Crypto.getKeyFromString(password)
|
||||||
note = data
|
switch (data.meta.type) {
|
||||||
|
case 'text':
|
||||||
|
note = {
|
||||||
|
meta: { type: 'text' },
|
||||||
|
contents: await Adapters.Text.decrypt(data.contents, key),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'file':
|
||||||
|
note = {
|
||||||
|
meta: { type: 'file' },
|
||||||
|
contents: await Adapters.Files.decrypt(data.contents, key),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
error = $t('show.errors.unsupported_type')
|
||||||
|
return
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
error = true
|
error = $t('show.errors.decryption_failed')
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -70,7 +90,7 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<br />
|
<br />
|
||||||
<p class="error-text">
|
<p class="error-text">
|
||||||
{$t('show.errors.decryption_failed')}
|
{error}
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
@ -79,5 +99,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<p>{$t('common.loading')}</p>
|
<p class="loader">{loading} <Loader /></p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.loader {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user