update to svelte 5
@ -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.15.4"
|
||||||
}
|
}
|
||||||
|
@ -13,23 +13,23 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lokalise/node-api": "^12.1.0",
|
"@lokalise/node-api": "^13.0.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@sveltejs/kit": "^2.5.2",
|
"@sveltejs/kit": "^2.16.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@zerodevx/svelte-toast": "^0.9.5",
|
"@zerodevx/svelte-toast": "^0.9.6",
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.16",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.7",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^5.19.0",
|
||||||
"svelte-check": "^3.6.6",
|
"svelte-check": "^4.1.4",
|
||||||
"svelte-intl-precompile": "^0.12.3",
|
"svelte-intl-precompile": "^0.12.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^5.1.7"
|
"vite": "^6.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/fira-mono": "^5.1.1",
|
||||||
"cryptgeon": "workspace:*",
|
"cryptgeon": "workspace:*",
|
||||||
"@fontsource/fira-mono": "^5.0.8",
|
|
||||||
"occulto": "^2.0.6",
|
"occulto": "^2.0.6",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"qrious": "^4.0.2"
|
"qrious": "^4.0.2"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
||||||
><title>Contrast</title><path
|
><title>Contrast</title><path
|
||||||
d="M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z"
|
d="M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z"
|
||||||
|
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 316 B |
@ -1,3 +1,5 @@
|
|||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
||||||
><title>Copy</title><path
|
><title>Copy</title><path
|
||||||
d="M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z"
|
d="M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z"
|
||||||
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 354 B |
@ -1,3 +1,5 @@
|
|||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
||||||
><title>Dice</title><path
|
><title>Dice</title><path
|
||||||
d="M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z"
|
d="M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z"
|
||||||
|
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 765 B |
@ -1,3 +1,5 @@
|
|||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
||||||
><title>Eye</title><circle cx="256" cy="256" r="64" /><path
|
><title>Eye</title><circle cx="256" cy="256" r="64" /><path
|
||||||
d="M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z"
|
d="M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z"
|
||||||
|
Before Width: | Height: | Size: 483 B After Width: | Height: | Size: 512 B |
@ -1,3 +1,5 @@
|
|||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
|
||||||
><title>Eye Off</title><path
|
><title>Eye Off</title><path
|
||||||
d="M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z"
|
d="M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z"
|
||||||
|
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 761 B |
@ -1,10 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let title: string
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
children?: Snippet
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, children }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>▶ {title}</b>
|
<b>▶ {title}</b>
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -6,13 +6,23 @@
|
|||||||
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
|
interface Props {
|
||||||
export let timeExpiration = false
|
note: Note
|
||||||
export let customPassword: string | null = null
|
timeExpiration?: boolean
|
||||||
|
customPassword?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
let hasCustomPassword = false
|
let {
|
||||||
|
note = $bindable(),
|
||||||
|
timeExpiration = $bindable(false),
|
||||||
|
customPassword = $bindable(null),
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
$: if (!hasCustomPassword) customPassword = null
|
let hasCustomPassword = $state(false)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!hasCustomPassword) customPassword = null
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex col">
|
<div class="flex col">
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
<button {...$$restProps} on:click><slot /></button>
|
<script lang="ts">
|
||||||
|
import type { HTMLButtonAttributes } from 'svelte/elements'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: import('svelte').Snippet
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children, ...rest }: HTMLButtonAttributes & Props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button {...rest}>{@render children?.()}</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
button {
|
||||||
|
@ -5,11 +5,15 @@
|
|||||||
|
|
||||||
import { getCSSVariable } from '$lib/utils'
|
import { getCSSVariable } from '$lib/utils'
|
||||||
|
|
||||||
export let value: string
|
interface Props {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement
|
let { value }: Props = $props()
|
||||||
|
|
||||||
$: {
|
let canvas: HTMLCanvasElement | null = $state(null)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
new QR({
|
new QR({
|
||||||
value,
|
value,
|
||||||
level: 'Q',
|
level: 'Q',
|
||||||
@ -18,12 +22,12 @@
|
|||||||
foreground: getCSSVariable('--ui-text-0'),
|
foreground: getCSSVariable('--ui-text-0'),
|
||||||
element: canvas,
|
element: canvas,
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<small>{$t('common.qr_code')}</small>
|
<small>{$t('common.qr_code')}</small>
|
||||||
<div>
|
<div>
|
||||||
<canvas bind:this={canvas} />
|
<canvas bind:this={canvas}></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -5,8 +5,13 @@
|
|||||||
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 = ''
|
interface Props {
|
||||||
export let files: FileDTO[] = []
|
label?: string
|
||||||
|
files?: FileDTO[]
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
let { label = '', files = $bindable([]), ...rest }: Props = $props()
|
||||||
|
|
||||||
async function fileToDTO(file: File): Promise<FileDTO> {
|
async function fileToDTO(file: File): Promise<FileDTO> {
|
||||||
return {
|
return {
|
||||||
@ -35,7 +40,7 @@
|
|||||||
<small>
|
<small>
|
||||||
{label}
|
{label}
|
||||||
</small>
|
</small>
|
||||||
<input {...$$restProps} type="file" on:change={onInput} multiple />
|
<input {...rest} type="file" onchange={onInput} multiple />
|
||||||
<div class="box">
|
<div class="box">
|
||||||
{#if files.length}
|
{#if files.length}
|
||||||
<div>
|
<div>
|
||||||
@ -45,8 +50,8 @@
|
|||||||
{file.name}
|
{file.name}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="spacer" />
|
<div class="spacer"></div>
|
||||||
<Button on:click={clear}>{$t('file_upload.clear')}</Button>
|
<Button onclick={clear}>{$t('file_upload.clear')}</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" module>
|
||||||
import IconContrast from '$lib/icons/IconContrast.svelte'
|
import IconContrast from '$lib/icons/IconContrast.svelte'
|
||||||
import IconCopy from '$lib/icons/IconCopy.svelte'
|
import IconCopy from '$lib/icons/IconCopy.svelte'
|
||||||
import IconDice from '$lib/icons/IconDice.svelte'
|
import IconDice from '$lib/icons/IconDice.svelte'
|
||||||
import IconEye from '$lib/icons/IconEye.svelte'
|
import IconEye from '$lib/icons/IconEye.svelte'
|
||||||
import IconEyeOff from '$lib/icons/IconEyeOff.svelte'
|
import IconEyeOff from '$lib/icons/IconEyeOff.svelte'
|
||||||
|
import type { HTMLButtonAttributes } from 'svelte/elements'
|
||||||
|
|
||||||
const map = {
|
const map = {
|
||||||
contrast: IconContrast,
|
contrast: IconContrast,
|
||||||
@ -15,12 +16,17 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let icon: keyof typeof map
|
interface Props {
|
||||||
|
icon: keyof typeof map
|
||||||
|
}
|
||||||
|
|
||||||
|
let { icon, ...rest }: HTMLButtonAttributes & Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button type="button" on:click {...$$restProps}>
|
<button type="button" {...rest}>
|
||||||
{#if map[icon]}
|
{#if map[icon]}
|
||||||
<svelte:component this={map[icon]} />
|
{@const SvelteComponent = map[icon]}
|
||||||
|
<SvelteComponent />
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
<script lang="ts"></script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
version="1.1"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
Before Width: | Height: | Size: 784 B After Width: | Height: | Size: 813 B |
@ -1,4 +1,4 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" module>
|
||||||
export type NoteResult = {
|
export type NoteResult = {
|
||||||
id: string
|
id: string
|
||||||
password?: string
|
password?: string
|
||||||
@ -12,9 +12,13 @@
|
|||||||
import TextInput from '$lib/ui/TextInput.svelte'
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
import Canvas from './Canvas.svelte'
|
import Canvas from './Canvas.svelte'
|
||||||
|
|
||||||
export let result: NoteResult
|
interface Props {
|
||||||
|
result: NoteResult
|
||||||
|
}
|
||||||
|
|
||||||
let url = `${window.location.origin}/note/${result.id}`
|
let { result }: Props = $props()
|
||||||
|
|
||||||
|
let url = $state(`${window.location.origin}/note/${result.id}`)
|
||||||
if (result.password) url += `#${result.password}`
|
if (result.password) url += `#${result.password}`
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
@ -41,7 +45,7 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
<br />
|
<br />
|
||||||
<Button on:click={reset}>{$t('home.new_note')}</Button>
|
<Button onclick={reset}>{$t('home.new_note')}</Button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" module>
|
||||||
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
|
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
|
||||||
|
|
||||||
function saveAs(file: File) {
|
function saveAs(file: File) {
|
||||||
@ -22,20 +22,14 @@
|
|||||||
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
|
interface Props {
|
||||||
|
note: DecryptedNote
|
||||||
|
}
|
||||||
|
|
||||||
|
let { note }: Props = $props()
|
||||||
|
|
||||||
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[] = $state([])
|
||||||
|
|
||||||
$: if (note.meta.type === 'file') {
|
|
||||||
files = note.contents
|
|
||||||
}
|
|
||||||
|
|
||||||
$: download = () => {
|
|
||||||
for (const file of files) {
|
|
||||||
downloadFile(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadFile(file: FileDTO) {
|
async function downloadFile(file: FileDTO) {
|
||||||
const f = new File([file.contents], file.name, {
|
const f = new File([file.contents], file.name, {
|
||||||
@ -44,7 +38,17 @@
|
|||||||
saveAs(f)
|
saveAs(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: links = typeof note.contents === 'string' ? note.contents.match(RE_URL) : []
|
$effect(() => {
|
||||||
|
if (note.meta.type === 'file') {
|
||||||
|
files = note.contents
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let download = $derived(() => {
|
||||||
|
for (const file of files) {
|
||||||
|
downloadFile(file)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let links = $derived(typeof note.contents === 'string' ? note.contents.match(RE_URL) : [])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
|
<p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
|
||||||
@ -53,7 +57,7 @@
|
|||||||
<div class="note">
|
<div class="note">
|
||||||
{note.contents}
|
{note.contents}
|
||||||
</div>
|
</div>
|
||||||
<Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
|
<Button onclick={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
|
||||||
|
|
||||||
{#if links && links.length}
|
{#if links && links.length}
|
||||||
<div class="links">
|
<div class="links">
|
||||||
@ -70,13 +74,13 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#each files as file}
|
{#each files as file}
|
||||||
<div class="note file">
|
<div class="note file">
|
||||||
<button on:click={() => downloadFile(file)}>
|
<button onclick={() => downloadFile(file)}>
|
||||||
<b>↓ {file.name}</b>
|
<b>↓ {file.name}</b>
|
||||||
</button>
|
</button>
|
||||||
<small> {file.type} - {prettyBytes(file.size)}</small>
|
<small> {file.type} - {prettyBytes(file.size)}</small>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<Button on:click={download}>{$t('show.download_all')}</Button>
|
<Button onclick={download}>{$t('show.download_all')}</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let label: string = ''
|
interface Props {
|
||||||
export let value: boolean
|
label?: string
|
||||||
export let color = true
|
value: boolean
|
||||||
|
color?: boolean
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
let { label = '', value = $bindable(), color = true, ...rest }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label {...$$restProps}>
|
<label {...rest}>
|
||||||
<small>{label}</small>
|
<small>{label}</small>
|
||||||
<input type="checkbox" bind:checked={value} />
|
<input type="checkbox" bind:checked={value} />
|
||||||
<span class:color class="slider" />
|
<span class:color class="slider"></span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let label: string = ''
|
interface Props {
|
||||||
export let value: string
|
label?: string
|
||||||
|
value: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
let { label = '', value = $bindable(), ...rest }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<small>
|
<small>
|
||||||
{label}
|
{label}
|
||||||
</small>
|
</small>
|
||||||
<textarea class="box" {...$$restProps} bind:value />
|
<textarea class="box" {...rest} bind:value></textarea>
|
||||||
</label>
|
</label>
|
||||||
|
@ -2,24 +2,38 @@
|
|||||||
import Icon from '$lib/ui/Icon.svelte'
|
import Icon from '$lib/ui/Icon.svelte'
|
||||||
import { copy as copyFN } from '$lib/utils'
|
import { copy as copyFN } from '$lib/utils'
|
||||||
import { getRandomBytes, Hex } from 'occulto'
|
import { getRandomBytes, Hex } from 'occulto'
|
||||||
|
import type { HTMLInputAttributes } from 'svelte/elements'
|
||||||
|
|
||||||
export let label: string = ''
|
interface Props {
|
||||||
export let value: any
|
label?: string
|
||||||
export let validate: (value: any) => boolean | string = () => true
|
value: any
|
||||||
export let copy: boolean = false
|
validate?: (value: any) => boolean | string
|
||||||
export let random: boolean = false
|
copy?: boolean
|
||||||
|
random?: boolean
|
||||||
const initialType = $$restProps.type
|
|
||||||
const isPassword = initialType === 'password'
|
|
||||||
let hidden = true
|
|
||||||
|
|
||||||
$: valid = validate(value)
|
|
||||||
|
|
||||||
$: if (isPassword) {
|
|
||||||
value
|
|
||||||
$$restProps.type = hidden ? initialType : 'text'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
label = '',
|
||||||
|
value = $bindable(),
|
||||||
|
validate = () => true,
|
||||||
|
copy = false,
|
||||||
|
random = false,
|
||||||
|
...rest
|
||||||
|
}: HTMLInputAttributes & Props = $props()
|
||||||
|
|
||||||
|
const initialType = rest.type
|
||||||
|
const isPassword = initialType === 'password'
|
||||||
|
let hidden = $state(true)
|
||||||
|
|
||||||
|
let valid = $derived(validate(value))
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isPassword) {
|
||||||
|
value
|
||||||
|
rest.type = hidden ? initialType : 'text'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
hidden = !hidden
|
hidden = !hidden
|
||||||
}
|
}
|
||||||
@ -30,31 +44,31 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<small class:disabled={$$restProps.disabled}>
|
<small class:disabled={rest.disabled}>
|
||||||
{label}
|
{label}
|
||||||
{#if valid !== true}
|
{#if valid !== true}
|
||||||
<span class="error-text">{valid}</span>
|
<span class="error-text">{valid}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</small>
|
</small>
|
||||||
<input bind:value {...$$restProps} class:valid={valid === true} />
|
<input bind:value {...rest} class:valid={valid === true} />
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
{#if isPassword}
|
{#if isPassword}
|
||||||
<Icon
|
<Icon
|
||||||
disabled={$$restProps.disabled}
|
disabled={rest.disabled}
|
||||||
class="icon"
|
class="icon"
|
||||||
icon={hidden ? 'eye' : 'eye-off'}
|
icon={hidden ? 'eye' : 'eye-off'}
|
||||||
on:click={toggle}
|
onclick={toggle}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if random}
|
{#if random}
|
||||||
<Icon disabled={$$restProps.disabled} class="icon" icon="dice" on:click={randomFN} />
|
<Icon disabled={rest.disabled} class="icon" icon="dice" onclick={randomFN} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if copy}
|
{#if copy}
|
||||||
<Icon
|
<Icon
|
||||||
disabled={$$restProps.disabled}
|
disabled={rest.disabled}
|
||||||
class="icon"
|
class="icon"
|
||||||
icon="copy"
|
icon="copy"
|
||||||
on:click={() => copyFN(value.toString())}
|
onclick={() => copyFN(value.toString())}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" module>
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
enum Theme {
|
const themes = ['dark', 'light', 'auto'] as const
|
||||||
Dark = 'dark',
|
type Theme = (typeof themes)[number]
|
||||||
Light = 'light',
|
|
||||||
Auto = 'auto',
|
|
||||||
}
|
|
||||||
|
|
||||||
const NextTheme = {
|
const NextTheme: Record<Theme, Theme> = {
|
||||||
[Theme.Auto]: Theme.Light,
|
auto: 'light',
|
||||||
[Theme.Light]: Theme.Dark,
|
light: 'dark',
|
||||||
[Theme.Dark]: Theme.Auto,
|
dark: 'auto',
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(): Theme {
|
function init(): Theme {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const saved = window.localStorage.getItem('theme') as Theme
|
const saved = window.localStorage.getItem('theme') as Theme
|
||||||
if (Object.values(Theme).includes(saved)) return saved
|
if (themes.includes(saved)) return saved
|
||||||
}
|
}
|
||||||
return Theme.Auto
|
return 'auto'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const theme = writable<Theme>(init())
|
export const theme = writable<Theme>(init())
|
||||||
@ -40,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button on:click={change}>
|
<button onclick={change}>
|
||||||
<Icon class="icon" icon="contrast" />
|
<Icon class="icon" icon="contrast" />
|
||||||
{$theme}
|
{$theme}
|
||||||
</button>
|
</button>
|
||||||
|
@ -15,27 +15,29 @@
|
|||||||
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 { Adapters, API, PayloadToLargeError, type FileDTO, type Note } from 'cryptgeon/shared'
|
||||||
|
|
||||||
let note: Note = {
|
let note: Note = $state({
|
||||||
contents: '',
|
contents: '',
|
||||||
meta: { type: 'text' },
|
meta: { type: 'text' },
|
||||||
views: 1,
|
views: 1,
|
||||||
expiration: 60,
|
expiration: 60,
|
||||||
}
|
})
|
||||||
let files: FileDTO[]
|
let files: FileDTO[] = $state([])
|
||||||
let result: NoteResult | null = null
|
let result: NoteResult | null = $state(null)
|
||||||
let advanced = false
|
let advanced = $state(false)
|
||||||
let isFile = false
|
let isFile = $state(false)
|
||||||
let timeExpiration = false
|
let timeExpiration = $state(false)
|
||||||
let customPassword: string | null = null
|
let customPassword: string | null = $state(null)
|
||||||
let description = ''
|
let description = $state('')
|
||||||
let loading: string | null = null
|
let loading: string | null = $state(null)
|
||||||
|
|
||||||
$: if (!advanced) {
|
$effect(() => {
|
||||||
|
if (!advanced) {
|
||||||
note.views = 1
|
note.views = 1
|
||||||
timeExpiration = false
|
timeExpiration = false
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$: {
|
$effect(() => {
|
||||||
description = $t('home.explanation', {
|
description = $t('home.explanation', {
|
||||||
values: {
|
values: {
|
||||||
type: $t(timeExpiration ? 'common.minutes' : 'common.views', {
|
type: $t(timeExpiration ? 'common.minutes' : 'common.views', {
|
||||||
@ -43,17 +45,22 @@
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
$: note.meta.type = isFile ? 'file' : 'text'
|
$effect(() => {
|
||||||
|
note.meta.type = isFile ? 'file' : 'text'
|
||||||
|
})
|
||||||
|
|
||||||
$: if (!isFile) {
|
$effect(() => {
|
||||||
|
if (!isFile) {
|
||||||
note.contents = ''
|
note.contents = ''
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
class EmptyContentError extends Error {}
|
class EmptyContentError extends Error {}
|
||||||
|
|
||||||
async function submit() {
|
async function submit(e: SubmitEvent) {
|
||||||
|
e.preventDefault()
|
||||||
try {
|
try {
|
||||||
loading = $t('common.encrypting')
|
loading = $t('common.encrypting')
|
||||||
|
|
||||||
@ -103,7 +110,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{@html $status?.theme_text || $t('home.intro')}
|
{@html $status?.theme_text || $t('home.intro')}
|
||||||
</p>
|
</p>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form onsubmit={submit}>
|
||||||
<fieldset disabled={loading !== null}>
|
<fieldset disabled={loading !== null}>
|
||||||
{#if isFile}
|
{#if isFile}
|
||||||
<FileUpload data-testid="file-upload" label={$t('common.file')} bind:files />
|
<FileUpload data-testid="file-upload" label={$t('common.file')} bind:files />
|
||||||
@ -132,7 +139,7 @@
|
|||||||
bind:value={advanced}
|
bind:value={advanced}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grow" />
|
<div class="grow"></div>
|
||||||
<div class="tr">
|
<div class="tr">
|
||||||
<small>{$t('common.max')}: <MaxSize /> </small>
|
<small>{$t('common.max')}: <MaxSize /> </small>
|
||||||
<br />
|
<br />
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<a on:click={reset} href="/">
|
<a onclick={reset} href="/">
|
||||||
{#if $status?.theme_image}
|
{#if $status?.theme_image}
|
||||||
<img alt="logo" src={$status.theme_image} />
|
<img alt="logo" src={$status.theme_image} />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
import { init as initStores, status } from '$lib/stores/status'
|
import { init as initStores, status } from '$lib/stores/status'
|
||||||
import Footer from '$lib/views/Footer.svelte'
|
import Footer from '$lib/views/Footer.svelte'
|
||||||
import Header from '$lib/views/Header.svelte'
|
import Header from '$lib/views/Header.svelte'
|
||||||
|
interface Props {
|
||||||
|
children?: import('svelte').Snippet
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children }: Props = $props()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
initStores()
|
initStores()
|
||||||
@ -22,7 +27,7 @@
|
|||||||
{#await waitLocale() then _}
|
{#await waitLocale() then _}
|
||||||
<main>
|
<main>
|
||||||
<Header />
|
<Header />
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<SvelteToast />
|
<SvelteToast />
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { get } from 'svelte/store';
|
import { goto } from '$app/navigation'
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { status } from '$lib/stores/status'
|
import { status } from '$lib/stores/status'
|
||||||
|
|
||||||
status.subscribe((config) => {
|
status.subscribe((config) => {
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
if (config.imprint_url) {
|
if (config.imprint_url) {
|
||||||
window.location = config.imprint_url;
|
window.location.href = config.imprint_url
|
||||||
}
|
} else if (config.imprint_html == '') {
|
||||||
else if (config.imprint_html == "") {
|
goto('/about')
|
||||||
goto("/about");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -20,9 +18,9 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<section class="content">
|
<section class="content">
|
||||||
{#if $status?.imprint_html}
|
{#if $status?.imprint_html}
|
||||||
{@html $status.imprint_html}
|
{@html $status.imprint_html}
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -10,18 +10,22 @@
|
|||||||
import { Adapters, API, type NoteMeta } from 'cryptgeon/shared'
|
import { Adapters, API, type NoteMeta } from 'cryptgeon/shared'
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
|
||||||
export let data: PageData
|
interface Props {
|
||||||
|
data: PageData
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: Props = $props()
|
||||||
|
|
||||||
let id = data.id
|
let id = data.id
|
||||||
let password: string | null = null
|
let password: string | null = $state<string | null>(null)
|
||||||
let note: DecryptedNote | null = null
|
let note: DecryptedNote | null = $state(null)
|
||||||
let exists = false
|
let exists = $state(false)
|
||||||
let meta: NoteMeta | null = null
|
let meta: NoteMeta | null = $state(null)
|
||||||
|
|
||||||
let loading: string | null = null
|
let loading: string | null = $state(null)
|
||||||
let error: string | null = null
|
let error: string | null = $state(null)
|
||||||
|
|
||||||
$: valid = !!password?.length
|
let valid = $derived(!!password?.length)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Check if note exists
|
// Check if note exists
|
||||||
@ -41,7 +45,8 @@
|
|||||||
/**
|
/**
|
||||||
* Get the actual contents of the note and decrypt it.
|
* Get the actual contents of the note and decrypt it.
|
||||||
*/
|
*/
|
||||||
async function show() {
|
async function show(e: SubmitEvent) {
|
||||||
|
e.preventDefault()
|
||||||
try {
|
try {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
error = $t('show.errors.no_password')
|
error = $t('show.errors.no_password')
|
||||||
@ -86,7 +91,7 @@
|
|||||||
{:else if note && !error}
|
{:else if note && !error}
|
||||||
<ShowNote {note} />
|
<ShowNote {note} />
|
||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={show}>
|
<form onsubmit={show}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<p>{$t('show.explanation')}</p>
|
<p>{$t('show.explanation')}</p>
|
||||||
{#if meta?.derivation}
|
{#if meta?.derivation}
|
||||||
|