패키지 버전 업데이트 및 SvelteKit 3, Tailwind CSS 4로 마이그레이션

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-11 20:49:24 +09:00
1 parent 35f3e92b5a
commit 67989f4741
155 files changed
+1834 -2299

No files matched your search

+1
View File
@@ -1,6 +1,7 @@
{ {
"printWidth": 100, "printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/app.css",
"overrides": [ "overrides": [
{ {
"files": "*.svelte", "files": "*.svelte",
+2 -2
View File
@@ -2,8 +2,8 @@
FROM node:22-alpine AS base FROM node:22-alpine AS base
WORKDIR /app WORKDIR /app
RUN npm install -g pnpm@10 RUN npm install -g pnpm@12
COPY pnpm-lock.yaml . COPY pnpm-lock.yaml pnpm-workspace.yaml ./
# Build Stage # Build Stage
FROM base AS build FROM base AS build
+9 -2
View File
@@ -1,5 +1,6 @@
import { includeIgnoreFile } from "@eslint/compat"; import { includeIgnoreFile } from "@eslint/compat";
import js from "@eslint/js"; import js from "@eslint/js";
import { loadConfig } from "@sveltejs/load-config";
import { defineConfig } from "eslint/config"; import { defineConfig } from "eslint/config";
import prettier from "eslint-config-prettier"; import prettier from "eslint-config-prettier";
import svelte from "eslint-plugin-svelte"; import svelte from "eslint-plugin-svelte";
@@ -7,7 +8,8 @@ import tailwind from "eslint-plugin-tailwindcss";
import globals from "globals"; import globals from "globals";
import ts from "typescript-eslint"; import ts from "typescript-eslint";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import svelteConfig from "./svelte.config.js";
const svelteConfig = (await loadConfig("./", { traverse: false }))?.config;
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url)); const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
@@ -16,7 +18,7 @@ export default defineConfig(
js.configs.recommended, js.configs.recommended,
...ts.configs.recommended, ...ts.configs.recommended,
...svelte.configs.recommended, ...svelte.configs.recommended,
...tailwind.configs["flat/recommended"], tailwind.configs.recommended,
prettier, prettier,
...svelte.configs.prettier, ...svelte.configs.prettier,
{ {
@@ -26,6 +28,11 @@ export default defineConfig(
...globals.node, ...globals.node,
}, },
}, },
settings: {
tailwindcss: {
cssConfigPath: "./src/app.css",
},
},
rules: { rules: {
"no-undef": "off", "no-undef": "off",
}, },
+43 -37
View File
@@ -8,7 +8,7 @@
"dev:remote": "__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.ts.net vite dev --host 0.0.0.0", "dev:remote": "__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=.ts.net vite dev --host 0.0.0.0",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && tsc --noEmit -p ./src/service-worker",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .", "format": "prettier --write .",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
@@ -17,59 +17,65 @@
"db:migrate": "kysely migrate" "db:migrate": "kysely migrate"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^2.1.0", "@eslint/compat": "^2.1.1",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@iconify-json/material-symbols": "^1.2.85", "@iconify-json/material-symbols": "^1.2.95",
"@noble/hashes": "^2.2.0", "@noble/hashes": "^2.4.0",
"@sveltejs/adapter-node": "^5.5.7", "@sveltejs/adapter-node": "^6.0.0",
"@sveltejs/kit": "^2.69.3", "@sveltejs/kit": "^3.0.1",
"@sveltejs/vite-plugin-svelte": "^7.2.0", "@sveltejs/load-config": "^0.2.2",
"@tanstack/svelte-virtual": "^3.13.32", "@sveltejs/vite-plugin-svelte": "^7.3.1",
"@trpc/client": "^11.18.0", "@tailwindcss/vite": "^4.3.3",
"@tanstack/svelte-virtual": "^3.13.40",
"@trpc/client": "^11.19.0",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/ms": "^0.7.34", "@types/ms": "^2.1.0",
"@types/node-schedule": "^2.1.8", "@types/node-schedule": "^2.1.8",
"@types/pg": "^8.20.0", "@types/pg": "^8.23.1",
"autoprefixer": "^10.5.3", "axios": "^1.20.0",
"axios": "^1.18.1", "dexie": "^4.4.6",
"dexie": "^4.4.4",
"es-hangul": "^2.4.0", "es-hangul": "^2.4.0",
"eslint": "^10.7.0", "eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.20.0", "eslint-plugin-svelte": "^3.23.1",
"eslint-plugin-tailwindcss": "^3.18.3", "eslint-plugin-tailwindcss": "^4.4.0",
"exifreader": "^4.41.0", "exifreader": "^4.48.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"globals": "^17.7.0", "globals": "^17.13.0",
"heic2any": "^0.0.4", "heic2any": "^0.0.4",
"kysely-ctl": "^0.21.0", "kysely-ctl": "^0.21.0",
"lru-cache": "^11.5.2", "lru-cache": "^11.5.3",
"mime": "^4.1.0", "mime": "^4.1.0",
"p-limit": "^7.3.0", "p-limit": "^7.3.3",
"prettier": "^3.9.5", "prettier": "^3.9.10",
"prettier-plugin-svelte": "^4.1.1", "prettier-plugin-svelte": "^4.1.1",
"prettier-plugin-tailwindcss": "^0.8.0", "prettier-plugin-tailwindcss": "^0.8.1",
"svelte": "^5.56.5", "svelte": "^5.57.2",
"svelte-check": "^4.7.2", "svelte-check": "^4.7.6",
"tailwindcss": "^3.4.19", "tailwindcss": "^4.3.3",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.64.0", "typescript-eslint": "^8.71.1",
"unplugin-icons": "^23.0.1", "unplugin-icons": "^24.0.0",
"vite": "^8.1.4" "vite": "^8.3.4"
}, },
"dependencies": { "dependencies": {
"@trpc/server": "^11.18.0", "@trpc/server": "^11.19.0",
"argon2": "^0.44.0", "argon2": "^0.45.1",
"kysely": "^0.29.3", "kysely": "^0.29.6",
"ms": "^2.1.3", "ms": "^2.1.3",
"node-schedule": "^2.1.1", "node-schedule": "^2.1.1",
"pg": "^8.22.0", "pg": "^8.23.1",
"superjson": "^2.2.6", "superjson": "^2.2.6",
"uuid": "^14.0.1", "uuid": "^14.0.3",
"zod": "^4.4.3" "zod": "^4.6.5"
}, },
"engines": { "engines": {
"node": "^22.0.0", "node": "^22.17.0",
"pnpm": "^10.0.0" "pnpm": "^12.0.0"
},
"imports": {
"#lib/*": "./src/lib/*",
"#trpc/*": "./src/trpc/*",
"#workers/*": "./src/workers/*"
} }
} }
+1164 -1697
View File
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -1,4 +1,4 @@
onlyBuiltDependencies: allowBuilds:
- argon2 argon2: true
- esbuild esbuild: true
- exifreader exifreader: true
-6
View File
@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
+48 -3
View File
@@ -1,3 +1,48 @@
@import "tailwindcss/base"; @import "tailwindcss";
@import "tailwindcss/components";
@import "tailwindcss/utilities"; @theme {
--color-primary-100: #ffe3e3;
--color-primary-200: #ffc9c9;
--color-primary-300: #ffa8a8;
--color-primary-400: #ff8787;
--color-primary-500: #ff6b6b;
--color-primary-600: #fa5252;
--color-primary-700: #f03e3e;
--color-primary-800: #e03131;
--color-primary-900: #c92a2a;
--color-primary-000: #fff5f5;
--color-gray-100: #f1f3f5;
--color-gray-200: #e9ecef;
--color-gray-300: #dee2e6;
--color-gray-400: #ced4da;
--color-gray-500: #adb5bd;
--color-gray-600: #868e96;
--color-gray-700: #495057;
--color-gray-800: #343a40;
--color-gray-900: #212529;
--color-gray-000: #f8f9fa;
--font-sans:
"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto,
"Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic",
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
+45
View File
@@ -0,0 +1,45 @@
import { building } from "$app/env";
import { defineEnvVars } from "@sveltejs/kit/env";
import ms, { type StringValue } from "ms";
// Empty values (e.g. `LIBRARY_PATH=` in .env) are treated as unset
const required = (value: string | undefined) => {
if (!value && !building) throw new Error("Required environment variable not set");
return value ?? "";
};
const optional = (value: string | undefined) => value || undefined;
const withDefault = (defaultValue: string) => (value: string | undefined) => value || defaultValue;
const duration = (defaultValue: StringValue) => (value: string | undefined) => {
const result = ms((value || defaultValue) as StringValue);
if (!Number.isFinite(result)) throw new Error(`Invalid duration: ${value}`);
return result;
};
export const variables = defineEnvVars({
NODE_ENV: { schema: withDefault("development") },
DATABASE_HOST: { schema: optional },
DATABASE_PORT: {
schema: (value) => {
if (!value) return undefined;
const port = Number(value);
if (!Number.isInteger(port)) throw new Error(`Invalid port: ${value}`);
return port;
},
},
DATABASE_USER: { schema: optional },
DATABASE_PASSWORD: { schema: required },
DATABASE_NAME: { schema: optional },
SESSION_SECRET: { schema: required },
SESSION_EXPIRES: { schema: duration("14d") },
USER_CLIENT_CHALLENGE_EXPIRES: { schema: duration("5m") },
SESSION_UPGRADE_CHALLENGE_EXPIRES: { schema: duration("5m") },
LIBRARY_PATH: { schema: withDefault("library") },
THUMBNAILS_PATH: { schema: withDefault("thumbnails") },
});
+9 -4
View File
@@ -1,7 +1,12 @@
import type { ClientInit } from "@sveltejs/kit"; import type { ClientInit } from "@sveltejs/kit/hooks";
import { cleanupDanglingInfos, getClientKey, getMasterKeys, getHmacSecrets } from "$lib/indexedDB"; import {
import { prepareFileCache } from "$lib/modules/file"; cleanupDanglingInfos,
import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores"; getClientKey,
getMasterKeys,
getHmacSecrets,
} from "#lib/indexedDB/index.js";
import { prepareFileCache } from "#lib/modules/file/index.js";
import { clientKeyStore, masterKeyStore, hmacSecretStore } from "#lib/stores/index.js";
const requestPersistentStorage = async () => { const requestPersistentStorage = async () => {
const isPersistent = await navigator.storage.persist(); const isPersistent = await navigator.storage.persist();
+6 -7
View File
@@ -1,14 +1,13 @@
import type { ServerInit } from "@sveltejs/kit"; import { sequence, type ServerInit } from "@sveltejs/kit/hooks";
import { sequence } from "@sveltejs/kit/hooks";
import schedule from "node-schedule"; import schedule from "node-schedule";
import { cleanupExpiredUserClientChallenges } from "$lib/server/db/client"; import { cleanupExpiredUserClientChallenges } from "#lib/server/db/client.js";
import { migrateDB } from "$lib/server/db/kysely"; import { migrateDB } from "#lib/server/db/kysely.js";
import { import {
cleanupExpiredSessions, cleanupExpiredSessions,
cleanupExpiredSessionUpgradeChallenges, cleanupExpiredSessionUpgradeChallenges,
} from "$lib/server/db/session"; } from "#lib/server/db/session.js";
import { authenticate, setAgentInfo } from "$lib/server/middlewares"; import { authenticate, setAgentInfo } from "#lib/server/middlewares/index.js";
import { cleanupExpiredUploadSessions } from "$lib/server/services/upload"; import { cleanupExpiredUploadSessions } from "#lib/server/services/upload.js";
export const init: ServerInit = async () => { export const init: ServerInit = async () => {
await migrateDB(); await migrateDB();
+3 -6
View File
@@ -2,7 +2,7 @@
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import type { ClassValue } from "svelte/elements"; import type { ClassValue } from "svelte/elements";
import { fade, fly } from "svelte/transition"; import { fade, fly } from "svelte/transition";
import { AdaptiveDiv } from "$lib/components/atoms"; import { AdaptiveDiv } from "#lib/components/atoms/index.js";
interface Props { interface Props {
children: Snippet; children: Snippet;
@@ -21,17 +21,14 @@
onclick={onclose || (() => (isOpen = false))} onclick={onclose || (() => (isOpen = false))}
class="fixed inset-0 z-10 flex items-end justify-center" class="fixed inset-0 z-10 flex items-end justify-center"
> >
<div <div class="absolute inset-0 bg-black/50" transition:fade|global={{ duration: 100 }}></div>
class="absolute inset-0 bg-black bg-opacity-50"
transition:fade|global={{ duration: 100 }}
></div>
<AdaptiveDiv class="z-10 w-full"> <AdaptiveDiv class="z-10 w-full">
<div <div
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
class="flex max-h-[70vh] min-h-[30vh] flex-col rounded-t-2xl bg-white" class="flex max-h-[70vh] min-h-[30vh] flex-col rounded-t-2xl bg-white"
transition:fly|global={{ y: 100, duration: 200 }} transition:fly|global={{ y: 100, duration: 200 }}
> >
<div class={["flex-grow overflow-y-auto", className]}> <div class={["grow overflow-y-auto", className]}>
{@render children()} {@render children()}
</div> </div>
</div> </div>
+2 -2
View File
@@ -2,7 +2,7 @@
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import type { ClassValue } from "svelte/elements"; import type { ClassValue } from "svelte/elements";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { AdaptiveDiv } from "$lib/components/atoms"; import { AdaptiveDiv } from "#lib/components/atoms/index.js";
interface Props { interface Props {
children: Snippet; children: Snippet;
@@ -19,7 +19,7 @@
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div <div
onclick={onclose || (() => (isOpen = false))} onclick={onclose || (() => (isOpen = false))}
class="fixed inset-0 z-10 bg-black bg-opacity-50" class="fixed inset-0 z-10 bg-black/50"
transition:fade|global={{ duration: 100 }} transition:fade|global={{ duration: 100 }}
> >
<AdaptiveDiv class="flex h-full items-center justify-center px-4"> <AdaptiveDiv class="flex h-full items-center justify-center px-4">
@@ -55,7 +55,7 @@
<div style:height="{$virtualizer.getTotalSize()}px"> <div style:height="{$virtualizer.getTotalSize()}px">
{#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)} {#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)}
<div <div
class="absolute left-0 top-0 w-full" class="absolute top-0 left-0 w-full"
style:transform="translateY({virtualItem.start - scrollMargin}px)" style:transform="translateY({virtualItem.start - scrollMargin}px)"
data-index={virtualItem.index} data-index={virtualItem.index}
use:measureItem use:measureItem
@@ -29,7 +29,7 @@
class={["rounded-xl", className]} class={["rounded-xl", className]}
> >
<div id="children" class="flex h-full items-center gap-x-4 p-2 transition"> <div id="children" class="flex h-full items-center gap-x-4 p-2 transition">
<div class="flex-grow overflow-x-hidden"> <div class="grow overflow-x-hidden">
{@render children()} {@render children()}
</div> </div>
{#if ActionButtonIcon} {#if ActionButtonIcon}
@@ -41,7 +41,7 @@
setTimeout(onActionButtonClick, 100); setTimeout(onActionButtonClick, 100);
} }
}} }}
class={["flex-shrink-0 rounded-full p-1 text-lg active:bg-gray-100", actionButtonClassName]} class={["shrink-0 rounded-full p-1 text-lg active:bg-gray-100", actionButtonClassName]}
> >
<ActionButtonIcon /> <ActionButtonIcon />
</button> </button>
@@ -50,6 +50,8 @@
</div> </div>
<style lang="postcss"> <style lang="postcss">
@reference "../../../../app.css";
#container:active:not(:has(#action-button:active)) { #container:active:not(:has(#action-button:active)) {
@apply bg-gray-100; @apply bg-gray-100;
} }
@@ -18,9 +18,9 @@
class={["rounded-xl active:bg-gray-100", className]} class={["rounded-xl active:bg-gray-100", className]}
> >
<div class="flex h-full items-center gap-x-4 p-2 transition active:scale-95"> <div class="flex h-full items-center gap-x-4 p-2 transition active:scale-95">
<div class="flex-grow"> <div class="grow">
{@render children()} {@render children()}
</div> </div>
<IconChevronRight class="flex-shrink-0 text-xl text-gray-800" /> <IconChevronRight class="shrink-0 text-xl text-gray-800" />
</div> </div>
</button> </button>
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { getFileThumbnail } from "$lib/modules/file"; import { getFileThumbnail } from "#lib/modules/file/index.js";
import type { SummarizedFileInfo } from "$lib/modules/filesystem"; import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import IconFavorite from "~icons/material-symbols/favorite"; import IconFavorite from "~icons/material-symbols/favorite";
@@ -16,15 +16,15 @@
<button <button
onclick={onclick && (() => setTimeout(() => onclick(info), 100))} onclick={onclick && (() => setTimeout(() => onclick(info), 100))}
class="relative aspect-square overflow-hidden rounded transition active:scale-95 active:brightness-90" class="relative aspect-square overflow-hidden rounded-sm transition active:scale-95 active:brightness-90"
> >
{#if $thumbnail} {#if $thumbnail}
<img src={$thumbnail} alt={info.name} class="h-full w-full object-cover" /> <img src={$thumbnail} alt={info.name} class="size-full object-cover" />
{:else} {:else}
<div class="h-full w-full bg-gray-100"></div> <div class="size-full bg-gray-100"></div>
{/if} {/if}
{#if info.isFavorite} {#if info.isFavorite}
<div class={["absolute bottom-0.5 right-0.5"]}> <div class={["absolute right-0.5 bottom-0.5"]}>
<IconFavorite <IconFavorite
class="text-sm text-red-500" class="text-sm text-red-500"
style="filter: drop-shadow(0 0 1px white) drop-shadow(0 0 1px white)" style="filter: drop-shadow(0 0 1px white) drop-shadow(0 0 1px white)"
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Component } from "svelte"; import type { Component } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements"; import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { AdaptiveDiv } from "$lib/components/atoms"; import { AdaptiveDiv } from "#lib/components/atoms/index.js";
interface Props { interface Props {
class: ClassValue; class: ClassValue;
@@ -17,7 +17,7 @@
<button <button
onclick={onclick && (() => setTimeout(onclick, 100))} onclick={onclick && (() => setTimeout(onclick, 100))}
class={[ class={[
"pointer-events-auto absolute flex h-14 w-14 items-center justify-center rounded-full bg-gray-300 text-xl shadow-lg transition active:scale-95 active:bg-gray-400", "pointer-events-auto absolute flex size-14 items-center justify-center rounded-full bg-gray-300 text-xl shadow-lg transition active:scale-95 active:bg-gray-400",
className, className,
]} ]}
> >
@@ -10,6 +10,6 @@
let { children, class: className }: Props = $props(); let { children, class: className }: Props = $props();
</script> </script>
<div class={["mx-auto max-w-screen-md", className]}> <div class={["mx-auto max-w-(--breakpoint-md)", className]}>
{@render children()} {@render children()}
</div> </div>
@@ -10,6 +10,6 @@
let { children, class: className }: Props = $props(); let { children, class: className }: Props = $props();
</script> </script>
<div class={["flex flex-grow flex-col justify-between px-4", className]}> <div class={["flex grow flex-col justify-between px-4", className]}>
{@render children()} {@render children()}
</div> </div>
@@ -17,11 +17,11 @@
bind:value bind:value
{type} {type}
placeholder="" placeholder=""
class="w-full border-b-2 border-gray-300 py-1 text-xl outline-none transition duration-300 ease-in-out" class="w-full border-b-2 border-gray-300 py-1 text-xl outline-hidden transition duration-300 ease-in-out"
/> />
<!-- svelte-ignore a11y_label_has_associated_control --> <!-- svelte-ignore a11y_label_has_associated_control -->
<label <label
class="pointer-events-none absolute left-0 top-1/2 -translate-y-1/2 transform text-xl text-gray-400 transition-all duration-300 ease-in-out" class="pointer-events-none absolute top-1/2 left-0 -translate-y-1/2 transform text-xl text-gray-400 transition-all duration-300 ease-in-out"
> >
{placeholder} {placeholder}
</label> </label>
@@ -29,6 +29,8 @@
</div> </div>
<style lang="postcss"> <style lang="postcss">
@reference "../../../../app.css";
input:focus, input:focus,
input:not(:placeholder-shown) { input:not(:placeholder-shown) {
@apply border-primary-300; @apply border-primary-300;
@@ -4,7 +4,7 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import { Button, Modal } from "$lib/components/atoms"; import { Button, Modal } from "#lib/components/atoms/index.js";
interface Props { interface Props {
cancelText?: string; cancelText?: string;
@@ -1,5 +1,5 @@
<script module lang="ts"> <script module lang="ts">
import type { DataKey } from "$lib/modules/filesystem"; import type { DataKey } from "#lib/modules/filesystem/index.js";
export interface SelectedCategory { export interface SelectedCategory {
id: number; id: number;
@@ -11,10 +11,10 @@
<script lang="ts"> <script lang="ts">
import type { Component } from "svelte"; import type { Component } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements"; import type { SvelteHTMLElements } from "svelte/elements";
import { ActionEntryButton } from "$lib/components/atoms"; import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { CategoryLabel } from "$lib/components/molecules"; import { CategoryLabel } from "#lib/components/molecules/index.js";
import type { SubCategoryInfo } from "$lib/modules/filesystem"; import type { SubCategoryInfo } from "#lib/modules/filesystem/index.js";
import { sortEntries } from "$lib/utils"; import { sortEntries } from "#lib/utils/index.js";
interface Props { interface Props {
categories: SubCategoryInfo[]; categories: SubCategoryInfo[];
@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import type { Component, Snippet } from "svelte"; import type { Component, Snippet } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements"; import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { EntryButton } from "$lib/components/atoms"; import { EntryButton } from "#lib/components/atoms/index.js";
import { IconLabel } from "$lib/components/molecules"; import { IconLabel } from "#lib/components/molecules/index.js";
interface Props { interface Props {
children: Snippet; children: Snippet;
@@ -1,8 +1,12 @@
<script lang="ts"> <script lang="ts">
import type { Component } from "svelte"; import type { Component } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements"; import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { Categories, IconEntryButton, type SelectedCategory } from "$lib/components/molecules"; import {
import type { CategoryInfo } from "$lib/modules/filesystem"; Categories,
IconEntryButton,
type SelectedCategory,
} from "#lib/components/molecules/index.js";
import type { CategoryInfo } from "#lib/modules/filesystem/index.js";
import IconAddCircle from "~icons/material-symbols/add-circle"; import IconAddCircle from "~icons/material-symbols/add-circle";
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Component, Snippet } from "svelte"; import type { Component, Snippet } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements"; import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { TitleLabel } from "$lib/components/molecules"; import { TitleLabel } from "#lib/components/molecules/index.js";
interface Props { interface Props {
children?: Snippet; children?: Snippet;
+4 -4
View File
@@ -21,20 +21,20 @@
className, className,
]} ]}
> >
<div class="w-[2.3rem] flex-shrink-0"> <div class="w-[2.3rem] shrink-0">
{#if showBackButton} {#if showBackButton}
<button <button
onclick={onBackClick ?? (() => history.back())} onclick={onBackClick ?? (() => history.back())}
class="w-full rounded-full p-1 text-2xl active:bg-black active:bg-opacity-[0.04]" class="w-full rounded-full p-1 text-2xl active:bg-black/4"
> >
<IconArrowBack /> <IconArrowBack />
</button> </button>
{/if} {/if}
</div> </div>
{#if title} {#if title}
<p class="flex-grow truncate text-center text-lg font-semibold">{title}</p> <p class="grow truncate text-center text-lg font-semibold">{title}</p>
{/if} {/if}
<div class="w-[2.3rem] flex-shrink-0"> <div class="w-[2.3rem] shrink-0">
{#if children} {#if children}
{@render children()} {@render children()}
{/if} {/if}
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ClassValue } from "svelte/elements"; import type { ClassValue } from "svelte/elements";
import { IconLabel } from "$lib/components/molecules"; import { IconLabel } from "#lib/components/molecules/index.js";
import IconCategory from "~icons/material-symbols/category"; import IconCategory from "~icons/material-symbols/category";
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ClassValue } from "svelte/elements"; import type { ClassValue } from "svelte/elements";
import { IconLabel } from "$lib/components/molecules"; import { IconLabel } from "#lib/components/molecules/index.js";
import IconFolder from "~icons/material-symbols/folder"; import IconFolder from "~icons/material-symbols/folder";
import IconDriveFolderUpload from "~icons/material-symbols/drive-folder-upload"; import IconDriveFolderUpload from "~icons/material-symbols/drive-folder-upload";
@@ -29,9 +29,14 @@
</script> </script>
{#snippet iconSnippet()} {#snippet iconSnippet()}
<div class="relative flex h-10 w-10 items-center justify-center text-xl"> <div class="relative flex size-10 items-center justify-center text-xl">
{#if thumbnail} {#if thumbnail}
<img src={thumbnail} alt={name} loading="lazy" class="aspect-square rounded object-cover" /> <img
src={thumbnail}
alt={name}
loading="lazy"
class="aspect-square rounded-sm object-cover"
/>
{:else if type === "directory"} {:else if type === "directory"}
<IconFolder /> <IconFolder />
{:else if type === "parent-directory"} {:else if type === "parent-directory"}
@@ -40,7 +45,7 @@
<IconDraft class="text-blue-400" /> <IconDraft class="text-blue-400" />
{/if} {/if}
{#if isFavorite} {#if isFavorite}
<div class={["absolute bottom-0 right-0", !thumbnail && "rounded-full bg-white p-0.5"]}> <div class={["absolute right-0 bottom-0", !thumbnail && "rounded-full bg-white p-0.5"]}>
<IconFavorite <IconFavorite
class="text-xs text-red-500" class="text-xs text-red-500"
style="filter: drop-shadow(0 0 1px white) drop-shadow(0 0 1px white)" style="filter: drop-shadow(0 0 1px white) drop-shadow(0 0 1px white)"
@@ -25,15 +25,15 @@
<div class={["flex items-center gap-x-4", className]}> <div class={["flex items-center gap-x-4", className]}>
{#if iconSnippet} {#if iconSnippet}
<div class={["flex-shrink-0", iconClassName]}> <div class={["shrink-0", iconClassName]}>
{@render iconSnippet()} {@render iconSnippet()}
</div> </div>
{:else if Icon} {:else if Icon}
<div class={["flex-shrink-0 text-lg", iconClassName]}> <div class={["shrink-0 text-lg", iconClassName]}>
<Icon /> <Icon />
</div> </div>
{/if} {/if}
<div class="flex flex-grow flex-col overflow-x-hidden text-left"> <div class="flex grow flex-col overflow-x-hidden text-left">
<p class={["truncate font-medium", textClassName]}> <p class={["truncate font-medium", textClassName]}>
{@render children()} {@render children()}
</p> </p>
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { TextInputModal } from "$lib/components/organisms"; import { TextInputModal } from "#lib/components/organisms/index.js";
interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { ActionModal } from "$lib/components/molecules"; import { ActionModal } from "#lib/components/molecules/index.js";
interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { TextInputModal } from "$lib/components/organisms"; import { TextInputModal } from "#lib/components/organisms/index.js";
interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { TextInput } from "$lib/components/atoms"; import { TextInput } from "#lib/components/atoms/index.js";
import { ActionModal, type ConfirmHandler } from "$lib/components/molecules"; import { ActionModal, type ConfirmHandler } from "#lib/components/molecules/index.js";
interface Props { interface Props {
defaultValue?: string; defaultValue?: string;
+1 -1
View File
@@ -1,4 +1,4 @@
import { AES_GCM_IV_SIZE } from "$lib/constants"; import { AES_GCM_IV_SIZE } from "#lib/constants/index.js";
import { import {
encodeString, encodeString,
decodeString, decodeString,
+2 -2
View File
@@ -1,5 +1,5 @@
import HmacWorker from "$workers/hmac?worker"; import HmacWorker from "#workers/hmac.js?worker";
import type { ComputeMessage, ResultMessage } from "$workers/hmac"; import type { ComputeMessage, ResultMessage } from "#workers/hmac.js";
export const digestMessage = async (message: BufferSource) => { export const digestMessage = async (message: BufferSource) => {
return await crypto.subtle.digest("SHA-256", message); return await crypto.subtle.digest("SHA-256", message);
+2 -2
View File
@@ -3,8 +3,8 @@ import {
storeFileCacheIndex, storeFileCacheIndex,
deleteFileCacheIndex, deleteFileCacheIndex,
type FileCacheIndex, type FileCacheIndex,
} from "$lib/indexedDB"; } from "#lib/indexedDB/index.js";
import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; import { readFile, writeFile, deleteFile } from "#lib/modules/opfs.js";
const fileCacheIndex = new Map<number, FileCacheIndex>(); const fileCacheIndex = new Map<number, FileCacheIndex>();
+2 -2
View File
@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import { limitFunction } from "p-limit"; import { limitFunction } from "p-limit";
import { ENCRYPTED_CHUNK_SIZE } from "$lib/constants"; import { ENCRYPTED_CHUNK_SIZE } from "#lib/constants/index.js";
import { decryptChunk, concatenateBuffers } from "$lib/modules/crypto"; import { decryptChunk, concatenateBuffers } from "#lib/modules/crypto/index.js";
export interface FileDownloadState { export interface FileDownloadState {
id: number; id: number;
+4 -4
View File
@@ -1,9 +1,9 @@
import { LRUCache } from "lru-cache"; import { LRUCache } from "lru-cache";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import { decryptChunk } from "$lib/modules/crypto"; import { decryptChunk } from "#lib/modules/crypto/index.js";
import type { SummarizedFileInfo } from "$lib/modules/filesystem"; import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; import { readFile, writeFile, deleteFile, deleteDirectory } from "#lib/modules/opfs.js";
import { getThumbnailUrl } from "$lib/modules/thumbnail"; import { getThumbnailUrl } from "#lib/modules/thumbnail.js";
const loadedThumbnails = new LRUCache<number, Writable<string>>({ max: 100 }); const loadedThumbnails = new LRUCache<number, Writable<string>>({ max: 100 });
const loadingThumbnails = new Map<number, Writable<string | undefined>>(); const loadingThumbnails = new Map<number, Writable<string | undefined>>();
+13 -8
View File
@@ -1,13 +1,18 @@
import ExifReader from "exifreader"; import ExifReader from "exifreader";
import { limitFunction } from "p-limit"; import { limitFunction } from "p-limit";
import { CHUNK_SIZE } from "$lib/constants"; import { CHUNK_SIZE } from "#lib/constants/index.js";
import { encodeToBase64, generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto"; import {
import { signMessageHmac } from "$lib/modules/crypto"; encodeToBase64,
import { generateThumbnail } from "$lib/modules/thumbnail"; generateDataKey,
import { uploadBlob } from "$lib/modules/upload"; wrapDataKey,
import type { MasterKey, HmacSecret } from "$lib/stores"; encryptString,
import { Scheduler } from "$lib/utils"; } from "#lib/modules/crypto/index.js";
import { trpc } from "$trpc/client"; import { signMessageHmac } from "#lib/modules/crypto/index.js";
import { generateThumbnail } from "#lib/modules/thumbnail.js";
import { uploadBlob } from "#lib/modules/upload.js";
import type { MasterKey, HmacSecret } from "#lib/stores/index.js";
import { Scheduler } from "#lib/utils/index.js";
import { trpc } from "#trpc/client.js";
export interface FileUploadState { export interface FileUploadState {
id: string; id: string;
+2 -2
View File
@@ -1,5 +1,5 @@
import * as IndexedDB from "$lib/indexedDB"; import * as IndexedDB from "#lib/indexedDB/index.js";
import { trpc, isTRPCClientError } from "$trpc/client"; import { trpc, isTRPCClientError } from "#trpc/client.js";
import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte"; import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte";
import type { CategoryInfo, MaybeCategoryInfo } from "./types"; import type { CategoryInfo, MaybeCategoryInfo } from "./types";
+2 -2
View File
@@ -1,5 +1,5 @@
import * as IndexedDB from "$lib/indexedDB"; import * as IndexedDB from "#lib/indexedDB/index.js";
import { trpc, isTRPCClientError } from "$trpc/client"; import { trpc, isTRPCClientError } from "#trpc/client.js";
import { FilesystemCache, decryptDirectoryMetadata, decryptFileMetadata } from "./internal.svelte"; import { FilesystemCache, decryptDirectoryMetadata, decryptFileMetadata } from "./internal.svelte";
import type { DirectoryInfo, MaybeDirectoryInfo } from "./types"; import type { DirectoryInfo, MaybeDirectoryInfo } from "./types";
+2 -2
View File
@@ -1,5 +1,5 @@
import * as IndexedDB from "$lib/indexedDB"; import * as IndexedDB from "#lib/indexedDB/index.js";
import { trpc, isTRPCClientError } from "$trpc/client"; import { trpc, isTRPCClientError } from "#trpc/client.js";
import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte"; import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte";
import type { FileInfo, MaybeFileInfo } from "./types"; import type { FileInfo, MaybeFileInfo } from "./types";
@@ -1,5 +1,5 @@
import { untrack } from "svelte"; import { untrack } from "svelte";
import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; import { unwrapDataKey, decryptString } from "#lib/modules/crypto/index.js";
interface FilesystemCacheOptions<K, V> { interface FilesystemCacheOptions<K, V> {
fetchFromIndexedDB: (key: K) => Promise<V | undefined>; fetchFromIndexedDB: (key: K) => Promise<V | undefined>;
+2 -2
View File
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { storeClientKey } from "$lib/indexedDB"; import { storeClientKey } from "#lib/indexedDB/index.js";
import type { ClientKeys } from "$lib/stores"; import type { ClientKeys } from "#lib/stores/index.js";
const SerializedClientKeysSchema = z.intersection( const SerializedClientKeysSchema = z.intersection(
z.object({ z.object({
+1 -1
View File
@@ -1,4 +1,4 @@
import { encodeToBase64 } from "$lib/modules/crypto"; import { encodeToBase64 } from "#lib/modules/crypto/index.js";
const scaleSize = (width: number, height: number, targetSize: number) => { const scaleSize = (width: number, height: number, targetSize: number) => {
if (width <= targetSize || height <= targetSize) { if (width <= targetSize || height <= targetSize) {
+3 -3
View File
@@ -1,9 +1,9 @@
import { sha256 } from "@noble/hashes/sha2.js"; import { sha256 } from "@noble/hashes/sha2.js";
import axios from "axios"; import axios from "axios";
import pLimit from "p-limit"; import pLimit from "p-limit";
import { ENCRYPTION_OVERHEAD, CHUNK_SIZE } from "$lib/constants"; import { ENCRYPTION_OVERHEAD, CHUNK_SIZE } from "#lib/constants/index.js";
import { encodeToBase64, encryptChunk } from "$lib/modules/crypto"; import { encodeToBase64, encryptChunk } from "#lib/modules/crypto/index.js";
import { BoundedQueue } from "$lib/utils"; import { BoundedQueue } from "#lib/utils/index.js";
interface UploadStats { interface UploadStats {
progress: number; progress: number;
+1 -1
View File
@@ -1,7 +1,7 @@
import { Kysely, PostgresDialect } from "kysely"; import { Kysely, PostgresDialect } from "kysely";
import { Migrator } from "kysely/migration"; import { Migrator } from "kysely/migration";
import pg from "pg"; import pg from "pg";
import env from "$lib/server/loadenv"; import env from "#lib/server/loadenv.js";
import migrations from "./migrations"; import migrations from "./migrations";
import type { Database } from "./schema"; import type { Database } from "./schema";
+1 -1
View File
@@ -1,5 +1,5 @@
import pg from "pg"; import pg from "pg";
import env from "$lib/server/loadenv"; import env from "#lib/server/loadenv.js";
import { IntegrityError } from "./error"; import { IntegrityError } from "./error";
import db from "./kysely"; import db from "./kysely";
+26 -20
View File
@@ -1,29 +1,35 @@
import ms from "ms"; import {
import { building } from "$app/environment"; NODE_ENV,
import { env } from "$env/dynamic/private"; DATABASE_HOST,
DATABASE_PORT,
if (!building) { DATABASE_USER,
if (!env.DATABASE_PASSWORD) throw new Error("DATABASE_PASSWORD not set"); DATABASE_PASSWORD,
if (!env.SESSION_SECRET) throw new Error("SESSION_SECRET not set"); DATABASE_NAME,
} SESSION_SECRET,
SESSION_EXPIRES,
USER_CLIENT_CHALLENGE_EXPIRES,
SESSION_UPGRADE_CHALLENGE_EXPIRES,
LIBRARY_PATH,
THUMBNAILS_PATH,
} from "$app/env/private";
export default { export default {
nodeEnv: env.NODE_ENV || "development", nodeEnv: NODE_ENV,
database: { database: {
host: env.DATABASE_HOST, host: DATABASE_HOST,
port: env.DATABASE_PORT ? parseInt(env.DATABASE_PORT, 10) : undefined, port: DATABASE_PORT,
user: env.DATABASE_USER, user: DATABASE_USER,
password: env.DATABASE_PASSWORD!, password: DATABASE_PASSWORD,
name: env.DATABASE_NAME, name: DATABASE_NAME,
}, },
session: { session: {
secret: env.SESSION_SECRET!, secret: SESSION_SECRET,
exp: ms(env.SESSION_EXPIRES || "14d"), exp: SESSION_EXPIRES,
}, },
challenge: { challenge: {
userClientExp: ms(env.USER_CLIENT_CHALLENGE_EXPIRES || "5m"), userClientExp: USER_CLIENT_CHALLENGE_EXPIRES,
sessionUpgradeExp: ms(env.SESSION_UPGRADE_CHALLENGE_EXPIRES || "5m"), sessionUpgradeExp: SESSION_UPGRADE_CHALLENGE_EXPIRES,
}, },
libraryPath: env.LIBRARY_PATH || "library", libraryPath: LIBRARY_PATH,
thumbnailsPath: env.THUMBNAILS_PATH || "thumbnails", thumbnailsPath: THUMBNAILS_PATH,
}; };
+3 -2
View File
@@ -1,5 +1,6 @@
import { error, redirect, type Handle } from "@sveltejs/kit"; import { error, redirect } from "@sveltejs/kit";
import { cookieOptions, authenticate, AuthenticationError } from "$lib/server/modules/auth"; import type { Handle } from "@sveltejs/kit/hooks";
import { cookieOptions, authenticate, AuthenticationError } from "#lib/server/modules/auth.js";
export const authenticateMiddleware: Handle = async ({ event, resolve }) => { export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
try { try {
+2 -1
View File
@@ -1,4 +1,5 @@
import { error, type Handle } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { Handle } from "@sveltejs/kit/hooks";
export const setAgentInfoMiddleware: Handle = async ({ event, resolve }) => { export const setAgentInfoMiddleware: Handle = async ({ event, resolve }) => {
const ip = event.getClientAddress(); const ip = event.getClientAddress();
+3 -3
View File
@@ -1,7 +1,7 @@
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { ClientRepo, SessionRepo, IntegrityError } from "$lib/server/db"; import { ClientRepo, SessionRepo, IntegrityError } from "#lib/server/db/index.js";
import env from "$lib/server/loadenv"; import env from "#lib/server/loadenv.js";
import { verifySessionId } from "$lib/server/modules/crypto"; import { verifySessionId } from "#lib/server/modules/crypto.js";
export interface Session { export interface Session {
sessionId: string; sessionId: string;
+1 -1
View File
@@ -2,7 +2,7 @@ import { error } from "@sveltejs/kit";
import { createReadStream } from "fs"; import { createReadStream } from "fs";
import { stat } from "fs/promises"; import { stat } from "fs/promises";
import { Readable } from "stream"; import { Readable } from "stream";
import { FileRepo, MediaRepo } from "$lib/server/db"; import { FileRepo, MediaRepo } from "#lib/server/db/index.js";
const createEncContentStream = async ( const createEncContentStream = async (
path: string, path: string,
+3 -3
View File
@@ -1,9 +1,9 @@
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { open } from "fs/promises"; import { open } from "fs/promises";
import { Readable } from "stream"; import { Readable } from "stream";
import { ENCRYPTION_OVERHEAD, ENCRYPTED_CHUNK_SIZE } from "$lib/constants"; import { ENCRYPTION_OVERHEAD, ENCRYPTED_CHUNK_SIZE } from "#lib/constants/index.js";
import { UploadRepo } from "$lib/server/db"; import { UploadRepo } from "#lib/server/db/index.js";
import { safeUnlink } from "$lib/server/modules/filesystem"; import { safeUnlink } from "#lib/server/modules/filesystem.js";
const chunkLocks = new Set<string>(); const chunkLocks = new Set<string>();
+1 -1
View File
@@ -1,4 +1,4 @@
import { DECRYPTED_FILE_URL_PREFIX } from "$lib/constants"; import { DECRYPTED_FILE_URL_PREFIX } from "#lib/constants/index.js";
import type { FileMetadata, ServiceWorkerMessage, ServiceWorkerResponse } from "./types"; import type { FileMetadata, ServiceWorkerMessage, ServiceWorkerResponse } from "./types";
const PREPARE_TIMEOUT_MS = 5000; const PREPARE_TIMEOUT_MS = 5000;
+2 -2
View File
@@ -1,5 +1,5 @@
import { encodeToBase64, decryptChallenge, signMessageRSA } from "$lib/modules/crypto"; import { encodeToBase64, decryptChallenge, signMessageRSA } from "#lib/modules/crypto/index.js";
import { trpc, isTRPCClientError } from "$trpc/client"; import { trpc, isTRPCClientError } from "#trpc/client.js";
export const requestSessionUpgrade = async ( export const requestSessionUpgrade = async (
encryptKeyBase64: string, encryptKeyBase64: string,
+3 -3
View File
@@ -1,6 +1,6 @@
import { generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto"; import { generateDataKey, wrapDataKey, encryptString } from "#lib/modules/crypto/index.js";
import type { MasterKey } from "$lib/stores"; import type { MasterKey } from "#lib/stores/index.js";
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
export const requestCategoryCreation = async ( export const requestCategoryCreation = async (
name: string, name: string,
+4 -4
View File
@@ -1,13 +1,13 @@
import { getAllFileInfos } from "$lib/indexedDB/filesystem"; import { getAllFileInfos } from "#lib/indexedDB/filesystem.js";
import { import {
getFileCache, getFileCache,
storeFileCache, storeFileCache,
deleteFileCache, deleteFileCache,
downloadFile, downloadFile,
deleteFileThumbnailCache, deleteFileThumbnailCache,
} from "$lib/modules/file"; } from "#lib/modules/file/index.js";
import { uploadBlob } from "$lib/modules/upload"; import { uploadBlob } from "#lib/modules/upload.js";
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
export const requestFileDownload = async ( export const requestFileDownload = async (
fileId: number, fileId: number,
+5 -5
View File
@@ -1,4 +1,4 @@
import { storeMasterKeys } from "$lib/indexedDB"; import { storeMasterKeys } from "#lib/indexedDB/index.js";
import { import {
encodeToBase64, encodeToBase64,
exportRSAKeyToBase64, exportRSAKeyToBase64,
@@ -7,10 +7,10 @@ import {
unwrapMasterKey, unwrapMasterKey,
signMasterKeyWrapped, signMasterKeyWrapped,
verifyMasterKeyWrapped, verifyMasterKeyWrapped,
} from "$lib/modules/crypto"; } from "#lib/modules/crypto/index.js";
import { requestSessionUpgrade } from "$lib/services/auth"; import { requestSessionUpgrade } from "#lib/services/auth.js";
import { masterKeyStore, type ClientKeys } from "$lib/stores"; import { masterKeyStore, type ClientKeys } from "#lib/stores/index.js";
import { trpc, isTRPCClientError } from "$trpc/client"; import { trpc, isTRPCClientError } from "#trpc/client.js";
export const requestClientRegistration = async ( export const requestClientRegistration = async (
encryptKeyBase64: string, encryptKeyBase64: string,
+9
View File
@@ -0,0 +1,9 @@
import { defineParams } from "@sveltejs/kit/params";
const matchThumbnail = (param: string) => {
return param === "thumbnail";
};
export const params = defineParams({
thumbnail: (param) => (matchThumbnail(param) ? param : undefined),
});
-5
View File
@@ -1,5 +0,0 @@
import type { ParamMatcher } from "@sveltejs/kit";
export const match: ParamMatcher = (param) => {
return param === "thumbnail";
};
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { AdaptiveDiv } from "$lib/components/atoms"; import { AdaptiveDiv } from "#lib/components/atoms/index.js";
let { children } = $props(); let { children } = $props();
</script> </script>
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextInput } from "$lib/components/atoms"; import { BottomDiv, Button, FullscreenDiv, TextInput } from "#lib/components/atoms/index.js";
import { TitledDiv, TopBar } from "$lib/components/molecules"; import { TitledDiv, TopBar } from "#lib/components/molecules/index.js";
import { requestPasswordChange } from "./service"; import { requestPasswordChange } from "./service";
let oldPassword = $state(""); let oldPassword = $state("");
@@ -24,9 +24,9 @@
<title>비밀번호 바꾸기</title> <title>비밀번호 바꾸기</title>
</svelte:head> </svelte:head>
<TopBar class="flex-shrink-0" /> <TopBar class="shrink-0" />
<FullscreenDiv> <FullscreenDiv>
<TitledDiv class="!pt-0" titleClass="!text-2xl" childrenClass="flex flex-col gap-y-2"> <TitledDiv class="pt-0!" titleClass="text-2xl!" childrenClass="flex flex-col gap-y-2">
{#snippet title()} {#snippet title()}
기존 비밀번호와 새 비밀번호를 입력해 주세요. 기존 비밀번호와 새 비밀번호를 입력해 주세요.
{/snippet} {/snippet}
@@ -1,4 +1,4 @@
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
export const requestPasswordChange = async (oldPassword: string, newPassword: string) => { export const requestPasswordChange = async (oldPassword: string, newPassword: string) => {
try { try {
@@ -1,9 +1,15 @@
<script lang="ts"> <script lang="ts">
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms"; import {
import { TitledDiv } from "$lib/components/molecules"; BottomDiv,
import { ForceLoginModal } from "$lib/components/organisms"; Button,
import { clientKeyStore, masterKeyStore } from "$lib/stores"; FullscreenDiv,
TextButton,
TextInput,
} from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { ForceLoginModal } from "#lib/components/organisms/index.js";
import { clientKeyStore, masterKeyStore } from "#lib/stores/index.js";
import { import {
requestLogin, requestLogin,
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
@@ -1,11 +1,11 @@
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
export { requestLogout } from "$lib/services/auth"; export { requestLogout } from "#lib/services/auth.js";
export { requestDeletedFilesCleanup } from "$lib/services/file"; export { requestDeletedFilesCleanup } from "#lib/services/file.js";
export { export {
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
requestMasterKeyDownload, requestMasterKeyDownload,
} from "$lib/services/key"; } from "#lib/services/key.js";
export const requestLogin = async (email: string, password: string) => { export const requestLogin = async (email: string, password: string) => {
try { try {
@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { FullscreenDiv } from "$lib/components/atoms"; import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TitledDiv } from "$lib/components/molecules"; import { TitledDiv } from "#lib/components/molecules/index.js";
import { clientKeyStore, masterKeyStore } from "$lib/stores"; import { clientKeyStore, masterKeyStore } from "#lib/stores/index.js";
import { generatePublicKeyFingerprint, requestMasterKeyDownload } from "./service"; import { generatePublicKeyFingerprint, requestMasterKeyDownload } from "./service";
import IconFingerprint from "~icons/material-symbols/fingerprint"; import IconFingerprint from "~icons/material-symbols/fingerprint";
@@ -22,7 +22,7 @@
($clientKeyStore && ($clientKeyStore &&
(await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey))) (await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)))
) { ) {
await goto(data.redirectPath, { replaceState: true }); await goto(data.redirectPath, { replace: true });
} }
}); });
</script> </script>
@@ -1,6 +1,6 @@
import { concatenateBuffers, exportRSAKey, digestMessage } from "$lib/modules/crypto"; import { concatenateBuffers, exportRSAKey, digestMessage } from "#lib/modules/crypto/index.js";
export { requestMasterKeyDownload } from "$lib/services/key"; export { requestMasterKeyDownload } from "#lib/services/key.js";
export const generatePublicKeyFingerprint = async (encryptKey: CryptoKey, verifyKey: CryptoKey) => { export const generatePublicKeyFingerprint = async (encryptKey: CryptoKey, verifyKey: CryptoKey) => {
const { key: encryptKeyBuffer } = await exportRSAKey(encryptKey); const { key: encryptKeyBuffer } = await exportRSAKey(encryptKey);
@@ -3,13 +3,13 @@
import { untrack } from "svelte"; import { untrack } from "svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { page } from "$app/state"; import { page } from "$app/state";
import { FullscreenDiv } from "$lib/components/atoms"; import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules"; import { Categories, IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
import { getFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem"; import { getFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { captureVideoThumbnail } from "$lib/modules/thumbnail"; import { captureVideoThumbnail } from "#lib/modules/thumbnail.js";
import { getFileDownloadState } from "$lib/modules/file"; import { getFileDownloadState } from "#lib/modules/file/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "$lib/utils"; import { HybridPromise } from "#lib/utils/index.js";
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte"; import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
import DownloadStatus from "./DownloadStatus.svelte"; import DownloadStatus from "./DownloadStatus.svelte";
import { import {
@@ -150,7 +150,7 @@
<div onclick={(e) => e.stopPropagation()}> <div onclick={(e) => e.stopPropagation()}>
<button <button
onclick={() => (isMenuOpen = !isMenuOpen)} onclick={() => (isMenuOpen = !isMenuOpen)}
class="w-full rounded-full p-1 text-2xl active:bg-black active:bg-opacity-[0.04]" class="w-full rounded-full p-1 text-2xl active:bg-black/4"
> >
<IconMoreVert /> <IconMoreVert />
</button> </button>
@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "$lib/components/atoms"; import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
import { SubCategories } from "$lib/components/molecules"; import { SubCategories } from "#lib/components/molecules/index.js";
import { CategoryCreateModal } from "$lib/components/organisms"; import { CategoryCreateModal } from "#lib/components/organisms/index.js";
import { getCategoryInfo, type MaybeCategoryInfo } from "$lib/modules/filesystem"; import { getCategoryInfo, type MaybeCategoryInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "$lib/utils"; import { HybridPromise } from "#lib/utils/index.js";
import { requestCategoryCreation } from "./service"; import { requestCategoryCreation } from "./service";
interface Props { interface Props {
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { isFileDownloading, type FileDownloadState } from "$lib/modules/file"; import { isFileDownloading, type FileDownloadState } from "#lib/modules/file/index.js";
import { formatNetworkSpeed } from "$lib/utils"; import { formatNetworkSpeed } from "#lib/utils/index.js";
interface Props { interface Props {
state: FileDownloadState; state: FileDownloadState;
@@ -46,7 +46,7 @@
{#if isOpen && (directoryId || downloadUrl || fileBlob)} {#if isOpen && (directoryId || downloadUrl || fileBlob)}
<div <div
class="absolute right-2 top-full z-20 min-w-44 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl" class="absolute top-full right-2 z-20 min-w-44 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl"
transition:fly={{ y: -8, duration: 200 }} transition:fly={{ y: -8, duration: 200 }}
> >
<p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p> <p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p>
+6 -6
View File
@@ -1,10 +1,10 @@
import { storeFileThumbnailCache } from "$lib/modules/file"; import { storeFileThumbnailCache } from "#lib/modules/file/index.js";
import { prepareFileDecryption, getDecryptedFileUrl } from "$lib/serviceWorker"; import { prepareFileDecryption, getDecryptedFileUrl } from "#lib/serviceWorker/index.js";
import { requestFileThumbnailUpload } from "$lib/services/file"; import { requestFileThumbnailUpload } from "#lib/services/file.js";
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category"; export { requestCategoryCreation, requestFileRemovalFromCategory } from "#lib/services/category.js";
export { requestFileDownload } from "$lib/services/file"; export { requestFileDownload } from "#lib/services/file.js";
export const requestVideoStream = async ( export const requestVideoStream = async (
fileId: number, fileId: number,
@@ -1,14 +1,14 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { FullscreenDiv } from "$lib/components/atoms"; import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TopBar } from "$lib/components/molecules"; import { TopBar } from "#lib/components/molecules/index.js";
import { import {
getDownloadingFiles, getDownloadingFiles,
clearDownloadedFiles, clearDownloadedFiles,
type FileDownloadState, type FileDownloadState,
} from "$lib/modules/file"; } from "#lib/modules/file/index.js";
import { bulkGetFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem"; import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import File from "./File.svelte"; import File from "./File.svelte";
let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]); let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]);
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { FileDownloadState } from "$lib/modules/file"; import type { FileDownloadState } from "#lib/modules/file/index.js";
import type { SummarizedFileInfo } from "$lib/modules/filesystem"; import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import { formatNetworkSpeed } from "$lib/utils"; import { formatNetworkSpeed } from "#lib/utils/index.js";
import IconCloud from "~icons/material-symbols/cloud"; import IconCloud from "~icons/material-symbols/cloud";
import IconCloudDownload from "~icons/material-symbols/cloud-download"; import IconCloudDownload from "~icons/material-symbols/cloud-download";
@@ -19,7 +19,7 @@
</script> </script>
<div class="flex h-14 items-center gap-x-4 p-2"> <div class="flex h-14 items-center gap-x-4 p-2">
<div class="flex-shrink-0 text-lg text-gray-600"> <div class="shrink-0 text-lg text-gray-600">
{#if state.status === "download-pending"} {#if state.status === "download-pending"}
<IconCloud /> <IconCloud />
{:else if state.status === "downloading"} {:else if state.status === "downloading"}
@@ -34,7 +34,7 @@
<IconError class="text-red-500" /> <IconError class="text-red-500" />
{/if} {/if}
</div> </div>
<div class="flex-grow overflow-hidden"> <div class="grow overflow-hidden">
<p title={info.name} class="truncate font-medium"> <p title={info.name} class="truncate font-medium">
{info.name} {info.name}
</p> </p>
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { FullscreenDiv } from "$lib/components/atoms"; import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TopBar } from "$lib/components/molecules"; import { TopBar } from "#lib/components/molecules/index.js";
import { getUploadingFiles, clearUploadedFiles } from "$lib/modules/file"; import { getUploadingFiles, clearUploadedFiles } from "#lib/modules/file/index.js";
import File from "./File.svelte"; import File from "./File.svelte";
const uploadingFiles = getUploadingFiles(); const uploadingFiles = getUploadingFiles();
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { FileUploadState } from "$lib/modules/file"; import type { FileUploadState } from "#lib/modules/file/index.js";
import { formatNetworkSpeed } from "$lib/utils"; import { formatNetworkSpeed } from "#lib/utils/index.js";
import IconPending from "~icons/material-symbols/pending"; import IconPending from "~icons/material-symbols/pending";
import IconLockClock from "~icons/material-symbols/lock-clock"; import IconLockClock from "~icons/material-symbols/lock-clock";
@@ -17,7 +17,7 @@
</script> </script>
<div class="flex h-14 items-center gap-x-4 p-2"> <div class="flex h-14 items-center gap-x-4 p-2">
<div class="flex-shrink-0 text-lg text-gray-600"> <div class="shrink-0 text-lg text-gray-600">
{#if state.status === "queued" || state.status === "encryption-pending"} {#if state.status === "queued" || state.status === "encryption-pending"}
<IconPending /> <IconPending />
{:else if state.status === "encrypting"} {:else if state.status === "encrypting"}
@@ -32,7 +32,7 @@
<IconError class="text-red-500" /> <IconError class="text-red-500" />
{/if} {/if}
</div> </div>
<div class="flex-grow overflow-hidden"> <div class="grow overflow-hidden">
<p title={state.name} class="truncate font-medium"> <p title={state.name} class="truncate font-medium">
{state.name} {state.name}
</p> </p>
+11 -7
View File
@@ -1,15 +1,19 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { FileThumbnailButton, FullscreenDiv, RowVirtualizer } from "$lib/components/atoms"; import {
import { TopBar } from "$lib/components/molecules"; FileThumbnailButton,
FullscreenDiv,
RowVirtualizer,
} from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import { import {
bulkGetFileInfo, bulkGetFileInfo,
type MaybeFileInfo, type MaybeFileInfo,
type SummarizedFileInfo, type SummarizedFileInfo,
} from "$lib/modules/filesystem"; } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import { formatDate, formatDateSortable, SortBy, sortEntries } from "$lib/utils"; import { formatDate, formatDateSortable, SortBy, sortEntries } from "#lib/utils/index.js";
let { data } = $props(); let { data } = $props();
@@ -65,7 +69,7 @@
count={rows.length} count={rows.length}
estimateItemHeight={(index) => estimateItemHeight={(index) =>
rows[index]!.type === "header" ? 28 : 181 + (rows[index]!.isLast ? 16 : 4)} rows[index]!.type === "header" ? 28 : 181 + (rows[index]!.isLast ? 16 : 4)}
class="flex flex-grow flex-col" class="flex grow flex-col"
> >
{#snippet item(index)} {#snippet item(index)}
{@const row = rows[index]!} {@const row = rows[index]!}
@@ -83,7 +87,7 @@
{/if} {/if}
{/snippet} {/snippet}
{#snippet placeholder()} {#snippet placeholder()}
<div class="flex h-full flex-grow items-center justify-center"> <div class="flex h-full grow items-center justify-center">
<p class="text-gray-500"> <p class="text-gray-500">
{#if files.length === 0} {#if files.length === 0}
업로드된 파일이 없어요. 업로드된 파일이 없어요.
+1 -1
View File
@@ -1,4 +1,4 @@
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ parent }) => { export const load: PageLoad = async ({ parent }) => {
@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
import { TitledDiv } from "$lib/components/molecules"; import { TitledDiv } from "#lib/components/molecules/index.js";
import { serializeClientKeys, storeClientKeys } from "$lib/modules/key"; import { serializeClientKeys, storeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "$lib/stores"; import { clientKeyStore } from "#lib/stores/index.js";
import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte"; import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
import BeforeContinueModal from "./BeforeContinueModal.svelte"; import BeforeContinueModal from "./BeforeContinueModal.svelte";
import { import {
@@ -89,7 +89,7 @@
암호 키를 파일로 내보낼까요? 암호 키를 파일로 내보낼까요?
{/snippet} {/snippet}
<div class="space-y-2 break-keep text-lg text-gray-800"> <div class="space-y-2 text-lg break-keep text-gray-800">
<p> <p>
모든 디바이스의 암호 키가 유실되면, 서버에 저장된 데이터를 영원히 복호화할 수 없게 돼요. 모든 디바이스의 암호 키가 유실되면, 서버에 저장된 데이터를 영원히 복호화할 수 없게 돼요.
</p> </p>
+1 -1
View File
@@ -1,5 +1,5 @@
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { keyExportState } from "$lib/utils"; import { keyExportState } from "#lib/utils/index.js";
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
export const load: PageLoad = async () => { export const load: PageLoad = async () => {
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "$lib/components/atoms"; import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
interface Props { interface Props {
onRetryClick: () => void; onRetryClick: () => void;
@@ -12,7 +12,7 @@
<BottomSheet bind:isOpen class="flex flex-col"> <BottomSheet bind:isOpen class="flex flex-col">
<FullscreenDiv> <FullscreenDiv>
<div class="space-y-2 break-keep py-4"> <div class="space-y-2 py-4 break-keep">
<p class="text-xl font-bold">암호 키 파일을 저장하셨나요?</p> <p class="text-xl font-bold">암호 키 파일을 저장하셨나요?</p>
<p> <p>
암호 키 파일은 유출 방지를 위해 이 화면에서만 저장할 수 있어요. 파일이 잘 저장되었는지 다시 암호 키 파일은 유출 방지를 위해 이 화면에서만 저장할 수 있어요. 파일이 잘 저장되었는지 다시
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { ActionModal } from "$lib/components/molecules"; import { ActionModal } from "#lib/components/molecules/index.js";
interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
@@ -1,5 +1,5 @@
export { requestSessionUpgrade } from "$lib/services/auth"; export { requestSessionUpgrade } from "#lib/services/auth.js";
export { export {
requestClientRegistration, requestClientRegistration,
requestInitialMasterKeyAndHmacSecretRegistration, requestInitialMasterKeyAndHmacSecretRegistration,
} from "$lib/services/key"; } from "#lib/services/key.js";
@@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms"; import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
import { TitledDiv } from "$lib/components/molecules"; import { TitledDiv } from "#lib/components/molecules/index.js";
import { ForceLoginModal } from "$lib/components/organisms"; import { ForceLoginModal } from "#lib/components/organisms/index.js";
import { storeClientKeys } from "$lib/modules/key"; import { storeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "$lib/stores"; import { clientKeyStore } from "#lib/stores/index.js";
import { gotoStateful } from "$lib/utils"; import { gotoStateful } from "#lib/utils/index.js";
import Order from "./Order.svelte"; import Order from "./Order.svelte";
import { import {
generateClientKeys, generateClientKeys,
@@ -111,7 +111,7 @@
onMount(async () => { onMount(async () => {
if ($clientKeyStore) { if ($clientKeyStore) {
await goto(data.redirectPath, { replaceState: true }); await goto(data.redirectPath, { replace: true });
} }
}); });
</script> </script>
@@ -12,12 +12,12 @@
<div class="flex gap-x-3"> <div class="flex gap-x-3">
<div class="flex flex-col"> <div class="flex flex-col">
<p <p
class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 text-lg font-bold text-gray-700" class="flex size-8 items-center justify-center rounded-full bg-gray-200 text-lg font-bold text-gray-700"
> >
{order} {order}
</p> </p>
{#if !isLast} {#if !isLast}
<div class="mx-auto mt-2 w-0.5 flex-grow rounded-full bg-gray-300"></div> <div class="mx-auto mt-2 w-0.5 grow rounded-full bg-gray-300"></div>
{/if} {/if}
</div> </div>
<div class="mb-5 space-y-1 break-keep"> <div class="mb-5 space-y-1 break-keep">
@@ -10,16 +10,16 @@ import {
makeAESKeyNonextractable, makeAESKeyNonextractable,
wrapHmacSecret, wrapHmacSecret,
generateHmacSecret, generateHmacSecret,
} from "$lib/modules/crypto"; } from "#lib/modules/crypto/index.js";
import { deserializeClientKeys } from "$lib/modules/key"; import { deserializeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "$lib/stores"; import { clientKeyStore } from "#lib/stores/index.js";
export { requestLogout } from "$lib/services/auth"; export { requestLogout } from "#lib/services/auth.js";
export { requestDeletedFilesCleanup } from "$lib/services/file"; export { requestDeletedFilesCleanup } from "#lib/services/file.js";
export { export {
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration, requestInitialMasterKeyAndHmacSecretRegistration,
} from "$lib/services/key"; } from "#lib/services/key.js";
export const generateClientKeys = async () => { export const generateClientKeys = async () => {
const { encryptKey, decryptKey } = await generateEncryptionKeyPair(); const { encryptKey, decryptKey } = await generateEncryptionKeyPair();
+9 -9
View File
@@ -4,14 +4,14 @@
import { untrack } from "svelte"; import { untrack } from "svelte";
import { slide } from "svelte/transition"; import { slide } from "svelte/transition";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { Chip, FullscreenDiv, RowVirtualizer } from "$lib/components/atoms"; import { Chip, FullscreenDiv, RowVirtualizer } from "#lib/components/atoms/index.js";
import { import {
getDirectoryInfo, getDirectoryInfo,
type LocalCategoryInfo, type LocalCategoryInfo,
type MaybeDirectoryInfo, type MaybeDirectoryInfo,
} from "$lib/modules/filesystem"; } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise, searchString, sortEntries } from "$lib/utils"; import { HybridPromise, searchString, sortEntries } from "#lib/utils/index.js";
import Directory from "./Directory.svelte"; import Directory from "./Directory.svelte";
import File from "./File.svelte"; import File from "./File.svelte";
import SearchBar from "./SearchBar.svelte"; import SearchBar from "./SearchBar.svelte";
@@ -170,9 +170,9 @@
</svelte:head> </svelte:head>
<SearchBar bind:value={filters.name} /> <SearchBar bind:value={filters.name} />
<FullscreenDiv class="bg-gray-100 !px-0"> <FullscreenDiv class="bg-gray-100 px-0!">
<div class="flex flex-grow flex-col space-y-4"> <div class="flex grow flex-col space-y-4">
<div class="space-y-2 bg-white p-4 !pt-0"> <div class="space-y-2 bg-white p-4 pt-0!">
<div class="space-y-3"> <div class="space-y-3">
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<Chip bind:selected={filters.includeImages}>사진</Chip> <Chip bind:selected={filters.includeImages}>사진</Chip>
@@ -214,7 +214,7 @@
</div> </div>
</div> </div>
{#if hasAnyFilter} {#if hasAnyFilter}
<div class="flex flex-grow flex-col space-y-2 bg-white p-4"> <div class="flex grow flex-col space-y-2 bg-white p-4">
<p class="text-lg font-bold text-gray-800"> <p class="text-lg font-bold text-gray-800">
검색 결과 검색 결과
{#if result.length > 0} {#if result.length > 0}
@@ -238,7 +238,7 @@
{/snippet} {/snippet}
</RowVirtualizer> </RowVirtualizer>
{:else} {:else}
<div class="flex flex-grow items-center justify-center py-8"> <div class="flex grow items-center justify-center py-8">
<p class="text-gray-500">검색 결과가 없어요.</p> <p class="text-gray-500">검색 결과가 없어요.</p>
</div> </div>
{/if} {/if}
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms"; import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { DirectoryEntryLabel } from "$lib/components/molecules"; import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
import type { SubDirectoryInfo } from "$lib/modules/filesystem"; import type { SubDirectoryInfo } from "#lib/modules/filesystem/index.js";
interface Props { interface Props {
info: SubDirectoryInfo; info: SubDirectoryInfo;
+5 -5
View File
@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms"; import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { DirectoryEntryLabel } from "$lib/components/molecules"; import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
import { getFileThumbnail } from "$lib/modules/file"; import { getFileThumbnail } from "#lib/modules/file/index.js";
import type { SummarizedFileInfo } from "$lib/modules/filesystem"; import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import { formatDateTime } from "$lib/utils"; import { formatDateTime } from "#lib/utils/index.js";
interface Props { interface Props {
info: SummarizedFileInfo; info: SummarizedFileInfo;
@@ -8,10 +8,10 @@
let { value = $bindable() }: Props = $props(); let { value = $bindable() }: Props = $props();
</script> </script>
<div class={"sticky top-0 z-10 flex items-center gap-x-2 px-2 py-3 backdrop-blur-2xl"}> <div class="sticky top-0 z-10 flex items-center gap-x-2 px-2 py-3 backdrop-blur-2xl">
<button <button
onclick={() => history.back()} onclick={() => history.back()}
class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-black active:bg-opacity-[0.04]" class="w-[2.3rem] shrink-0 rounded-full p-1 active:bg-black/4"
> >
<IconArrowBack class="text-2xl" /> <IconArrowBack class="text-2xl" />
</button> </button>
@@ -19,6 +19,6 @@
bind:value bind:value
type="text" type="text"
placeholder="검색" placeholder="검색"
class="mr-1 h-[2.3rem] min-w-0 flex-grow rounded-lg bg-gray-100 px-3 py-1.5 placeholder-gray-600 focus:outline-none" class="mr-1 h-[2.3rem] min-w-0 grow rounded-lg bg-gray-100 px-3 py-1.5 placeholder-gray-600 focus:outline-hidden"
/> />
</div> </div>
@@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "$lib/components/atoms"; import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
import { SubCategories } from "$lib/components/molecules"; import { SubCategories } from "#lib/components/molecules/index.js";
import { import {
getCategoryInfo, getCategoryInfo,
type LocalCategoryInfo, type LocalCategoryInfo,
type MaybeCategoryInfo, type MaybeCategoryInfo,
} from "$lib/modules/filesystem"; } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "$lib/utils"; import { HybridPromise } from "#lib/utils/index.js";
interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
+3 -3
View File
@@ -4,9 +4,9 @@ import {
type LocalDirectoryInfo, type LocalDirectoryInfo,
type FileInfo, type FileInfo,
type LocalCategoryInfo, type LocalCategoryInfo,
} from "$lib/modules/filesystem"; } from "#lib/modules/filesystem/index.js";
import { HybridPromise } from "$lib/utils"; import { HybridPromise } from "#lib/utils/index.js";
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
export interface SearchFilter { export interface SearchFilter {
ancestorId: DirectoryId; ancestorId: DirectoryId;
+11 -8
View File
@@ -1,12 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { FullscreenDiv } from "$lib/components/atoms"; import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TopBar } from "$lib/components/molecules"; import { TopBar } from "#lib/components/molecules/index.js";
import type { FileCacheIndex } from "$lib/indexedDB"; import type { FileCacheIndex } from "#lib/indexedDB/index.js";
import { getFileCacheIndex, deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; import {
import { bulkGetFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem"; getFileCacheIndex,
import { masterKeyStore } from "$lib/stores"; deleteFileCache as doDeleteFileCache,
import { formatFileSize } from "$lib/utils"; } from "#lib/modules/file/index.js";
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { formatFileSize } from "#lib/utils/index.js";
import File from "./File.svelte"; import File from "./File.svelte";
interface FileCache { interface FileCache {
@@ -58,7 +61,7 @@
</div> </div>
</div> </div>
{:else} {:else}
<div class="flex flex-grow items-center justify-center"> <div class="flex grow items-center justify-center">
<p class="text-gray-500"> <p class="text-gray-500">
{#if fileCache} {#if fileCache}
캐시된 파일이 없어요. 캐시된 파일이 없어요.
+7 -7
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { FileCacheIndex } from "$lib/indexedDB"; import type { FileCacheIndex } from "#lib/indexedDB/index.js";
import type { MaybeFileInfo } from "$lib/modules/filesystem"; import type { MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { formatDate, formatFileSize } from "$lib/utils"; import { formatDate, formatFileSize } from "#lib/utils/index.js";
import IconDraft from "~icons/material-symbols/draft"; import IconDraft from "~icons/material-symbols/draft";
import IconScanDelete from "~icons/material-symbols/scan-delete"; import IconScanDelete from "~icons/material-symbols/scan-delete";
@@ -18,15 +18,15 @@
<div class="flex h-14 items-center gap-x-4 p-2"> <div class="flex h-14 items-center gap-x-4 p-2">
{#if info.exists} {#if info.exists}
<div class="flex-shrink-0 rounded-full bg-blue-100 p-1 text-xl"> <div class="shrink-0 rounded-full bg-blue-100 p-1 text-xl">
<IconDraft class="text-blue-400" /> <IconDraft class="text-blue-400" />
</div> </div>
{:else} {:else}
<div class="flex-shrink-0 rounded-full bg-red-100 p-1 text-xl"> <div class="shrink-0 rounded-full bg-red-100 p-1 text-xl">
<IconScanDelete class="text-red-400" /> <IconScanDelete class="text-red-400" />
</div> </div>
{/if} {/if}
<div class="flex-grow overflow-hidden"> <div class="grow overflow-hidden">
{#if info.exists} {#if info.exists}
<p title={info.name} class="truncate font-medium">{info.name}</p> <p title={info.name} class="truncate font-medium">{info.name}</p>
{:else} {:else}
@@ -38,7 +38,7 @@
</div> </div>
<button <button
onclick={() => setTimeout(() => onDeleteClick(index.fileId), 100)} onclick={() => setTimeout(() => onDeleteClick(index.fileId), 100)}
class="flex-shrink-0 rounded-full p-1 active:bg-gray-100" class="shrink-0 rounded-full p-1 active:bg-gray-100"
> >
<IconDelete class="text-lg text-gray-600" /> <IconDelete class="text-lg text-gray-600" />
</button> </button>
@@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms"; import { BottomDiv, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
import { IconEntryButton, TopBar } from "$lib/components/molecules"; import { IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
import { deleteAllFileThumbnailCaches } from "$lib/modules/file"; import { deleteAllFileThumbnailCaches } from "#lib/modules/file/index.js";
import type { MaybeFileInfo } from "$lib/modules/filesystem"; import type { MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "$lib/stores"; import { masterKeyStore } from "#lib/stores/index.js";
import { sortEntries } from "$lib/utils"; import { sortEntries } from "#lib/utils/index.js";
import File from "./File.svelte"; import File from "./File.svelte";
import { import {
getThumbnailGenerationStatus, getThumbnailGenerationStatus,
@@ -55,15 +55,15 @@
</svelte:head> </svelte:head>
<TopBar title="썸네일" /> <TopBar title="썸네일" />
<FullscreenDiv class="bg-gray-100 !px-0"> <FullscreenDiv class="bg-gray-100 px-0!">
<div class="flex flex-grow flex-col space-y-4"> <div class="flex grow flex-col space-y-4">
<div class="bg-white p-4 !pt-0"> <div class="bg-white p-4 pt-0!">
<IconEntryButton icon={IconDelete} onclick={deleteAllFileThumbnailCaches} class="w-full"> <IconEntryButton icon={IconDelete} onclick={deleteAllFileThumbnailCaches} class="w-full">
저장된 썸네일 모두 삭제하기 저장된 썸네일 모두 삭제하기
</IconEntryButton> </IconEntryButton>
</div> </div>
{#if files.length > 0} {#if files.length > 0}
<div class="flex-grow space-y-2 bg-white p-4"> <div class="grow space-y-2 bg-white p-4">
<p class="text-lg font-bold text-gray-800">썸네일이 누락된 파일</p> <p class="text-lg font-bold text-gray-800">썸네일이 누락된 파일</p>
<div class="space-y-4"> <div class="space-y-4">
<p class="break-keep text-gray-800"> <p class="break-keep text-gray-800">
@@ -1,4 +1,4 @@
import { trpc } from "$trpc/client"; import { trpc } from "#trpc/client.js";
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ parent }) => { export const load: PageLoad = async ({ parent }) => {
@@ -9,10 +9,10 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms"; import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { DirectoryEntryLabel } from "$lib/components/molecules"; import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
import type { FileInfo } from "$lib/modules/filesystem"; import type { FileInfo } from "#lib/modules/filesystem/index.js";
import { formatDateTime } from "$lib/utils"; import { formatDateTime } from "#lib/utils/index.js";
import type { GenerationStatus } from "./service"; import type { GenerationStatus } from "./service";
import IconCamera from "~icons/material-symbols/camera"; import IconCamera from "~icons/material-symbols/camera";
@@ -1,11 +1,11 @@
import { limitFunction } from "p-limit"; import { limitFunction } from "p-limit";
import { SvelteMap } from "svelte/reactivity"; import { SvelteMap } from "svelte/reactivity";
import { storeFileThumbnailCache } from "$lib/modules/file"; import { storeFileThumbnailCache } from "#lib/modules/file/index.js";
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; import { getFileInfo, type FileInfo } from "#lib/modules/filesystem/index.js";
import { generateThumbnail } from "$lib/modules/thumbnail"; import { generateThumbnail } from "#lib/modules/thumbnail.js";
import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file"; import { requestFileDownload, requestFileThumbnailUpload } from "#lib/services/file.js";
import { HybridPromise, Scheduler } from "$lib/utils"; import { HybridPromise, Scheduler } from "#lib/utils/index.js";
import type { RouterOutputs } from "$trpc/router.server"; import type { RouterOutputs } from "#trpc/router.server.js";
export type GenerationStatus = export type GenerationStatus =
"queued" | "generation-pending" | "generating" | "uploading" | "uploaded" | "error"; "queued" | "generation-pending" | "generating" | "uploading" | "uploaded" | "error";
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff. Show more