패키지 버전 업데이트 및 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,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/app.css",
"overrides": [
{
"files": "*.svelte",
+2 -2
View File
@@ -2,8 +2,8 @@
FROM node:22-alpine AS base
WORKDIR /app
RUN npm install -g pnpm@10
COPY pnpm-lock.yaml .
RUN npm install -g pnpm@12
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
# Build Stage
FROM base AS build
+9 -2
View File
@@ -1,5 +1,6 @@
import { includeIgnoreFile } from "@eslint/compat";
import js from "@eslint/js";
import { loadConfig } from "@sveltejs/load-config";
import { defineConfig } from "eslint/config";
import prettier from "eslint-config-prettier";
import svelte from "eslint-plugin-svelte";
@@ -7,7 +8,8 @@ import tailwind from "eslint-plugin-tailwindcss";
import globals from "globals";
import ts from "typescript-eslint";
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));
@@ -16,7 +18,7 @@ export default defineConfig(
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
...tailwind.configs["flat/recommended"],
tailwind.configs.recommended,
prettier,
...svelte.configs.prettier,
{
@@ -26,6 +28,11 @@ export default defineConfig(
...globals.node,
},
},
settings: {
tailwindcss: {
cssConfigPath: "./src/app.css",
},
},
rules: {
"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",
"build": "vite build",
"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",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
@@ -17,59 +17,65 @@
"db:migrate": "kysely migrate"
},
"devDependencies": {
"@eslint/compat": "^2.1.0",
"@eslint/compat": "^2.1.1",
"@eslint/js": "^10.0.1",
"@iconify-json/material-symbols": "^1.2.85",
"@noble/hashes": "^2.2.0",
"@sveltejs/adapter-node": "^5.5.7",
"@sveltejs/kit": "^2.69.3",
"@sveltejs/vite-plugin-svelte": "^7.2.0",
"@tanstack/svelte-virtual": "^3.13.32",
"@trpc/client": "^11.18.0",
"@iconify-json/material-symbols": "^1.2.95",
"@noble/hashes": "^2.4.0",
"@sveltejs/adapter-node": "^6.0.0",
"@sveltejs/kit": "^3.0.1",
"@sveltejs/load-config": "^0.2.2",
"@sveltejs/vite-plugin-svelte": "^7.3.1",
"@tailwindcss/vite": "^4.3.3",
"@tanstack/svelte-virtual": "^3.13.40",
"@trpc/client": "^11.19.0",
"@types/file-saver": "^2.0.7",
"@types/ms": "^0.7.34",
"@types/ms": "^2.1.0",
"@types/node-schedule": "^2.1.8",
"@types/pg": "^8.20.0",
"autoprefixer": "^10.5.3",
"axios": "^1.18.1",
"dexie": "^4.4.4",
"@types/pg": "^8.23.1",
"axios": "^1.20.0",
"dexie": "^4.4.6",
"es-hangul": "^2.4.0",
"eslint": "^10.7.0",
"eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.20.0",
"eslint-plugin-tailwindcss": "^3.18.3",
"exifreader": "^4.41.0",
"eslint-plugin-svelte": "^3.23.1",
"eslint-plugin-tailwindcss": "^4.4.0",
"exifreader": "^4.48.0",
"file-saver": "^2.0.5",
"globals": "^17.7.0",
"globals": "^17.13.0",
"heic2any": "^0.0.4",
"kysely-ctl": "^0.21.0",
"lru-cache": "^11.5.2",
"lru-cache": "^11.5.3",
"mime": "^4.1.0",
"p-limit": "^7.3.0",
"prettier": "^3.9.5",
"p-limit": "^7.3.3",
"prettier": "^3.9.10",
"prettier-plugin-svelte": "^4.1.1",
"prettier-plugin-tailwindcss": "^0.8.0",
"svelte": "^5.56.5",
"svelte-check": "^4.7.2",
"tailwindcss": "^3.4.19",
"prettier-plugin-tailwindcss": "^0.8.1",
"svelte": "^5.57.2",
"svelte-check": "^4.7.6",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3",
"typescript-eslint": "^8.64.0",
"unplugin-icons": "^23.0.1",
"vite": "^8.1.4"
"typescript-eslint": "^8.71.1",
"unplugin-icons": "^24.0.0",
"vite": "^8.3.4"
},
"dependencies": {
"@trpc/server": "^11.18.0",
"argon2": "^0.44.0",
"kysely": "^0.29.3",
"@trpc/server": "^11.19.0",
"argon2": "^0.45.1",
"kysely": "^0.29.6",
"ms": "^2.1.3",
"node-schedule": "^2.1.1",
"pg": "^8.22.0",
"pg": "^8.23.1",
"superjson": "^2.2.6",
"uuid": "^14.0.1",
"zod": "^4.4.3"
"uuid": "^14.0.3",
"zod": "^4.6.5"
},
"engines": {
"node": "^22.0.0",
"pnpm": "^10.0.0"
"node": "^22.17.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:
- argon2
- esbuild
- exifreader
allowBuilds:
argon2: true
esbuild: true
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/components";
@import "tailwindcss/utilities";
@import "tailwindcss";
@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 { cleanupDanglingInfos, getClientKey, getMasterKeys, getHmacSecrets } from "$lib/indexedDB";
import { prepareFileCache } from "$lib/modules/file";
import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores";
import type { ClientInit } from "@sveltejs/kit/hooks";
import {
cleanupDanglingInfos,
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 isPersistent = await navigator.storage.persist();
+6 -7
View File
@@ -1,14 +1,13 @@
import type { ServerInit } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";
import { sequence, type ServerInit } from "@sveltejs/kit/hooks";
import schedule from "node-schedule";
import { cleanupExpiredUserClientChallenges } from "$lib/server/db/client";
import { migrateDB } from "$lib/server/db/kysely";
import { cleanupExpiredUserClientChallenges } from "#lib/server/db/client.js";
import { migrateDB } from "#lib/server/db/kysely.js";
import {
cleanupExpiredSessions,
cleanupExpiredSessionUpgradeChallenges,
} from "$lib/server/db/session";
import { authenticate, setAgentInfo } from "$lib/server/middlewares";
import { cleanupExpiredUploadSessions } from "$lib/server/services/upload";
} from "#lib/server/db/session.js";
import { authenticate, setAgentInfo } from "#lib/server/middlewares/index.js";
import { cleanupExpiredUploadSessions } from "#lib/server/services/upload.js";
export const init: ServerInit = async () => {
await migrateDB();
+3 -6
View File
@@ -2,7 +2,7 @@
import type { Snippet } from "svelte";
import type { ClassValue } from "svelte/elements";
import { fade, fly } from "svelte/transition";
import { AdaptiveDiv } from "$lib/components/atoms";
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
interface Props {
children: Snippet;
@@ -21,17 +21,14 @@
onclick={onclose || (() => (isOpen = false))}
class="fixed inset-0 z-10 flex items-end justify-center"
>
<div
class="absolute inset-0 bg-black bg-opacity-50"
transition:fade|global={{ duration: 100 }}
></div>
<div class="absolute inset-0 bg-black/50" transition:fade|global={{ duration: 100 }}></div>
<AdaptiveDiv class="z-10 w-full">
<div
onclick={(e) => e.stopPropagation()}
class="flex max-h-[70vh] min-h-[30vh] flex-col rounded-t-2xl bg-white"
transition:fly|global={{ y: 100, duration: 200 }}
>
<div class={["flex-grow overflow-y-auto", className]}>
<div class={["grow overflow-y-auto", className]}>
{@render children()}
</div>
</div>
+2 -2
View File
@@ -2,7 +2,7 @@
import type { Snippet } from "svelte";
import type { ClassValue } from "svelte/elements";
import { fade } from "svelte/transition";
import { AdaptiveDiv } from "$lib/components/atoms";
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
interface Props {
children: Snippet;
@@ -19,7 +19,7 @@
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
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 }}
>
<AdaptiveDiv class="flex h-full items-center justify-center px-4">
@@ -55,7 +55,7 @@
<div style:height="{$virtualizer.getTotalSize()}px">
{#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)}
<div
class="absolute left-0 top-0 w-full"
class="absolute top-0 left-0 w-full"
style:transform="translateY({virtualItem.start - scrollMargin}px)"
data-index={virtualItem.index}
use:measureItem
@@ -29,7 +29,7 @@
class={["rounded-xl", className]}
>
<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()}
</div>
{#if ActionButtonIcon}
@@ -41,7 +41,7 @@
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 />
</button>
@@ -50,6 +50,8 @@
</div>
<style lang="postcss">
@reference "../../../../app.css";
#container:active:not(:has(#action-button:active)) {
@apply bg-gray-100;
}
@@ -18,9 +18,9 @@
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-grow">
<div class="grow">
{@render children()}
</div>
<IconChevronRight class="flex-shrink-0 text-xl text-gray-800" />
<IconChevronRight class="shrink-0 text-xl text-gray-800" />
</div>
</button>
@@ -1,6 +1,6 @@
<script lang="ts">
import { getFileThumbnail } from "$lib/modules/file";
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
import { getFileThumbnail } from "#lib/modules/file/index.js";
import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import IconFavorite from "~icons/material-symbols/favorite";
@@ -16,15 +16,15 @@
<button
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}
<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}
<div class="h-full w-full bg-gray-100"></div>
<div class="size-full bg-gray-100"></div>
{/if}
{#if info.isFavorite}
<div class={["absolute bottom-0.5 right-0.5"]}>
<div class={["absolute right-0.5 bottom-0.5"]}>
<IconFavorite
class="text-sm text-red-500"
style="filter: drop-shadow(0 0 1px white) drop-shadow(0 0 1px white)"
@@ -1,7 +1,7 @@
<script lang="ts">
import type { Component } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { AdaptiveDiv } from "$lib/components/atoms";
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
interface Props {
class: ClassValue;
@@ -17,7 +17,7 @@
<button
onclick={onclick && (() => setTimeout(onclick, 100))}
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,
]}
>
@@ -10,6 +10,6 @@
let { children, class: className }: Props = $props();
</script>
<div class={["mx-auto max-w-screen-md", className]}>
<div class={["mx-auto max-w-(--breakpoint-md)", className]}>
{@render children()}
</div>
@@ -10,6 +10,6 @@
let { children, class: className }: Props = $props();
</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()}
</div>
@@ -17,11 +17,11 @@
bind:value
{type}
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 -->
<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}
</label>
@@ -29,6 +29,8 @@
</div>
<style lang="postcss">
@reference "../../../../app.css";
input:focus,
input:not(:placeholder-shown) {
@apply border-primary-300;
@@ -4,7 +4,7 @@
<script lang="ts">
import type { Snippet } from "svelte";
import { Button, Modal } from "$lib/components/atoms";
import { Button, Modal } from "#lib/components/atoms/index.js";
interface Props {
cancelText?: string;
@@ -1,5 +1,5 @@
<script module lang="ts">
import type { DataKey } from "$lib/modules/filesystem";
import type { DataKey } from "#lib/modules/filesystem/index.js";
export interface SelectedCategory {
id: number;
@@ -11,10 +11,10 @@
<script lang="ts">
import type { Component } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";
import { ActionEntryButton } from "$lib/components/atoms";
import { CategoryLabel } from "$lib/components/molecules";
import type { SubCategoryInfo } from "$lib/modules/filesystem";
import { sortEntries } from "$lib/utils";
import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { CategoryLabel } from "#lib/components/molecules/index.js";
import type { SubCategoryInfo } from "#lib/modules/filesystem/index.js";
import { sortEntries } from "#lib/utils/index.js";
interface Props {
categories: SubCategoryInfo[];
@@ -1,8 +1,8 @@
<script lang="ts">
import type { Component, Snippet } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { EntryButton } from "$lib/components/atoms";
import { IconLabel } from "$lib/components/molecules";
import { EntryButton } from "#lib/components/atoms/index.js";
import { IconLabel } from "#lib/components/molecules/index.js";
interface Props {
children: Snippet;
@@ -1,8 +1,12 @@
<script lang="ts">
import type { Component } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { Categories, IconEntryButton, type SelectedCategory } from "$lib/components/molecules";
import type { CategoryInfo } from "$lib/modules/filesystem";
import {
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";
@@ -1,7 +1,7 @@
<script lang="ts">
import type { Component, Snippet } from "svelte";
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
import { TitleLabel } from "$lib/components/molecules";
import { TitleLabel } from "#lib/components/molecules/index.js";
interface Props {
children?: Snippet;
+4 -4
View File
@@ -21,20 +21,20 @@
className,
]}
>
<div class="w-[2.3rem] flex-shrink-0">
<div class="w-[2.3rem] shrink-0">
{#if showBackButton}
<button
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 />
</button>
{/if}
</div>
{#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}
<div class="w-[2.3rem] flex-shrink-0">
<div class="w-[2.3rem] shrink-0">
{#if children}
{@render children()}
{/if}
@@ -1,6 +1,6 @@
<script lang="ts">
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";
@@ -1,6 +1,6 @@
<script lang="ts">
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 IconDriveFolderUpload from "~icons/material-symbols/drive-folder-upload";
@@ -29,9 +29,14 @@
</script>
{#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}
<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"}
<IconFolder />
{:else if type === "parent-directory"}
@@ -40,7 +45,7 @@
<IconDraft class="text-blue-400" />
{/if}
{#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
class="text-xs text-red-500"
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]}>
{#if iconSnippet}
<div class={["flex-shrink-0", iconClassName]}>
<div class={["shrink-0", iconClassName]}>
{@render iconSnippet()}
</div>
{:else if Icon}
<div class={["flex-shrink-0 text-lg", iconClassName]}>
<div class={["shrink-0 text-lg", iconClassName]}>
<Icon />
</div>
{/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]}>
{@render children()}
</p>
@@ -1,5 +1,5 @@
<script lang="ts">
import { TextInputModal } from "$lib/components/organisms";
import { TextInputModal } from "#lib/components/organisms/index.js";
interface Props {
isOpen: boolean;
@@ -1,5 +1,5 @@
<script lang="ts">
import { ActionModal } from "$lib/components/molecules";
import { ActionModal } from "#lib/components/molecules/index.js";
interface Props {
isOpen: boolean;
@@ -1,5 +1,5 @@
<script lang="ts">
import { TextInputModal } from "$lib/components/organisms";
import { TextInputModal } from "#lib/components/organisms/index.js";
interface Props {
isOpen: boolean;
@@ -1,6 +1,6 @@
<script lang="ts">
import { TextInput } from "$lib/components/atoms";
import { ActionModal, type ConfirmHandler } from "$lib/components/molecules";
import { TextInput } from "#lib/components/atoms/index.js";
import { ActionModal, type ConfirmHandler } from "#lib/components/molecules/index.js";
interface Props {
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 {
encodeString,
decodeString,
+2 -2
View File
@@ -1,5 +1,5 @@
import HmacWorker from "$workers/hmac?worker";
import type { ComputeMessage, ResultMessage } from "$workers/hmac";
import HmacWorker from "#workers/hmac.js?worker";
import type { ComputeMessage, ResultMessage } from "#workers/hmac.js";
export const digestMessage = async (message: BufferSource) => {
return await crypto.subtle.digest("SHA-256", message);
+2 -2
View File
@@ -3,8 +3,8 @@ import {
storeFileCacheIndex,
deleteFileCacheIndex,
type FileCacheIndex,
} from "$lib/indexedDB";
import { readFile, writeFile, deleteFile } from "$lib/modules/opfs";
} from "#lib/indexedDB/index.js";
import { readFile, writeFile, deleteFile } from "#lib/modules/opfs.js";
const fileCacheIndex = new Map<number, FileCacheIndex>();
+2 -2
View File
@@ -1,7 +1,7 @@
import axios from "axios";
import { limitFunction } from "p-limit";
import { ENCRYPTED_CHUNK_SIZE } from "$lib/constants";
import { decryptChunk, concatenateBuffers } from "$lib/modules/crypto";
import { ENCRYPTED_CHUNK_SIZE } from "#lib/constants/index.js";
import { decryptChunk, concatenateBuffers } from "#lib/modules/crypto/index.js";
export interface FileDownloadState {
id: number;
+4 -4
View File
@@ -1,9 +1,9 @@
import { LRUCache } from "lru-cache";
import { writable, type Writable } from "svelte/store";
import { decryptChunk } from "$lib/modules/crypto";
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs";
import { getThumbnailUrl } from "$lib/modules/thumbnail";
import { decryptChunk } from "#lib/modules/crypto/index.js";
import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import { readFile, writeFile, deleteFile, deleteDirectory } from "#lib/modules/opfs.js";
import { getThumbnailUrl } from "#lib/modules/thumbnail.js";
const loadedThumbnails = new LRUCache<number, Writable<string>>({ max: 100 });
const loadingThumbnails = new Map<number, Writable<string | undefined>>();
+13 -8
View File
@@ -1,13 +1,18 @@
import ExifReader from "exifreader";
import { limitFunction } from "p-limit";
import { CHUNK_SIZE } from "$lib/constants";
import { encodeToBase64, generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto";
import { signMessageHmac } from "$lib/modules/crypto";
import { generateThumbnail } from "$lib/modules/thumbnail";
import { uploadBlob } from "$lib/modules/upload";
import type { MasterKey, HmacSecret } from "$lib/stores";
import { Scheduler } from "$lib/utils";
import { trpc } from "$trpc/client";
import { CHUNK_SIZE } from "#lib/constants/index.js";
import {
encodeToBase64,
generateDataKey,
wrapDataKey,
encryptString,
} from "#lib/modules/crypto/index.js";
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 {
id: string;
+2 -2
View File
@@ -1,5 +1,5 @@
import * as IndexedDB from "$lib/indexedDB";
import { trpc, isTRPCClientError } from "$trpc/client";
import * as IndexedDB from "#lib/indexedDB/index.js";
import { trpc, isTRPCClientError } from "#trpc/client.js";
import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte";
import type { CategoryInfo, MaybeCategoryInfo } from "./types";
+2 -2
View File
@@ -1,5 +1,5 @@
import * as IndexedDB from "$lib/indexedDB";
import { trpc, isTRPCClientError } from "$trpc/client";
import * as IndexedDB from "#lib/indexedDB/index.js";
import { trpc, isTRPCClientError } from "#trpc/client.js";
import { FilesystemCache, decryptDirectoryMetadata, decryptFileMetadata } from "./internal.svelte";
import type { DirectoryInfo, MaybeDirectoryInfo } from "./types";
+2 -2
View File
@@ -1,5 +1,5 @@
import * as IndexedDB from "$lib/indexedDB";
import { trpc, isTRPCClientError } from "$trpc/client";
import * as IndexedDB from "#lib/indexedDB/index.js";
import { trpc, isTRPCClientError } from "#trpc/client.js";
import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte";
import type { FileInfo, MaybeFileInfo } from "./types";
@@ -1,5 +1,5 @@
import { untrack } from "svelte";
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
import { unwrapDataKey, decryptString } from "#lib/modules/crypto/index.js";
interface FilesystemCacheOptions<K, V> {
fetchFromIndexedDB: (key: K) => Promise<V | undefined>;
+2 -2
View File
@@ -1,6 +1,6 @@
import { z } from "zod";
import { storeClientKey } from "$lib/indexedDB";
import type { ClientKeys } from "$lib/stores";
import { storeClientKey } from "#lib/indexedDB/index.js";
import type { ClientKeys } from "#lib/stores/index.js";
const SerializedClientKeysSchema = z.intersection(
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) => {
if (width <= targetSize || height <= targetSize) {
+3 -3
View File
@@ -1,9 +1,9 @@
import { sha256 } from "@noble/hashes/sha2.js";
import axios from "axios";
import pLimit from "p-limit";
import { ENCRYPTION_OVERHEAD, CHUNK_SIZE } from "$lib/constants";
import { encodeToBase64, encryptChunk } from "$lib/modules/crypto";
import { BoundedQueue } from "$lib/utils";
import { ENCRYPTION_OVERHEAD, CHUNK_SIZE } from "#lib/constants/index.js";
import { encodeToBase64, encryptChunk } from "#lib/modules/crypto/index.js";
import { BoundedQueue } from "#lib/utils/index.js";
interface UploadStats {
progress: number;
+1 -1
View File
@@ -1,7 +1,7 @@
import { Kysely, PostgresDialect } from "kysely";
import { Migrator } from "kysely/migration";
import pg from "pg";
import env from "$lib/server/loadenv";
import env from "#lib/server/loadenv.js";
import migrations from "./migrations";
import type { Database } from "./schema";
+1 -1
View File
@@ -1,5 +1,5 @@
import pg from "pg";
import env from "$lib/server/loadenv";
import env from "#lib/server/loadenv.js";
import { IntegrityError } from "./error";
import db from "./kysely";
+26 -20
View File
@@ -1,29 +1,35 @@
import ms from "ms";
import { building } from "$app/environment";
import { env } from "$env/dynamic/private";
if (!building) {
if (!env.DATABASE_PASSWORD) throw new Error("DATABASE_PASSWORD not set");
if (!env.SESSION_SECRET) throw new Error("SESSION_SECRET not set");
}
import {
NODE_ENV,
DATABASE_HOST,
DATABASE_PORT,
DATABASE_USER,
DATABASE_PASSWORD,
DATABASE_NAME,
SESSION_SECRET,
SESSION_EXPIRES,
USER_CLIENT_CHALLENGE_EXPIRES,
SESSION_UPGRADE_CHALLENGE_EXPIRES,
LIBRARY_PATH,
THUMBNAILS_PATH,
} from "$app/env/private";
export default {
nodeEnv: env.NODE_ENV || "development",
nodeEnv: NODE_ENV,
database: {
host: env.DATABASE_HOST,
port: env.DATABASE_PORT ? parseInt(env.DATABASE_PORT, 10) : undefined,
user: env.DATABASE_USER,
password: env.DATABASE_PASSWORD!,
name: env.DATABASE_NAME,
host: DATABASE_HOST,
port: DATABASE_PORT,
user: DATABASE_USER,
password: DATABASE_PASSWORD,
name: DATABASE_NAME,
},
session: {
secret: env.SESSION_SECRET!,
exp: ms(env.SESSION_EXPIRES || "14d"),
secret: SESSION_SECRET,
exp: SESSION_EXPIRES,
},
challenge: {
userClientExp: ms(env.USER_CLIENT_CHALLENGE_EXPIRES || "5m"),
sessionUpgradeExp: ms(env.SESSION_UPGRADE_CHALLENGE_EXPIRES || "5m"),
userClientExp: USER_CLIENT_CHALLENGE_EXPIRES,
sessionUpgradeExp: SESSION_UPGRADE_CHALLENGE_EXPIRES,
},
libraryPath: env.LIBRARY_PATH || "library",
thumbnailsPath: env.THUMBNAILS_PATH || "thumbnails",
libraryPath: LIBRARY_PATH,
thumbnailsPath: THUMBNAILS_PATH,
};
+3 -2
View File
@@ -1,5 +1,6 @@
import { error, redirect, type Handle } from "@sveltejs/kit";
import { cookieOptions, authenticate, AuthenticationError } from "$lib/server/modules/auth";
import { error, redirect } from "@sveltejs/kit";
import type { Handle } from "@sveltejs/kit/hooks";
import { cookieOptions, authenticate, AuthenticationError } from "#lib/server/modules/auth.js";
export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
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 }) => {
const ip = event.getClientAddress();
+3 -3
View File
@@ -1,7 +1,7 @@
import { error } from "@sveltejs/kit";
import { ClientRepo, SessionRepo, IntegrityError } from "$lib/server/db";
import env from "$lib/server/loadenv";
import { verifySessionId } from "$lib/server/modules/crypto";
import { ClientRepo, SessionRepo, IntegrityError } from "#lib/server/db/index.js";
import env from "#lib/server/loadenv.js";
import { verifySessionId } from "#lib/server/modules/crypto.js";
export interface Session {
sessionId: string;
+1 -1
View File
@@ -2,7 +2,7 @@ import { error } from "@sveltejs/kit";
import { createReadStream } from "fs";
import { stat } from "fs/promises";
import { Readable } from "stream";
import { FileRepo, MediaRepo } from "$lib/server/db";
import { FileRepo, MediaRepo } from "#lib/server/db/index.js";
const createEncContentStream = async (
path: string,
+3 -3
View File
@@ -1,9 +1,9 @@
import { error } from "@sveltejs/kit";
import { open } from "fs/promises";
import { Readable } from "stream";
import { ENCRYPTION_OVERHEAD, ENCRYPTED_CHUNK_SIZE } from "$lib/constants";
import { UploadRepo } from "$lib/server/db";
import { safeUnlink } from "$lib/server/modules/filesystem";
import { ENCRYPTION_OVERHEAD, ENCRYPTED_CHUNK_SIZE } from "#lib/constants/index.js";
import { UploadRepo } from "#lib/server/db/index.js";
import { safeUnlink } from "#lib/server/modules/filesystem.js";
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";
const PREPARE_TIMEOUT_MS = 5000;
+2 -2
View File
@@ -1,5 +1,5 @@
import { encodeToBase64, decryptChallenge, signMessageRSA } from "$lib/modules/crypto";
import { trpc, isTRPCClientError } from "$trpc/client";
import { encodeToBase64, decryptChallenge, signMessageRSA } from "#lib/modules/crypto/index.js";
import { trpc, isTRPCClientError } from "#trpc/client.js";
export const requestSessionUpgrade = async (
encryptKeyBase64: string,
+3 -3
View File
@@ -1,6 +1,6 @@
import { generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto";
import type { MasterKey } from "$lib/stores";
import { trpc } from "$trpc/client";
import { generateDataKey, wrapDataKey, encryptString } from "#lib/modules/crypto/index.js";
import type { MasterKey } from "#lib/stores/index.js";
import { trpc } from "#trpc/client.js";
export const requestCategoryCreation = async (
name: string,
+4 -4
View File
@@ -1,13 +1,13 @@
import { getAllFileInfos } from "$lib/indexedDB/filesystem";
import { getAllFileInfos } from "#lib/indexedDB/filesystem.js";
import {
getFileCache,
storeFileCache,
deleteFileCache,
downloadFile,
deleteFileThumbnailCache,
} from "$lib/modules/file";
import { uploadBlob } from "$lib/modules/upload";
import { trpc } from "$trpc/client";
} from "#lib/modules/file/index.js";
import { uploadBlob } from "#lib/modules/upload.js";
import { trpc } from "#trpc/client.js";
export const requestFileDownload = async (
fileId: number,
+5 -5
View File
@@ -1,4 +1,4 @@
import { storeMasterKeys } from "$lib/indexedDB";
import { storeMasterKeys } from "#lib/indexedDB/index.js";
import {
encodeToBase64,
exportRSAKeyToBase64,
@@ -7,10 +7,10 @@ import {
unwrapMasterKey,
signMasterKeyWrapped,
verifyMasterKeyWrapped,
} from "$lib/modules/crypto";
import { requestSessionUpgrade } from "$lib/services/auth";
import { masterKeyStore, type ClientKeys } from "$lib/stores";
import { trpc, isTRPCClientError } from "$trpc/client";
} from "#lib/modules/crypto/index.js";
import { requestSessionUpgrade } from "#lib/services/auth.js";
import { masterKeyStore, type ClientKeys } from "#lib/stores/index.js";
import { trpc, isTRPCClientError } from "#trpc/client.js";
export const requestClientRegistration = async (
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">
import { AdaptiveDiv } from "$lib/components/atoms";
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
let { children } = $props();
</script>
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextInput } from "$lib/components/atoms";
import { TitledDiv, TopBar } from "$lib/components/molecules";
import { BottomDiv, Button, FullscreenDiv, TextInput } from "#lib/components/atoms/index.js";
import { TitledDiv, TopBar } from "#lib/components/molecules/index.js";
import { requestPasswordChange } from "./service";
let oldPassword = $state("");
@@ -24,9 +24,9 @@
<title>비밀번호 바꾸기</title>
</svelte:head>
<TopBar class="flex-shrink-0" />
<TopBar class="shrink-0" />
<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}
@@ -1,4 +1,4 @@
import { trpc } from "$trpc/client";
import { trpc } from "#trpc/client.js";
export const requestPasswordChange = async (oldPassword: string, newPassword: string) => {
try {
@@ -1,9 +1,15 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms";
import { TitledDiv } from "$lib/components/molecules";
import { ForceLoginModal } from "$lib/components/organisms";
import { clientKeyStore, masterKeyStore } from "$lib/stores";
import {
BottomDiv,
Button,
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 {
requestLogin,
requestClientRegistrationAndSessionUpgrade,
@@ -1,11 +1,11 @@
import { trpc } from "$trpc/client";
import { trpc } from "#trpc/client.js";
export { requestLogout } from "$lib/services/auth";
export { requestDeletedFilesCleanup } from "$lib/services/file";
export { requestLogout } from "#lib/services/auth.js";
export { requestDeletedFilesCleanup } from "#lib/services/file.js";
export {
requestClientRegistrationAndSessionUpgrade,
requestMasterKeyDownload,
} from "$lib/services/key";
} from "#lib/services/key.js";
export const requestLogin = async (email: string, password: string) => {
try {
@@ -1,9 +1,9 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { FullscreenDiv } from "$lib/components/atoms";
import { TitledDiv } from "$lib/components/molecules";
import { clientKeyStore, masterKeyStore } from "$lib/stores";
import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { clientKeyStore, masterKeyStore } from "#lib/stores/index.js";
import { generatePublicKeyFingerprint, requestMasterKeyDownload } from "./service";
import IconFingerprint from "~icons/material-symbols/fingerprint";
@@ -22,7 +22,7 @@
($clientKeyStore &&
(await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)))
) {
await goto(data.redirectPath, { replaceState: true });
await goto(data.redirectPath, { replace: true });
}
});
</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) => {
const { key: encryptKeyBuffer } = await exportRSAKey(encryptKey);
@@ -3,13 +3,13 @@
import { untrack } from "svelte";
import { goto } from "$app/navigation";
import { page } from "$app/state";
import { FullscreenDiv } from "$lib/components/atoms";
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules";
import { getFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem";
import { captureVideoThumbnail } from "$lib/modules/thumbnail";
import { getFileDownloadState } from "$lib/modules/file";
import { masterKeyStore } from "$lib/stores";
import { HybridPromise } from "$lib/utils";
import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { Categories, IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
import { getFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { captureVideoThumbnail } from "#lib/modules/thumbnail.js";
import { getFileDownloadState } from "#lib/modules/file/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
import DownloadStatus from "./DownloadStatus.svelte";
import {
@@ -150,7 +150,7 @@
<div onclick={(e) => e.stopPropagation()}>
<button
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 />
</button>
@@ -1,10 +1,10 @@
<script lang="ts">
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "$lib/components/atoms";
import { SubCategories } from "$lib/components/molecules";
import { CategoryCreateModal } from "$lib/components/organisms";
import { getCategoryInfo, type MaybeCategoryInfo } from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { HybridPromise } from "$lib/utils";
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
import { SubCategories } from "#lib/components/molecules/index.js";
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
import { getCategoryInfo, type MaybeCategoryInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
import { requestCategoryCreation } from "./service";
interface Props {
@@ -1,6 +1,6 @@
<script lang="ts">
import { isFileDownloading, type FileDownloadState } from "$lib/modules/file";
import { formatNetworkSpeed } from "$lib/utils";
import { isFileDownloading, type FileDownloadState } from "#lib/modules/file/index.js";
import { formatNetworkSpeed } from "#lib/utils/index.js";
interface Props {
state: FileDownloadState;
@@ -46,7 +46,7 @@
{#if isOpen && (directoryId || downloadUrl || fileBlob)}
<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 }}
>
<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 { prepareFileDecryption, getDecryptedFileUrl } from "$lib/serviceWorker";
import { requestFileThumbnailUpload } from "$lib/services/file";
import { trpc } from "$trpc/client";
import { storeFileThumbnailCache } from "#lib/modules/file/index.js";
import { prepareFileDecryption, getDecryptedFileUrl } from "#lib/serviceWorker/index.js";
import { requestFileThumbnailUpload } from "#lib/services/file.js";
import { trpc } from "#trpc/client.js";
export { requestCategoryCreation, requestFileRemovalFromCategory } from "$lib/services/category";
export { requestFileDownload } from "$lib/services/file";
export { requestCategoryCreation, requestFileRemovalFromCategory } from "#lib/services/category.js";
export { requestFileDownload } from "#lib/services/file.js";
export const requestVideoStream = async (
fileId: number,
@@ -1,14 +1,14 @@
<script lang="ts">
import { onMount } from "svelte";
import { FullscreenDiv } from "$lib/components/atoms";
import { TopBar } from "$lib/components/molecules";
import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import {
getDownloadingFiles,
clearDownloadedFiles,
type FileDownloadState,
} from "$lib/modules/file";
import { bulkGetFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
} from "#lib/modules/file/index.js";
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import File from "./File.svelte";
let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]);
@@ -1,7 +1,7 @@
<script lang="ts">
import type { FileDownloadState } from "$lib/modules/file";
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
import { formatNetworkSpeed } from "$lib/utils";
import type { FileDownloadState } from "#lib/modules/file/index.js";
import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import { formatNetworkSpeed } from "#lib/utils/index.js";
import IconCloud from "~icons/material-symbols/cloud";
import IconCloudDownload from "~icons/material-symbols/cloud-download";
@@ -19,7 +19,7 @@
</script>
<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"}
<IconCloud />
{:else if state.status === "downloading"}
@@ -34,7 +34,7 @@
<IconError class="text-red-500" />
{/if}
</div>
<div class="flex-grow overflow-hidden">
<div class="grow overflow-hidden">
<p title={info.name} class="truncate font-medium">
{info.name}
</p>
@@ -1,7 +1,7 @@
<script lang="ts">
import { FullscreenDiv } from "$lib/components/atoms";
import { TopBar } from "$lib/components/molecules";
import { getUploadingFiles, clearUploadedFiles } from "$lib/modules/file";
import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import { getUploadingFiles, clearUploadedFiles } from "#lib/modules/file/index.js";
import File from "./File.svelte";
const uploadingFiles = getUploadingFiles();
@@ -1,6 +1,6 @@
<script lang="ts">
import type { FileUploadState } from "$lib/modules/file";
import { formatNetworkSpeed } from "$lib/utils";
import type { FileUploadState } from "#lib/modules/file/index.js";
import { formatNetworkSpeed } from "#lib/utils/index.js";
import IconPending from "~icons/material-symbols/pending";
import IconLockClock from "~icons/material-symbols/lock-clock";
@@ -17,7 +17,7 @@
</script>
<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"}
<IconPending />
{:else if state.status === "encrypting"}
@@ -32,7 +32,7 @@
<IconError class="text-red-500" />
{/if}
</div>
<div class="flex-grow overflow-hidden">
<div class="grow overflow-hidden">
<p title={state.name} class="truncate font-medium">
{state.name}
</p>
+11 -7
View File
@@ -1,15 +1,19 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { FileThumbnailButton, FullscreenDiv, RowVirtualizer } from "$lib/components/atoms";
import { TopBar } from "$lib/components/molecules";
import {
FileThumbnailButton,
FullscreenDiv,
RowVirtualizer,
} from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import {
bulkGetFileInfo,
type MaybeFileInfo,
type SummarizedFileInfo,
} from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { formatDate, formatDateSortable, SortBy, sortEntries } from "$lib/utils";
} from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { formatDate, formatDateSortable, SortBy, sortEntries } from "#lib/utils/index.js";
let { data } = $props();
@@ -65,7 +69,7 @@
count={rows.length}
estimateItemHeight={(index) =>
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)}
{@const row = rows[index]!}
@@ -83,7 +87,7 @@
{/if}
{/snippet}
{#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">
{#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";
export const load: PageLoad = async ({ parent }) => {
@@ -1,10 +1,10 @@
<script lang="ts">
import FileSaver from "file-saver";
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms";
import { TitledDiv } from "$lib/components/molecules";
import { serializeClientKeys, storeClientKeys } from "$lib/modules/key";
import { clientKeyStore } from "$lib/stores";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { serializeClientKeys, storeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "#lib/stores/index.js";
import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
import BeforeContinueModal from "./BeforeContinueModal.svelte";
import {
@@ -89,7 +89,7 @@
암호 키를 파일로 내보낼까요?
{/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>
+1 -1
View File
@@ -1,5 +1,5 @@
import { error } from "@sveltejs/kit";
import { keyExportState } from "$lib/utils";
import { keyExportState } from "#lib/utils/index.js";
import type { PageLoad } from "./$types";
export const load: PageLoad = async () => {
@@ -1,5 +1,5 @@
<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 {
onRetryClick: () => void;
@@ -12,7 +12,7 @@
<BottomSheet bind:isOpen class="flex flex-col">
<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>
암호 키 파일은 유출 방지를 위해 이 화면에서만 저장할 수 있어요. 파일이 잘 저장되었는지 다시
@@ -1,5 +1,5 @@
<script lang="ts">
import { ActionModal } from "$lib/components/molecules";
import { ActionModal } from "#lib/components/molecules/index.js";
interface Props {
isOpen: boolean;
@@ -1,5 +1,5 @@
export { requestSessionUpgrade } from "$lib/services/auth";
export { requestSessionUpgrade } from "#lib/services/auth.js";
export {
requestClientRegistration,
requestInitialMasterKeyAndHmacSecretRegistration,
} from "$lib/services/key";
} from "#lib/services/key.js";
@@ -1,12 +1,12 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "$lib/components/atoms";
import { TitledDiv } from "$lib/components/molecules";
import { ForceLoginModal } from "$lib/components/organisms";
import { storeClientKeys } from "$lib/modules/key";
import { clientKeyStore } from "$lib/stores";
import { gotoStateful } from "$lib/utils";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { ForceLoginModal } from "#lib/components/organisms/index.js";
import { storeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "#lib/stores/index.js";
import { gotoStateful } from "#lib/utils/index.js";
import Order from "./Order.svelte";
import {
generateClientKeys,
@@ -111,7 +111,7 @@
onMount(async () => {
if ($clientKeyStore) {
await goto(data.redirectPath, { replaceState: true });
await goto(data.redirectPath, { replace: true });
}
});
</script>
@@ -12,12 +12,12 @@
<div class="flex gap-x-3">
<div class="flex flex-col">
<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}
</p>
{#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}
</div>
<div class="mb-5 space-y-1 break-keep">
@@ -10,16 +10,16 @@ import {
makeAESKeyNonextractable,
wrapHmacSecret,
generateHmacSecret,
} from "$lib/modules/crypto";
import { deserializeClientKeys } from "$lib/modules/key";
import { clientKeyStore } from "$lib/stores";
} from "#lib/modules/crypto/index.js";
import { deserializeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "#lib/stores/index.js";
export { requestLogout } from "$lib/services/auth";
export { requestDeletedFilesCleanup } from "$lib/services/file";
export { requestLogout } from "#lib/services/auth.js";
export { requestDeletedFilesCleanup } from "#lib/services/file.js";
export {
requestClientRegistrationAndSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration,
} from "$lib/services/key";
} from "#lib/services/key.js";
export const generateClientKeys = async () => {
const { encryptKey, decryptKey } = await generateEncryptionKeyPair();
+9 -9
View File
@@ -4,14 +4,14 @@
import { untrack } from "svelte";
import { slide } from "svelte/transition";
import { goto } from "$app/navigation";
import { Chip, FullscreenDiv, RowVirtualizer } from "$lib/components/atoms";
import { Chip, FullscreenDiv, RowVirtualizer } from "#lib/components/atoms/index.js";
import {
getDirectoryInfo,
type LocalCategoryInfo,
type MaybeDirectoryInfo,
} from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { HybridPromise, searchString, sortEntries } from "$lib/utils";
} from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise, searchString, sortEntries } from "#lib/utils/index.js";
import Directory from "./Directory.svelte";
import File from "./File.svelte";
import SearchBar from "./SearchBar.svelte";
@@ -170,9 +170,9 @@
</svelte:head>
<SearchBar bind:value={filters.name} />
<FullscreenDiv class="bg-gray-100 !px-0">
<div class="flex flex-grow flex-col space-y-4">
<div class="space-y-2 bg-white p-4 !pt-0">
<FullscreenDiv class="bg-gray-100 px-0!">
<div class="flex grow flex-col space-y-4">
<div class="space-y-2 bg-white p-4 pt-0!">
<div class="space-y-3">
<div class="flex flex-wrap gap-2">
<Chip bind:selected={filters.includeImages}>사진</Chip>
@@ -214,7 +214,7 @@
</div>
</div>
{#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">
검색 결과
{#if result.length > 0}
@@ -238,7 +238,7 @@
{/snippet}
</RowVirtualizer>
{: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>
</div>
{/if}
@@ -1,7 +1,7 @@
<script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
import type { SubDirectoryInfo } from "$lib/modules/filesystem";
import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
import type { SubDirectoryInfo } from "#lib/modules/filesystem/index.js";
interface Props {
info: SubDirectoryInfo;
+5 -5
View File
@@ -1,9 +1,9 @@
<script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
import { getFileThumbnail } from "$lib/modules/file";
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
import { formatDateTime } from "$lib/utils";
import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
import { getFileThumbnail } from "#lib/modules/file/index.js";
import type { SummarizedFileInfo } from "#lib/modules/filesystem/index.js";
import { formatDateTime } from "#lib/utils/index.js";
interface Props {
info: SummarizedFileInfo;
@@ -8,10 +8,10 @@
let { value = $bindable() }: Props = $props();
</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
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" />
</button>
@@ -19,6 +19,6 @@
bind:value
type="text"
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>
@@ -1,13 +1,13 @@
<script lang="ts">
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "$lib/components/atoms";
import { SubCategories } from "$lib/components/molecules";
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
import { SubCategories } from "#lib/components/molecules/index.js";
import {
getCategoryInfo,
type LocalCategoryInfo,
type MaybeCategoryInfo,
} from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { HybridPromise } from "$lib/utils";
} from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
interface Props {
isOpen: boolean;
+3 -3
View File
@@ -4,9 +4,9 @@ import {
type LocalDirectoryInfo,
type FileInfo,
type LocalCategoryInfo,
} from "$lib/modules/filesystem";
import { HybridPromise } from "$lib/utils";
import { trpc } from "$trpc/client";
} from "#lib/modules/filesystem/index.js";
import { HybridPromise } from "#lib/utils/index.js";
import { trpc } from "#trpc/client.js";
export interface SearchFilter {
ancestorId: DirectoryId;
+11 -8
View File
@@ -1,12 +1,15 @@
<script lang="ts">
import { onMount } from "svelte";
import { FullscreenDiv } from "$lib/components/atoms";
import { TopBar } from "$lib/components/molecules";
import type { FileCacheIndex } from "$lib/indexedDB";
import { getFileCacheIndex, deleteFileCache as doDeleteFileCache } from "$lib/modules/file";
import { bulkGetFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { formatFileSize } from "$lib/utils";
import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import type { FileCacheIndex } from "#lib/indexedDB/index.js";
import {
getFileCacheIndex,
deleteFileCache as doDeleteFileCache,
} 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";
interface FileCache {
@@ -58,7 +61,7 @@
</div>
</div>
{:else}
<div class="flex flex-grow items-center justify-center">
<div class="flex grow items-center justify-center">
<p class="text-gray-500">
{#if fileCache}
캐시된 파일이 없어요.
+7 -7
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import type { FileCacheIndex } from "$lib/indexedDB";
import type { MaybeFileInfo } from "$lib/modules/filesystem";
import { formatDate, formatFileSize } from "$lib/utils";
import type { FileCacheIndex } from "#lib/indexedDB/index.js";
import type { MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { formatDate, formatFileSize } from "#lib/utils/index.js";
import IconDraft from "~icons/material-symbols/draft";
import IconScanDelete from "~icons/material-symbols/scan-delete";
@@ -18,15 +18,15 @@
<div class="flex h-14 items-center gap-x-4 p-2">
{#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" />
</div>
{: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" />
</div>
{/if}
<div class="flex-grow overflow-hidden">
<div class="grow overflow-hidden">
{#if info.exists}
<p title={info.name} class="truncate font-medium">{info.name}</p>
{:else}
@@ -38,7 +38,7 @@
</div>
<button
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" />
</button>
@@ -1,12 +1,12 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms";
import { IconEntryButton, TopBar } from "$lib/components/molecules";
import { deleteAllFileThumbnailCaches } from "$lib/modules/file";
import type { MaybeFileInfo } from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { sortEntries } from "$lib/utils";
import { BottomDiv, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
import { IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
import { deleteAllFileThumbnailCaches } from "#lib/modules/file/index.js";
import type { MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { sortEntries } from "#lib/utils/index.js";
import File from "./File.svelte";
import {
getThumbnailGenerationStatus,
@@ -55,15 +55,15 @@
</svelte:head>
<TopBar title="썸네일" />
<FullscreenDiv class="bg-gray-100 !px-0">
<div class="flex flex-grow flex-col space-y-4">
<div class="bg-white p-4 !pt-0">
<FullscreenDiv class="bg-gray-100 px-0!">
<div class="flex grow flex-col space-y-4">
<div class="bg-white p-4 pt-0!">
<IconEntryButton icon={IconDelete} onclick={deleteAllFileThumbnailCaches} class="w-full">
저장된 썸네일 모두 삭제하기
</IconEntryButton>
</div>
{#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>
<div class="space-y-4">
<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";
export const load: PageLoad = async ({ parent }) => {
@@ -9,10 +9,10 @@
</script>
<script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
import type { FileInfo } from "$lib/modules/filesystem";
import { formatDateTime } from "$lib/utils";
import { ActionEntryButton } from "#lib/components/atoms/index.js";
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
import type { FileInfo } from "#lib/modules/filesystem/index.js";
import { formatDateTime } from "#lib/utils/index.js";
import type { GenerationStatus } from "./service";
import IconCamera from "~icons/material-symbols/camera";
@@ -1,11 +1,11 @@
import { limitFunction } from "p-limit";
import { SvelteMap } from "svelte/reactivity";
import { storeFileThumbnailCache } from "$lib/modules/file";
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
import { generateThumbnail } from "$lib/modules/thumbnail";
import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file";
import { HybridPromise, Scheduler } from "$lib/utils";
import type { RouterOutputs } from "$trpc/router.server";
import { storeFileThumbnailCache } from "#lib/modules/file/index.js";
import { getFileInfo, type FileInfo } from "#lib/modules/filesystem/index.js";
import { generateThumbnail } from "#lib/modules/thumbnail.js";
import { requestFileDownload, requestFileThumbnailUpload } from "#lib/services/file.js";
import { HybridPromise, Scheduler } from "#lib/utils/index.js";
import type { RouterOutputs } from "#trpc/router.server.js";
export type GenerationStatus =
"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