Compare commits

..

1 Commits

Author SHA1 Message Date
4ea6bc3e39 Merge e15d9eb537 into b60a079bbe 2024-08-26 22:42:26 +00:00
42 changed files with 196 additions and 317 deletions

View File

@@ -12,7 +12,6 @@
<br/><br/> <br/><br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a> <a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
<a href=""><img src="./.github/lokalise.png" height="50"> <a href=""><img src="./.github/lokalise.png" height="50">
<a title="Install cryptgeon Raycast Extension" href="https://www.raycast.com/cupcakearmy/cryptgeon"><img src="https://www.raycast.com/cupcakearmy/cryptgeon/install_button@2x.png?v=1.1" height="64" alt="" style="height: 64px;"></a>
<br/><br/> <br/><br/>
EN | [简体中文](README_zh-CN.md) | [ES](README_ES.md) EN | [简体中文](README_zh-CN.md) | [ES](README_ES.md)
@@ -40,12 +39,6 @@ npx cryptgeon send text "This is a secret note"
For more documentation about the CLI see the [readme](./packages/cli/README.md). For more documentation about the CLI see the [readme](./packages/cli/README.md).
### Raycast Extension
There is an [official Raycast extension](https://www.raycast.com/cupcakearmy/cryptgeon).
<a title="Install cryptgeon Raycast Extension" href="https://www.raycast.com/cupcakearmy/cryptgeon"><img src="https://www.raycast.com/cupcakearmy/cryptgeon/install_button@2x.png?v=1.1" height="64" alt="" style="height: 64px;"></a>
## Features ## Features
- send text or files - send text or files
@@ -77,15 +70,14 @@ of the notes even if it tried to.
| `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. |
| `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. |
| `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`. |
| `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_. |
| `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 |
| `THEME_PAGE_TITLE` | `""` | Custom text the page title | | `THEME_PAGE_TITLE` | `""` | Custom text the page title |
| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable | | `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`. |
| `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 ## Deployment
> `https` is required otherwise browsers will not support the cryptographic functions. > `https` is required otherwise browsers will not support the cryptographic functions.
@@ -152,8 +144,6 @@ There is a [guide](https://mariushosting.com/how-to-install-cryptgeon-on-your-sy
### Written Guides ### Written Guides
- French by [zarevskaya](https://belginux.com/installer-cryptgeon-avec-docker/) - French by [zarevskaya](https://belginux.com/installer-cryptgeon-avec-docker/)
- Italian by [@nicfab](https://notes.nicfab.eu/it/posts/cryptgeon/)
- English by [@nicfab](https://notes.nicfab.eu/en/posts/cryptgeon/)
## Development ## Development

View File

@@ -11,6 +11,9 @@
}, },
{ {
"path": "packages/cli" "path": "packages/cli"
},
{
"path": "packages/shared"
} }
], ],
"settings": { "settings": {

View File

@@ -2,7 +2,7 @@
Assumptions: Assumptions:
- Traefik 2/3 installed. - Traefik 2 installed.
- External proxy docker network `proxy`. - External proxy docker network `proxy`.
- A certificate resolver `le`. - A certificate resolver `le`.
- A https entrypoint `secure`. - A https entrypoint `secure`.
@@ -34,43 +34,3 @@ services:
- traefik.http.routers.cryptgeon.entrypoints=secure - traefik.http.routers.cryptgeon.entrypoints=secure
- traefik.http.routers.cryptgeon.tls.certresolver=le - traefik.http.routers.cryptgeon.tls.certresolver=le
``` ```
## With basic auth
Some times it's useful to hide the service behind auth. This is easily achieved with traefik middleware. Many reverse proxies support similar features, so while traefik is used in this example, other reverse proxies can do the same.
```yaml
services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
redis:
image: redis:7-alpine
cryptgeon:
image: cupcakearmy/cryptgeon
depends_on:
- redis
labels:
- "traefik.enable=true"
- "traefik.http.routers.cryptgeon.rule=Host(`cryptgeon.localhost`)"
- "traefik.http.routers.cryptgeon.entrypoints=web"
- "traefik.http.routers.cryptgeon.middlewares=cryptgeon-auth"
- "traefik.http.middlewares.cryptgeon-auth.basicauth.users=user:$$2y$$05$$juUw0zgc5ebvJ00MFPVVLujF6P.rcEMbGZ99Jfq6ZWEa1dgetacEq"
```
```bash
docker compose up -d
```
1. Open http://cryptgeon.localhost
2. Log in with `user` and `secret`

View File

@@ -5,8 +5,8 @@
"dev": "run-p dev:*", "dev": "run-p dev:*",
"docker:up": "docker compose -f docker-compose.dev.yaml up", "docker:up": "docker compose -f docker-compose.dev.yaml up",
"docker:build": "docker compose -f docker-compose.dev.yaml build", "docker:build": "docker compose -f docker-compose.dev.yaml build",
"test": "playwright test --project=chrome --project=firefox --project=safari", "test": "playwright test --project chrome firefox safari",
"test:local": "playwright test --project=chrome", "test:local": "playwright test --project chrome",
"test:server": "run-s docker:up", "test:server": "run-s docker:up",
"test:prepare": "run-p build docker:build", "test:prepare": "run-p build docker:build",
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build" "build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
@@ -17,5 +17,5 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"shelljs": "^0.8.5" "shelljs": "^0.8.5"
}, },
"packageManager": "pnpm@9.11.0" "packageManager": "pnpm@9.9.0"
} }

View File

@@ -261,7 +261,7 @@ dependencies = [
[[package]] [[package]]
name = "cryptgeon" name = "cryptgeon"
version = "2.8.4" version = "2.7.0"
dependencies = [ dependencies = [
"axum", "axum",
"bs62", "bs62",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cryptgeon" name = "cryptgeon"
version = "2.8.4" version = "2.7.0"
authors = ["cupcakearmy <hi@nicco.io>"] authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021" edition = "2021"
rust-version = "1.80" rust-version = "1.80"

View File

@@ -38,14 +38,6 @@ pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES")
.unwrap_or("true".to_string()) .unwrap_or("true".to_string())
.parse() .parse()
.unwrap(); .unwrap();
pub static ref IMPRINT_URL: String = std::env::var("IMPRINT_URL")
.unwrap_or("".to_string())
.parse()
.unwrap();
pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML")
.unwrap_or("".to_string())
.parse()
.unwrap();
} }
// THEME // THEME

View File

@@ -1,9 +1,7 @@
use axum::{ use axum::extract::Path;
extract::Path, use axum::http::StatusCode;
http::StatusCode, use axum::response::{IntoResponse, Response};
response::{IntoResponse, Response}, use axum::Json;
Json,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::SystemTime; use std::time::SystemTime;

View File

@@ -1,5 +1,6 @@
use crate::config; use crate::config;
use axum::{http::StatusCode, Json}; use axum::http::StatusCode;
use axum::Json;
use serde::Serialize; use serde::Serialize;
#[derive(Serialize)] #[derive(Serialize)]
@@ -12,8 +13,6 @@ pub struct Status {
pub max_expiration: u32, pub max_expiration: u32,
pub allow_advanced: bool, pub allow_advanced: bool,
pub allow_files: bool, pub allow_files: bool,
pub imprint_url: String,
pub imprint_html: String,
// Theme // Theme
pub theme_image: String, pub theme_image: String,
pub theme_text: String, pub theme_text: String,
@@ -30,8 +29,6 @@ pub async fn get_status() -> (StatusCode, Json<Status>) {
max_expiration: *config::MAX_EXPIRATION, max_expiration: *config::MAX_EXPIRATION,
allow_advanced: *config::ALLOW_ADVANCED, allow_advanced: *config::ALLOW_ADVANCED,
allow_files: *config::ALLOW_FILES, allow_files: *config::ALLOW_FILES,
imprint_url: config::IMPRINT_URL.to_string(),
imprint_html: config::IMPRINT_HTML.to_string(),
theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE, theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE,
theme_image: config::THEME_IMAGE.to_string(), theme_image: config::THEME_IMAGE.to_string(),
theme_text: config::THEME_TEXT.to_string(), theme_text: config::THEME_TEXT.to_string(),

View File

@@ -1,14 +1,13 @@
import { build } from 'tsup'
import pkg from './package.json' with { type: 'json' } import pkg from './package.json' with { type: 'json' }
import { build } from 'tsup'
const watch = process.argv.slice(2)[0] === '--watch' const watch = process.argv.slice(2)[0] === '--watch'
await build({ await build({
entry: ['src/index.ts', 'src/cli.ts', 'src/shared/shared.ts'], entry: ['src/index.ts', 'src/cli.ts'],
dts: true, dts: true,
minify: true, minify: true,
format: ['esm', 'cjs'], format: ['esm', 'cjs'],
target: 'es2020',
clean: true, clean: true,
define: { VERSION: `"${pkg.version}"` }, define: { VERSION: `"${pkg.version}"` },
watch, watch,

View File

@@ -1,6 +1,6 @@
{ {
"name": "cryptgeon", "name": "cryptgeon",
"version": "2.8.4", "version": "2.7.0",
"homepage": "https://github.com/cupcakearmy/cryptgeon", "homepage": "https://github.com/cupcakearmy/cryptgeon",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -9,11 +9,7 @@
}, },
"type": "module", "type": "module",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js"
"./shared": {
"import": "./dist/shared/shared.js",
"types": "./dist/shared/shared.d.ts"
}
}, },
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"bin": { "bin": {
@@ -29,14 +25,15 @@
"prepublishOnly": "run-s build" "prepublishOnly": "run-s build"
}, },
"devDependencies": { "devDependencies": {
"@commander-js/extra-typings": "^12.1.0", "@commander-js/extra-typings": "^12.0.1",
"@cryptgeon/shared": "workspace:*",
"@types/inquirer": "^9.0.7", "@types/inquirer": "^9.0.7",
"@types/mime": "^4.0.0", "@types/mime": "^3.0.4",
"@types/node": "^20.11.24", "@types/node": "^20.11.24",
"commander": "^12.1.0", "commander": "^12.0.0",
"inquirer": "^9.2.15", "inquirer": "^9.2.15",
"mime": "^4.0.1", "mime": "^4.0.1",
"occulto": "^2.0.6", "occulto": "^2.0.3",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"tsup": "^8.2.4", "tsup": "^8.2.4",
"typescript": "^5.3.3" "typescript": "^5.3.3"

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Argument, Option, program } from '@commander-js/extra-typings' import { Argument, Option, program } from '@commander-js/extra-typings'
import { setOptions, status } from '@cryptgeon/shared'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import { download } from './actions/download.js' import { download } from './download.js'
import { upload } from './actions/upload.js' import { parseFile, parseNumber } from './parsers.js'
import { API } from './shared/api.js' import { getStdin } from './stdin.js'
import { parseFile, parseNumber } from './utils/parsers.js' import { upload } from './upload.js'
import { getStdin } from './utils/stdin.js' import { checkConstrains, exit } from './utils.js'
import { checkConstrains, exit } from './utils/utils.js'
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org' const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer) const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
@@ -33,8 +33,8 @@ program
.description('show information about the server') .description('show information about the server')
.addOption(server) .addOption(server)
.action(async (options) => { .action(async (options) => {
API.setOptions({ server: options.server }) setOptions({ server: options.server })
const response = await API.status() const response = await status()
const formatted = { const formatted = {
...response, ...response,
max_size: prettyBytes(response.max_size), max_size: prettyBytes(response.max_size),
@@ -54,7 +54,7 @@ send
.addOption(minutes) .addOption(minutes)
.addOption(password) .addOption(password)
.action(async (files, options) => { .action(async (files, options) => {
API.setOptions({ server: options.server }) setOptions({ server: options.server })
await checkConstrains(options) await checkConstrains(options)
options.password ||= await getStdin() options.password ||= await getStdin()
try { try {
@@ -72,7 +72,7 @@ send
.addOption(minutes) .addOption(minutes)
.addOption(password) .addOption(password)
.action(async (text, options) => { .action(async (text, options) => {
API.setOptions({ server: options.server }) setOptions({ server: options.server })
await checkConstrains(options) await checkConstrains(options)
options.password ||= await getStdin() options.password ||= await getStdin()
try { try {

View File

@@ -1,15 +1,14 @@
import { Adapters, get, info, setOptions } from '@cryptgeon/shared'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import { access, constants, writeFile } from 'node:fs/promises' import { access, constants, writeFile } from 'node:fs/promises'
import { basename, resolve } from 'node:path' import { basename, resolve } from 'node:path'
import { AES, Hex } from 'occulto' import { AES, Hex } from 'occulto'
import pretty from 'pretty-bytes' import pretty from 'pretty-bytes'
import { Adapters } from '../shared/adapters.js'
import { API } from '../shared/api.js'
export async function download(url: URL, all: boolean, suggestedPassword?: string) { export async function download(url: URL, all: boolean, suggestedPassword?: string) {
API.setOptions({ server: url.origin }) setOptions({ server: url.origin })
const id = url.pathname.split('/')[2] const id = url.pathname.split('/')[2]
const preview = await API.info(id).catch(() => { const preview = await info(id).catch(() => {
throw new Error('Note does not exist or is expired') throw new Error('Note does not exist or is expired')
}) })
@@ -34,7 +33,7 @@ export async function download(url: URL, all: boolean, suggestedPassword?: strin
} }
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password) const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
const note = await API.get(id) const note = await get(id)
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password') const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
switch (note.meta.type) { switch (note.meta.type) {

View File

@@ -1,4 +1,4 @@
export * from './actions/download.js' export * from '@cryptgeon/shared'
export * from './actions/upload.js' export * from './download.js'
export * from './shared/adapters.js' export * from './upload.js'
export * from './shared/api.js' export * from './utils.js'

View File

@@ -21,7 +21,7 @@ export function parseURL(value: string, _: URL): URL {
} }
export function parseNumber(value: string, _: number): number { export function parseNumber(value: string, _: number): number {
const n = Number.parseInt(value, 10) const n = parseInt(value, 10)
if (Number.isNaN(n)) throw new InvalidOptionArgumentError('invalid number') if (isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
return n return n
} }

View File

@@ -18,7 +18,6 @@ export function getStdin(timeout: number = 10): Promise<string> {
resolve('') resolve('')
}, timeout) }, timeout)
process.stdin.on('error', reject)
process.stdin.on('data', dataHandler) process.stdin.on('data', dataHandler)
process.stdin.on('end', endHandler) process.stdin.on('end', endHandler)
}) })

View File

@@ -1,10 +1,9 @@
import { readFile, stat } from 'node:fs/promises' import { readFile, stat } from 'node:fs/promises'
import { basename } from 'node:path' import { basename } from 'node:path'
import { Adapters, create, getOptions, FileDTO, Note, NoteMeta } from '@cryptgeon/shared'
import mime from 'mime' import mime from 'mime'
import { AES, Hex } from 'occulto' import { AES, Hex } from 'occulto'
import { Adapters } from '../shared/adapters.js'
import { API, FileDTO, Note, NoteMeta } from '../shared/api.js'
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string } export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
@@ -39,8 +38,8 @@ export async function upload(input: string | string[], options: UploadOptions):
// Create the actual note and upload it. // Create the actual note and upload it.
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } } const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
const result = await API.create(note) const result = await create(note)
let url = `${API.getOptions().server}/note/${result.id}` let url = `${getOptions().server}/note/${result.id}`
if (!derived) url += `#${Hex.encode(key)}` if (!derived) url += `#${Hex.encode(key)}`
return url return url
} }

View File

@@ -1,5 +1,5 @@
import { status } from '@cryptgeon/shared'
import { exit as exitNode } from 'node:process' import { exit as exitNode } from 'node:process'
import { API } from '../shared/api.js'
export function exit(message: string) { export function exit(message: string) {
console.error(message) console.error(message)
@@ -11,7 +11,7 @@ export async function checkConstrains(constrains: { views?: number; minutes?: nu
if (views && minutes) exit('cannot set view and minutes constrains simultaneously') if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
if (!views && !minutes) constrains.views = 1 if (!views && !minutes) constrains.views = 1
const response = await API.status() const response = await status()
if (views && views > response.max_views) if (views && views > response.max_views)
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`) exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
if (minutes && minutes > response.max_expiration) if (minutes && minutes > response.max_expiration)

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "es2022", "target": "es2022",
"module": "es2022", "module": "es2022",
"moduleResolution": "Bundler", "moduleResolution": "node",
"declaration": true, "declaration": true,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"strict": true, "strict": true,

View File

@@ -1,58 +0,0 @@
{
"common": {
"note": "筆記",
"file": "檔案",
"advanced": "進階",
"create": "創建",
"loading": "載入中",
"mode": "模式",
"views": "{n, plural, =0 {瀏覽次數} =1 {1 次瀏覽} other {# 次瀏覽}}",
"minutes": "{n, plural, =0 {分鐘} =1 {1 分鐘} other {# 分鐘}}",
"max": "最大",
"share_link": "分享連結",
"copy_clipboard": "複製到剪貼板",
"copied_to_clipboard": "已複製到剪貼板",
"encrypting": "加密中",
"decrypting": "解密中",
"uploading": "上傳中",
"downloading": "下載中",
"qr_code": "QR 碼",
"password": "密碼"
},
"home": {
"intro": "輕鬆地以一鍵傳送<i>完全加密</i>的安全筆記或檔案。只需創建筆記並分享連結。",
"explanation": "筆記將在 {type} 後過期並被銷毀。",
"new_note": "新筆記",
"new_note_notice": "<b>可用性:</b><br />筆記不保證被儲存,因為所有內容都保留在 RAM 中,如果 RAM 填滿,最舊的筆記將被移除。<br />(您可能會沒事,只是提醒一下。)",
"errors": {
"note_to_big": "無法創建筆記。筆記過大",
"note_error": "無法創建筆記。請再試一次。",
"max": "最大值:{n}",
"empty_content": "筆記內容為空。"
},
"messages": {
"note_created": "筆記已創建。"
},
"advanced": {
"explanation": "預設情況下,每個筆記都會使用安全生成的密碼。您也可以選擇自己的密碼,該密碼不會包含在連結中。",
"custom_password": "自定義密碼"
}
},
"show": {
"errors": {
"not_found": "筆記未找到或已被刪除。",
"decryption_failed": "密碼錯誤。無法解密。可能是連結已損壞。筆記已被銷毀。",
"unsupported_type": "不支持的筆記類型。"
},
"explanation": "如果計數器達到限制,請點擊下方以顯示並刪除筆記",
"show_note": "顯示筆記",
"warning_will_not_see_again": "您將<b>無法</b>再次查看筆記。",
"download_all": "全部下載",
"links_found": "在筆記中找到的連結:"
},
"file_upload": {
"selected_files": "已選擇的檔案",
"no_files_selected": "未選擇檔案",
"clear": "重置"
}
}

View File

@@ -17,6 +17,7 @@
"@sveltejs/adapter-static": "^3.0.1", "@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.2", "@sveltejs/kit": "^2.5.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2", "@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/file-saver": "^2.0.7",
"@zerodevx/svelte-toast": "^0.9.5", "@zerodevx/svelte-toast": "^0.9.5",
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@@ -28,9 +29,11 @@
"vite": "^5.1.7" "vite": "^5.1.7"
}, },
"dependencies": { "dependencies": {
"cryptgeon": "workspace:*", "@cryptgeon/shared": "workspace:*",
"@fontsource/fira-mono": "^5.0.8", "@fontsource/fira-mono": "^5.0.8",
"occulto": "^2.0.6", "copy-to-clipboard": "^3.3.3",
"file-saver": "^2.0.5",
"occulto": "^2.0.3",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"qrious": "^4.0.2" "qrious": "^4.0.2"
} }

View File

@@ -1,8 +1,8 @@
import { API, type Status } from 'cryptgeon/shared' import { status as getStatus, type Status } from '@cryptgeon/shared'
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
export const status = writable<null | Status>(null) export const status = writable<null | Status>(null)
export async function init() { export async function init() {
status.set(await API.status()) status.set(await getStatus())
} }

View File

@@ -1,11 +1,11 @@
import { toast } from '@zerodevx/svelte-toast' import { toast, type SvelteToastOptions } from '@zerodevx/svelte-toast'
export enum NotifyType { export enum NotifyType {
Success = 'success', Success = 'success',
Error = 'error', Error = 'error',
} }
const themeMapping: Record<NotifyType, Record<string, string>> = { const themeMapping: Record<NotifyType, SvelteToastOptions['theme']> = {
[NotifyType.Success]: { [NotifyType.Success]: {
'--toastBackground': 'var(--ui-clr-primary)', '--toastBackground': 'var(--ui-clr-primary)',
'--toastBarBackground': 'var(--ui-clr-primary-alt)', '--toastBarBackground': 'var(--ui-clr-primary-alt)',
@@ -17,7 +17,7 @@ const themeMapping: Record<NotifyType, Record<string, string>> = {
} }
function notifyFN(message: string, type: NotifyType = NotifyType.Success) { function notifyFN(message: string, type: NotifyType = NotifyType.Success) {
const options = { const options: SvelteToastOptions = {
duration: 5_000, duration: 5_000,
theme: { theme: {
...themeMapping[type], ...themeMapping[type],

View File

@@ -4,7 +4,7 @@
import { status } from '$lib/stores/status' import { status } from '$lib/stores/status'
import Switch from '$lib/ui/Switch.svelte' import Switch from '$lib/ui/Switch.svelte'
import TextInput from '$lib/ui/TextInput.svelte' import TextInput from '$lib/ui/TextInput.svelte'
import type { Note } from 'cryptgeon/shared' import type { Note } from '@cryptgeon/shared'
export let note: Note export let note: Note
export let timeExpiration = false export let timeExpiration = false

View File

@@ -3,7 +3,7 @@
import Button from '$lib/ui/Button.svelte' import Button from '$lib/ui/Button.svelte'
import MaxSize from '$lib/ui/MaxSize.svelte' import MaxSize from '$lib/ui/MaxSize.svelte'
import type { FileDTO } from 'cryptgeon/shared' import type { FileDTO } from '@cryptgeon/shared'
export let label: string = '' export let label: string = ''
export let files: FileDTO[] = [] export let files: FileDTO[] = []

View File

@@ -18,7 +18,7 @@
export let icon: keyof typeof map export let icon: keyof typeof map
</script> </script>
<button type="button" on:click {...$$restProps}> <button on:click {...$$restProps}>
{#if map[icon]} {#if map[icon]}
<svelte:component this={map[icon]} /> <svelte:component this={map[icon]} />
{/if} {/if}

View File

@@ -1,26 +1,16 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any } export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
function saveAs(file: File) {
const url = window.URL.createObjectURL(file)
const a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.download = file.name
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
a.remove()
}
</script> </script>
<script lang="ts"> <script lang="ts">
import pkg from 'file-saver'
const { saveAs } = pkg
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 '$lib/ui/Button.svelte' import Button from '$lib/ui/Button.svelte'
import { copy } from '$lib/utils' import { copy } from '$lib/utils'
import type { FileDTO, NotePublic } from 'cryptgeon/shared' import type { FileDTO, NotePublic } from '@cryptgeon/shared'
export let note: DecryptedNote export let note: DecryptedNote

View File

@@ -39,23 +39,13 @@
<input bind:value {...$$restProps} class:valid={valid === true} /> <input bind:value {...$$restProps} class:valid={valid === true} />
<div class="icons"> <div class="icons">
{#if isPassword} {#if isPassword}
<Icon <Icon class="icon" icon={hidden ? 'eye' : 'eye-off'} on:click={toggle} />
disabled={$$restProps.disabled}
class="icon"
icon={hidden ? 'eye' : 'eye-off'}
on:click={toggle}
/>
{/if} {/if}
{#if random} {#if random}
<Icon disabled={$$restProps.disabled} class="icon" icon="dice" on:click={randomFN} /> <Icon class="icon" icon="dice" on:click={randomFN} />
{/if} {/if}
{#if copy} {#if copy}
<Icon <Icon class="icon" icon="copy" on:click={() => copyFN(value.toString())} />
disabled={$$restProps.disabled}
class="icon"
icon="copy"
on:click={() => copyFN(value.toString())}
/>
{/if} {/if}
</div> </div>
</label> </label>

View File

@@ -1,10 +1,11 @@
import copyToClipboard from 'copy-to-clipboard'
import { t } from 'svelte-intl-precompile' import { t } from 'svelte-intl-precompile'
import { get } from 'svelte/store' import { get } from 'svelte/store'
import { notify } from './toast' import { notify } from './toast'
export function copy(value: string) { export function copy(value: string) {
window.navigator.clipboard.writeText(value) copyToClipboard(value)
const msg = get(t)('common.copied_to_clipboard') const msg = get(t)('common.copied_to_clipboard')
notify.success(msg) notify.success(msg)
} }

View File

@@ -13,7 +13,8 @@
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte' import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
import Switch from '$lib/ui/Switch.svelte' import Switch from '$lib/ui/Switch.svelte'
import TextArea from '$lib/ui/TextArea.svelte' import TextArea from '$lib/ui/TextArea.svelte'
import { Adapters, API, PayloadToLargeError, type FileDTO, type Note } from 'cryptgeon/shared' import type { FileDTO, Note } from '@cryptgeon/shared'
import { Adapters, PayloadToLargeError, create } from '@cryptgeon/shared'
let note: Note = { let note: Note = {
contents: '', contents: '',
@@ -76,7 +77,7 @@
else data.views = parseInt(note.views as any) else data.views = parseInt(note.views as any)
loading = $t('common.uploading') loading = $t('common.uploading')
const response = await API.create(data) const response = await create(data)
result = { result = {
id: response.id, id: response.id,
password: customPassword ? undefined : Hex.encode(key), password: customPassword ? undefined : Hex.encode(key),

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import ThemeToggle from '$lib/ui/ThemeToggle.svelte' import ThemeToggle from '$lib/ui/ThemeToggle.svelte'
import { status } from '$lib/stores/status'
</script> </script>
<footer> <footer>
@@ -8,11 +7,6 @@
<nav> <nav>
<a href="/">/home</a> <a href="/">/home</a>
<a href="/about">/about</a> <a href="/about">/about</a>
{#if $status?.imprint_url}
<a href={$status.imprint_url} target="_blank" rel="noopener noreferrer">/imprint</a>
{:else if $status?.imprint_html}
<a href="/imprint">/imprint</a>
{/if}
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener noreferrer"> <a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener noreferrer">
code code
</a> </a>

View File

@@ -1,35 +0,0 @@
<script lang="ts">
import { get } from 'svelte/store';
import { goto } from '$app/navigation';
import { status } from '$lib/stores/status'
status.subscribe((config) => {
if (config != null) {
if (config.imprint_url) {
window.location = config.imprint_url;
}
else if (config.imprint_html == "") {
goto("/about");
}
}
});
</script>
<svelte:head>
<title>Imprint</title>
</svelte:head>
<section class="content">
{#if $status?.imprint_html}
{@html $status.imprint_html}
{/if}
</section>
<style>
section {
width: 100%;
display: flex;
flex-direction: column;
gap: 2rem;
}
</style>

View File

@@ -7,7 +7,7 @@
import Loader from '$lib/ui/Loader.svelte' import Loader from '$lib/ui/Loader.svelte'
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte' import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
import TextInput from '$lib/ui/TextInput.svelte' import TextInput from '$lib/ui/TextInput.svelte'
import { Adapters, API, type NoteMeta } from 'cryptgeon/shared' import { Adapters, get, info, type NoteMeta } from '@cryptgeon/shared'
import type { PageData } from './$types' import type { PageData } from './$types'
export let data: PageData export let data: PageData
@@ -28,7 +28,7 @@
try { try {
loading = $t('common.loading') loading = $t('common.loading')
password = window.location.hash.slice(1) password = window.location.hash.slice(1)
const note = await API.info(id) const note = await info(id)
meta = note.meta meta = note.meta
exists = true exists = true
} catch { } catch {
@@ -51,7 +51,7 @@
// Load note // Load note
error = null error = null
loading = $t('common.downloading') loading = $t('common.downloading')
const data = await API.get(id) const data = await get(id)
loading = $t('common.decrypting') loading = $t('common.decrypting')
const derived = meta?.derivation && (await AES.derive(password!, meta.derivation)) const derived = meta?.derivation && (await AES.derive(password!, meta.derivation))
const key = derived ? derived[0] : Hex.decode(password!) const key = derived ? derived[0] : Hex.decode(password!)

View File

@@ -0,0 +1,22 @@
{
"private": true,
"name": "@cryptgeon/shared",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"dev": "tsc -w",
"build": "tsc"
},
"devDependencies": {
"typescript": "^5.3.3"
},
"dependencies": {
"occulto": "^2.0.3"
}
}

View File

@@ -39,15 +39,15 @@ export let client: ClientOptions = {
server: '', server: '',
} }
function setOptions(options: Partial<ClientOptions>) { export function setOptions(options: Partial<ClientOptions>) {
client = { ...client, ...options } client = { ...client, ...options }
} }
function getOptions(): ClientOptions { export function getOptions(): ClientOptions {
return client return client
} }
async function call(options: CallOptions) { export async function call(options: CallOptions) {
const url = client.server + '/api/' + options.url const url = client.server + '/api/' + options.url
const response = await fetch(url, { const response = await fetch(url, {
method: options.method, method: options.method,
@@ -65,7 +65,7 @@ async function call(options: CallOptions) {
return response.json() return response.json()
} }
async function create(note: Note) { export async function create(note: Note) {
const { meta, ...rest } = note const { meta, ...rest } = note
const body: NoteCreate = { const body: NoteCreate = {
...rest, ...rest,
@@ -79,7 +79,7 @@ async function create(note: Note) {
return data as { id: string } return data as { id: string }
} }
async function get(id: string): Promise<NotePublic> { export async function get(id: string): Promise<NotePublic> {
const data = await call({ const data = await call({
url: `notes/${id}`, url: `notes/${id}`,
method: 'delete', method: 'delete',
@@ -93,7 +93,7 @@ async function get(id: string): Promise<NotePublic> {
return note return note
} }
async function info(id: string): Promise<NoteInfo> { export async function info(id: string): Promise<NoteInfo> {
const data = await call({ const data = await call({
url: `notes/${id}`, url: `notes/${id}`,
method: 'get', method: 'get',
@@ -112,9 +112,6 @@ export type Status = {
max_views: number max_views: number
max_expiration: number max_expiration: number
allow_advanced: boolean allow_advanced: boolean
allow_files: boolean
imprint_url: string
imprint_html: string
theme_image: string theme_image: string
theme_text: string theme_text: string
theme_favicon: string theme_favicon: string
@@ -122,19 +119,10 @@ export type Status = {
theme_new_note_notice: boolean theme_new_note_notice: boolean
} }
async function status() { export async function status() {
const data = await call({ const data = await call({
url: 'status/', url: 'status/',
method: 'get', method: 'get',
}) })
return data as Status return data as Status
} }
export const API = {
setOptions,
getOptions,
create,
get,
info,
status,
}

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"incremental": true,
"composite": true,
"target": "es2022",
"module": "es2022",
"rootDir": "./src",
"moduleResolution": "node",
"outDir": "./dist",
"strict": true
}
}

View File

@@ -10,11 +10,9 @@ const config: PlaywrightTestConfig = {
outputDir: './test-results', outputDir: './test-results',
testDir: './test', testDir: './test',
timeout: 30_000, timeout: 30_000,
fullyParallel: true,
retries: 2,
webServer: { webServer: {
command: 'pnpm run docker:up', command: 'docker compose -f docker-compose.dev.yaml up',
port: 3000, port: 3000,
reuseExistingServer: true, reuseExistingServer: true,
}, },

103
pnpm-lock.yaml generated
View File

@@ -26,20 +26,23 @@ importers:
packages/cli: packages/cli:
devDependencies: devDependencies:
'@commander-js/extra-typings': '@commander-js/extra-typings':
specifier: ^12.1.0 specifier: ^12.0.1
version: 12.1.0(commander@12.1.0) version: 12.0.1(commander@12.0.0)
'@cryptgeon/shared':
specifier: workspace:*
version: link:../shared
'@types/inquirer': '@types/inquirer':
specifier: ^9.0.7 specifier: ^9.0.7
version: 9.0.7 version: 9.0.7
'@types/mime': '@types/mime':
specifier: ^4.0.0 specifier: ^3.0.4
version: 4.0.0 version: 3.0.4
'@types/node': '@types/node':
specifier: ^20.11.24 specifier: ^20.11.24
version: 20.11.24 version: 20.11.24
commander: commander:
specifier: ^12.1.0 specifier: ^12.0.0
version: 12.1.0 version: 12.0.0
inquirer: inquirer:
specifier: ^9.2.15 specifier: ^9.2.15
version: 9.2.15 version: 9.2.15
@@ -47,8 +50,8 @@ importers:
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
occulto: occulto:
specifier: ^2.0.6 specifier: ^2.0.3
version: 2.0.6 version: 2.0.3
pretty-bytes: pretty-bytes:
specifier: ^6.1.1 specifier: ^6.1.1
version: 6.1.1 version: 6.1.1
@@ -61,15 +64,21 @@ importers:
packages/frontend: packages/frontend:
dependencies: dependencies:
'@cryptgeon/shared':
specifier: workspace:*
version: link:../shared
'@fontsource/fira-mono': '@fontsource/fira-mono':
specifier: ^5.0.8 specifier: ^5.0.8
version: 5.0.8 version: 5.0.8
cryptgeon: copy-to-clipboard:
specifier: workspace:* specifier: ^3.3.3
version: link:../cli version: 3.3.3
file-saver:
specifier: ^2.0.5
version: 2.0.5
occulto: occulto:
specifier: ^2.0.6 specifier: ^2.0.3
version: 2.0.6 version: 2.0.3
pretty-bytes: pretty-bytes:
specifier: ^6.1.1 specifier: ^6.1.1
version: 6.1.1 version: 6.1.1
@@ -89,6 +98,9 @@ importers:
'@sveltejs/vite-plugin-svelte': '@sveltejs/vite-plugin-svelte':
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2(svelte@4.2.12)(vite@5.1.7(@types/node@22.5.0)) version: 3.0.2(svelte@4.2.12)(vite@5.1.7(@types/node@22.5.0))
'@types/file-saver':
specifier: ^2.0.7
version: 2.0.7
'@zerodevx/svelte-toast': '@zerodevx/svelte-toast':
specifier: ^0.9.5 specifier: ^0.9.5
version: 0.9.5(svelte@4.2.12) version: 0.9.5(svelte@4.2.12)
@@ -123,6 +135,16 @@ importers:
specifier: ^1.18.1 specifier: ^1.18.1
version: 1.18.1 version: 1.18.1
packages/shared:
dependencies:
occulto:
specifier: ^2.0.3
version: 2.0.3
devDependencies:
typescript:
specifier: ^5.3.3
version: 5.3.3
packages: packages:
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
@@ -220,10 +242,10 @@ packages:
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@commander-js/extra-typings@12.1.0': '@commander-js/extra-typings@12.0.1':
resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==} resolution: {integrity: sha512-OvkMobb1eMqOCuJdbuSin/KJkkZr7n24/UNV+Lcz/0Dhepf3r2p9PaGwpRpAWej7A+gQnny4h8mGhpFl4giKkg==}
peerDependencies: peerDependencies:
commander: ~12.1.0 commander: ~12.0.0
'@esbuild/aix-ppc64@0.19.12': '@esbuild/aix-ppc64@0.19.12':
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
@@ -756,12 +778,14 @@ packages:
'@types/estree@1.0.5': '@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
'@types/inquirer@9.0.7': '@types/inquirer@9.0.7':
resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==} resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==}
'@types/mime@4.0.0': '@types/mime@3.0.4':
resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==} resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed.
'@types/node@20.11.24': '@types/node@20.11.24':
resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==} resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==}
@@ -956,8 +980,8 @@ packages:
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
commander@12.1.0: commander@12.0.0:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==}
engines: {node: '>=18'} engines: {node: '>=18'}
commander@4.1.1: commander@4.1.1:
@@ -978,6 +1002,9 @@ packages:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
cross-spawn@6.0.5: cross-spawn@6.0.5:
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
engines: {node: '>=4.8'} engines: {node: '>=4.8'}
@@ -1126,6 +1153,9 @@ packages:
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
engines: {node: '>=8'} engines: {node: '>=8'}
file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
fill-range@7.0.1: fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1588,9 +1618,9 @@ packages:
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
occulto@2.0.6: occulto@2.0.3:
resolution: {integrity: sha512-oaCwtnQjr+fTfFVfSPEDC5rh+L13OcEJ6uQzhmG8PlLxxYn2MxTDPmoCUVHKD7rUxwxCpH7/N4hbTu5U4mqZag==} resolution: {integrity: sha512-rLXDNqsxM3Gusp4cn8QsveeKsmX36SbnIUcUc3mSeM88pGsNY5Tn6VnWItw/7zST01z9gtaZWQFk0F1L53jMCQ==}
engines: {node: '>=18'} engines: {node: '>=16', npm: please-use-pnpm, pnpm: '>=8', yarn: please-use-pnpm}
once@1.4.0: once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -2049,6 +2079,9 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
totalist@3.0.1: totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -2344,9 +2377,9 @@ snapshots:
'@babel/helper-validator-identifier': 7.22.20 '@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
'@commander-js/extra-typings@12.1.0(commander@12.1.0)': '@commander-js/extra-typings@12.0.1(commander@12.0.0)':
dependencies: dependencies:
commander: 12.1.0 commander: 12.0.0
'@esbuild/aix-ppc64@0.19.12': '@esbuild/aix-ppc64@0.19.12':
optional: true optional: true
@@ -2700,14 +2733,14 @@ snapshots:
'@types/estree@1.0.5': {} '@types/estree@1.0.5': {}
'@types/file-saver@2.0.7': {}
'@types/inquirer@9.0.7': '@types/inquirer@9.0.7':
dependencies: dependencies:
'@types/through': 0.0.33 '@types/through': 0.0.33
rxjs: 7.8.1 rxjs: 7.8.1
'@types/mime@4.0.0': '@types/mime@3.0.4': {}
dependencies:
mime: 4.0.1
'@types/node@20.11.24': '@types/node@20.11.24':
dependencies: dependencies:
@@ -2909,7 +2942,7 @@ snapshots:
color-name@1.1.4: {} color-name@1.1.4: {}
commander@12.1.0: {} commander@12.0.0: {}
commander@4.1.1: {} commander@4.1.1: {}
@@ -2921,6 +2954,10 @@ snapshots:
cookie@0.6.0: {} cookie@0.6.0: {}
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
cross-spawn@6.0.5: cross-spawn@6.0.5:
dependencies: dependencies:
nice-try: 1.0.5 nice-try: 1.0.5
@@ -3153,6 +3190,8 @@ snapshots:
dependencies: dependencies:
escape-string-regexp: 1.0.5 escape-string-regexp: 1.0.5
file-saver@2.0.5: {}
fill-range@7.0.1: fill-range@7.0.1:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
@@ -3584,7 +3623,7 @@ snapshots:
has-symbols: 1.0.3 has-symbols: 1.0.3
object-keys: 1.1.1 object-keys: 1.1.1
occulto@2.0.6: {} occulto@2.0.3: {}
once@1.4.0: once@1.4.0:
dependencies: dependencies:
@@ -4078,6 +4117,8 @@ snapshots:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
toggle-selection@1.0.6: {}
totalist@3.0.1: {} totalist@3.0.1: {}
tr46@1.0.1: tr46@1.0.1:

View File

@@ -3,8 +3,8 @@ import { Files } from '../../files'
import { createNoteError } from '../../utils' import { createNoteError } from '../../utils'
test.describe('@web', () => { test.describe('@web', () => {
test.skip('to big zip', async ({ page }) => { test('to big zip', async ({ page }) => {
const files = [Files.Zip] const files = [Files.Zip]
await createNoteError(page, { files }, 'could not create note. note is too big') await createNoteError(page, { files }, 'note is to big')
}) })
}) })

View File

@@ -2,8 +2,7 @@ import { test } from '@playwright/test'
import { checkLinkDoesNotExist, checkLinkForText, createNoteSuccessfully } from '../../utils' import { checkLinkDoesNotExist, checkLinkForText, createNoteSuccessfully } from '../../utils'
test.describe('@web', () => { test.describe('@web', () => {
test('1 minute', async ({ page, browserName }) => { test('1 minute', async ({ page }) => {
test.skip(browserName === 'webkit')
const text = `Virtues value ascetic revaluation sea dead strong burying.` const text = `Virtues value ascetic revaluation sea dead strong burying.`
const minutes = 1 const minutes = 1
const timeout = minutes * 60_000 const timeout = minutes * 60_000