From 2f6d35c3354537ab932106a856da2ff51f4aa6b3 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 18 Jan 2026 14:16:40 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EB=AA=A8=EB=B0=94=EC=9D=BC=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=20SearchBar=EC=9D=98=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=EC=9D=B4=20=EA=B9=A8=EC=A7=80?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(fullscreen)/file/[id]/TopBarMenu.svelte | 6 +- .../(fullscreen)/search/SearchBar.svelte | 9 +- src/routes/(main)/category/[[id]]/File.svelte | 4 +- .../(main)/directory/[[id]]/+page.svelte | 2 +- src/routes/(main)/favorites/+page.svelte | 92 +++++++++---------- src/routes/(main)/favorites/File.svelte | 9 +- 6 files changed, 64 insertions(+), 58 deletions(-) diff --git a/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte b/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte index 5c12fd4..83fff67 100644 --- a/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte +++ b/src/routes/(fullscreen)/file/[id]/TopBarMenu.svelte @@ -46,7 +46,7 @@ {#if isOpen && (directoryId || downloadUrl || fileBlob)}

더보기

@@ -57,7 +57,9 @@ onclick: () => void, )}
diff --git a/src/routes/(main)/category/[[id]]/File.svelte b/src/routes/(main)/category/[[id]]/File.svelte index 90fb659..3909d9e 100644 --- a/src/routes/(main)/category/[[id]]/File.svelte +++ b/src/routes/(main)/category/[[id]]/File.svelte @@ -3,6 +3,7 @@ import { DirectoryEntryLabel } from "$lib/components/molecules"; import { getFileThumbnail } from "$lib/modules/file"; import type { CategoryFileInfo } from "$lib/modules/filesystem"; + import { formatDateTime } from "$lib/utils"; import type { SelectedFile } from "./service.svelte"; import IconClose from "~icons/material-symbols/close"; @@ -19,7 +20,7 @@ onclick(info)} actionButtonIcon={onRemoveClick && IconClose} onActionButtonClick={() => onRemoveClick?.(info)} @@ -28,6 +29,7 @@ type="file" thumbnail={$thumbnail} name={info.name} + subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)} isFavorite={info.isFavorite} /> diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index 98abb79..075bd97 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -103,7 +103,7 @@ - 파일 + 내 파일 diff --git a/src/routes/(main)/favorites/+page.svelte b/src/routes/(main)/favorites/+page.svelte index 5c4c16d..c236c09 100644 --- a/src/routes/(main)/favorites/+page.svelte +++ b/src/routes/(main)/favorites/+page.svelte @@ -44,52 +44,50 @@ 즐겨찾기 - - - -
- {#if isLoading} -
-

- {#if data.favorites.files.length === 0 && data.favorites.directories.length === 0} - 즐겨찾기한 항목이 없어요. - {:else} - 즐겨찾기 목록을 불러오고 있어요. - {/if} -

-
- {:else if entries.length === 0} -
-

즐겨찾기한 항목이 없어요.

-
- {:else} - `${entries[index]!.type}-${entries[index]!.details.id}`} - estimateItemHeight={() => 56} - itemGap={4} +
+ + + +
+ {#if entries.length > 0} + `${entries[index]!.type}-${entries[index]!.details.id}`} + estimateItemHeight={() => 56} + itemGap={4} + > + {#snippet item(index)} + {@const entry = entries[index]!} + {#if entry.type === "directory"} + handleClick(entry)} + onRemoveClick={() => handleRemove(entry)} + /> + {:else} + handleClick(entry)} + onRemoveClick={() => handleRemove(entry)} + /> + {/if} + {/snippet} + + {:else} +
+

+ {#if isLoading} + 즐겨찾기 목록을 불러오고 있어요. + {:else} + 즐겨찾기한 항목이 없어요. + {/if} +

+
+ {/if} +
diff --git a/src/routes/(main)/favorites/File.svelte b/src/routes/(main)/favorites/File.svelte index 54fd07d..3750094 100644 --- a/src/routes/(main)/favorites/File.svelte +++ b/src/routes/(main)/favorites/File.svelte @@ -3,6 +3,7 @@ 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 IconClose from "~icons/material-symbols/close"; @@ -23,5 +24,11 @@ actionButtonIcon={IconClose} onActionButtonClick={onRemoveClick} > - + From 3b0cfd5a9244ec0fd50ec8e45bdbb37719f645d9 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 18 Jan 2026 16:16:38 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=ED=95=84=ED=84=B0=EB=A5=BC=20=EC=9E=AC?= =?UTF-8?q?=EA=B7=80=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/directory.ts | 77 +++++++++++++++------ src/lib/server/db/file.ts | 51 ++++++++++---- src/routes/(fullscreen)/search/+page.svelte | 16 +++-- src/routes/(fullscreen)/search/service.ts | 2 + src/trpc/routers/search.ts | 7 +- 5 files changed, 112 insertions(+), 41 deletions(-) diff --git a/src/lib/server/db/directory.ts b/src/lib/server/db/directory.ts index 432b1dd..6c566e7 100644 --- a/src/lib/server/db/directory.ts +++ b/src/lib/server/db/directory.ts @@ -74,28 +74,6 @@ export const getAllDirectoriesByParent = async (userId: number, parentId: Direct return directories.map(toDirectory); }; -export const getAllRecursiveDirectoriesByParent = async (userId: number, parentId: DirectoryId) => { - const directories = await db - .withRecursive("directory_tree", (db) => - db - .selectFrom("directory") - .selectAll() - .$if(parentId === "root", (qb) => qb.where("parent_id", "is", null)) - .$if(parentId !== "root", (qb) => qb.where("parent_id", "=", parentId as number)) - .where("user_id", "=", userId) - .unionAll((db) => - db - .selectFrom("directory") - .innerJoin("directory_tree", "directory.parent_id", "directory_tree.id") - .selectAll("directory"), - ), - ) - .selectFrom("directory_tree") - .selectAll() - .execute(); - return directories.map(toDirectory); -}; - export const getAllFavoriteDirectories = async (userId: number) => { const directories = await db .selectFrom("directory") @@ -117,6 +95,61 @@ export const getDirectory = async (userId: number, directoryId: number) => { return directory ? toDirectory(directory) : null; }; +export const searchDirectories = async ( + userId: number, + filters: { + parentId: DirectoryId; + inFavorites: boolean; + }, +) => { + const directories = await db + .withRecursive("directory_tree", (db) => + db + .selectFrom("directory") + .select("id") + .where("user_id", "=", userId) + .$if(filters.parentId === "root", (qb) => qb.where((eb) => eb.lit(false))) // directory_tree will be empty if parentId is "root" + .$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number)) + .unionAll( + db + .selectFrom("directory as d") + .innerJoin("directory_tree as dt", "d.parent_id", "dt.id") + .select("d.id"), + ), + ) + .withRecursive("favorite_directory_tree", (db) => + db + .selectFrom("directory") + .select("id") + .where("user_id", "=", userId) + .$if(!filters.inFavorites, (qb) => qb.where((eb) => eb.lit(false))) // favorite_directory_tree will be empty if inFavorites is false + .$if(filters.inFavorites, (qb) => qb.where("is_favorite", "=", true)) + .unionAll((db) => + db + .selectFrom("directory as d") + .innerJoin("favorite_directory_tree as dt", "d.parent_id", "dt.id") + .select("d.id"), + ), + ) + .selectFrom("directory") + .selectAll() + .where("user_id", "=", userId) + .$if(filters.parentId !== "root", (qb) => + qb.where((eb) => + eb.exists(eb.selectFrom("directory_tree as dt").whereRef("dt.id", "=", "parent_id")), + ), + ) + .$if(filters.inFavorites, (qb) => + qb.where((eb) => + eb.exists( + eb.selectFrom("favorite_directory_tree as dt").whereRef("dt.id", "=", "directory.id"), + ), + ), + ) + .execute(); + return directories.map(toDirectory); +}; + export const setDirectoryEncName = async ( userId: number, directoryId: number, diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index af2b6ad..4ad88ab 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -248,6 +248,7 @@ export const searchFiles = async ( userId: number, filters: { parentId: DirectoryId; + inFavorites: boolean; includeCategoryIds: number[]; excludeCategoryIds: number[]; }, @@ -258,7 +259,7 @@ export const searchFiles = async ( .selectFrom("directory") .select("id") .where("user_id", "=", userId) - .where((eb) => eb.val(filters.parentId !== "root")) // directory_tree will be empty if parentId is "root" + .$if(filters.parentId === "root", (qb) => qb.where((eb) => eb.lit(false))) // directory_tree will be empty if parentId is "root" .$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number)) .unionAll( db @@ -267,6 +268,20 @@ export const searchFiles = async ( .select("d.id"), ), ) + .withRecursive("favorite_directory_tree", (db) => + db + .selectFrom("directory") + .select("id") + .where("user_id", "=", userId) + .$if(!filters.inFavorites, (qb) => qb.where((eb) => eb.lit(false))) // favorite_directory_tree will be empty if inFavorites is false + .$if(filters.inFavorites, (qb) => qb.where("is_favorite", "=", true)) + .unionAll((db) => + db + .selectFrom("directory as d") + .innerJoin("favorite_directory_tree as dt", "d.parent_id", "dt.id") + .select("d.id"), + ), + ) .withRecursive("include_category_tree", (db) => db .selectFrom("category") @@ -295,19 +310,31 @@ export const searchFiles = async ( ) .selectFrom("file") .selectAll("file") - .$if(filters.parentId === "root", (qb) => qb.where("user_id", "=", userId)) // directory_tree isn't used if parentId is "root" + .where("user_id", "=", userId) .$if(filters.parentId !== "root", (qb) => - qb.where("parent_id", "in", (eb) => eb.selectFrom("directory_tree").select("id")), + qb.where((eb) => + eb.exists(eb.selectFrom("directory_tree as dt").whereRef("dt.id", "=", "file.parent_id")), + ), ) - .where((eb) => - eb.not( - eb.exists( - eb - .selectFrom("file_category") - .whereRef("file_id", "=", "file.id") - .where("category_id", "in", (eb) => - eb.selectFrom("exclude_category_tree").select("id"), - ), + .$if(filters.inFavorites, (qb) => + qb.where((eb) => + eb.or([ + eb("is_favorite", "=", true), + eb.exists( + eb.selectFrom("favorite_directory_tree as dt").whereRef("dt.id", "=", "file.parent_id"), + ), + ]), + ), + ) + .$if(filters.excludeCategoryIds.length > 0, (qb) => + qb.where((eb) => + eb.not( + eb.exists( + eb + .selectFrom("file_category") + .innerJoin("exclude_category_tree", "category_id", "exclude_category_tree.id") + .whereRef("file_id", "=", "file.id"), + ), ), ), ); diff --git a/src/routes/(fullscreen)/search/+page.svelte b/src/routes/(fullscreen)/search/+page.svelte index ef0c447..c2a423e 100644 --- a/src/routes/(fullscreen)/search/+page.svelte +++ b/src/routes/(fullscreen)/search/+page.svelte @@ -37,8 +37,8 @@ includeImages: false, includeVideos: false, includeDirectories: false, - searchInFavorites: false, searchInDirectory: false, + searchInFavorites: false, categories: [], }); let hasCategoryFilter = $derived(filters.categories.length > 0); @@ -47,7 +47,6 @@ filters.includeImages || filters.includeVideos || filters.includeDirectories || - filters.searchInFavorites || filters.name.trim().length > 0, ); @@ -84,9 +83,7 @@ return sortEntries( [...directories, ...files].filter( - (entry) => - (!nameFilter || searchString(entry.name, nameFilter)) && - (!filters.searchInFavorites || entry.isFavorite), + (entry) => !nameFilter || searchString(entry.name, nameFilter), ), ); }); @@ -145,6 +142,7 @@ // Svelte sucks hasAnyFilter; filters.searchInDirectory; + filters.searchInFavorites; filters.categories.length; if (untrack(() => isRestoredFromSnapshot)) { @@ -156,6 +154,7 @@ requestSearch( { ancestorId: filters.searchInDirectory ? data.directoryId! : "root", + inFavorites: filters.searchInFavorites, categories: filters.categories, }, $masterKeyStore?.get(1)?.key!, @@ -216,7 +215,12 @@
{#if hasAnyFilter}
-

검색 결과

+

+ 검색 결과 + {#if result.length > 0} + {" "}({result.length}개) + {/if} +

{#if result.length > 0} { const { directories: directoriesRaw, files: filesRaw } = await trpc().search.search.query({ ancestor: filter.ancestorId, + inFavorites: filter.inFavorites, includeCategories: filter.categories .filter(({ type }) => type === "include") .map(({ info }) => info.id), diff --git a/src/trpc/routers/search.ts b/src/trpc/routers/search.ts index 07caeb2..e47690a 100644 --- a/src/trpc/routers/search.ts +++ b/src/trpc/routers/search.ts @@ -8,6 +8,7 @@ const searchRouter = router({ .input( z.object({ ancestor: DirectoryIdSchema.default("root"), + inFavorites: z.boolean().default(false), includeCategories: z.number().positive().array().default([]), excludeCategories: z.number().positive().array().default([]), }), @@ -15,10 +16,14 @@ const searchRouter = router({ .query(async ({ ctx, input }) => { const [directories, files] = await Promise.all([ input.includeCategories.length === 0 && input.excludeCategories.length === 0 - ? DirectoryRepo.getAllRecursiveDirectoriesByParent(ctx.session.userId, input.ancestor) + ? DirectoryRepo.searchDirectories(ctx.session.userId, { + parentId: input.ancestor, + inFavorites: input.inFavorites, + }) : [], FileRepo.searchFiles(ctx.session.userId, { parentId: input.ancestor, + inFavorites: input.inFavorites, includeCategoryIds: input.includeCategories, excludeCategoryIds: input.excludeCategories, }), From 9635d2a51bc5075b29ec1f0bc861c7bb0c87bd5a Mon Sep 17 00:00:00 2001 From: static Date: Sun, 18 Jan 2026 16:32:06 +0900 Subject: [PATCH 3/3] =?UTF-8?q?IndexedDB=EC=97=90=20=EA=B0=80=EB=81=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20=EB=94=94=EB=A0=89=ED=84=B0?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/filesystem/internal.svelte.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/modules/filesystem/internal.svelte.ts b/src/lib/modules/filesystem/internal.svelte.ts index 30ba83b..918ceef 100644 --- a/src/lib/modules/filesystem/internal.svelte.ts +++ b/src/lib/modules/filesystem/internal.svelte.ts @@ -34,7 +34,7 @@ export class FilesystemCache { } (state.value - ? Promise.resolve(state.value) + ? Promise.resolve($state.snapshot(state.value) as V) : this.options.fetchFromIndexedDB(key).then((loadedInfo) => { if (loadedInfo) { state.value = loadedInfo;