mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
이름 검색 로직 개선 및 뒤로가기로 검색 페이지로 돌아온 경우에 필터 및 검색 결과가 유지되도록 개선
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
"autoprefixer": "^10.4.23",
|
"autoprefixer": "^10.4.23",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"dexie": "^4.2.1",
|
"dexie": "^4.2.1",
|
||||||
|
"es-hangul": "^2.3.8",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-svelte": "^3.14.0",
|
"eslint-plugin-svelte": "^3.14.0",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -84,6 +84,9 @@ importers:
|
|||||||
dexie:
|
dexie:
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1
|
version: 4.2.1
|
||||||
|
es-hangul:
|
||||||
|
specifier: ^2.3.8
|
||||||
|
version: 2.3.8
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.2
|
specifier: ^9.39.2
|
||||||
version: 9.39.2(jiti@1.21.7)
|
version: 9.39.2(jiti@1.21.7)
|
||||||
@@ -989,6 +992,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-hangul@2.3.8:
|
||||||
|
resolution: {integrity: sha512-VrJuqYBC7W04aKYjCnswomuJNXQRc0q33SG1IltVrRofi2YEE6FwVDPlsEJIdKbHwsOpbBL/mk9sUaBxVpbd+w==}
|
||||||
|
|
||||||
es-object-atoms@1.1.1:
|
es-object-atoms@1.1.1:
|
||||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2781,6 +2787,8 @@ snapshots:
|
|||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-hangul@2.3.8: {}
|
||||||
|
|
||||||
es-object-atoms@1.1.1:
|
es-object-atoms@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ export const searchFiles = async (
|
|||||||
: await baseQuery.execute();
|
: await baseQuery.execute();
|
||||||
return files.map((file) => ({
|
return files.map((file) => ({
|
||||||
id: file.id,
|
id: file.id,
|
||||||
parentId: file.parent_id ?? "root",
|
parentId: file.parent_id ?? ("root" as const),
|
||||||
userId: file.user_id,
|
userId: file.user_id,
|
||||||
path: file.path,
|
path: file.path,
|
||||||
mekVersion: file.master_encryption_key_version,
|
mekVersion: file.master_encryption_key_version,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./concurrency";
|
export * from "./concurrency";
|
||||||
export * from "./format";
|
export * from "./format";
|
||||||
export * from "./gotoStateful";
|
export * from "./gotoStateful";
|
||||||
|
export * from "./search";
|
||||||
export * from "./sort";
|
export * from "./sort";
|
||||||
|
|||||||
28
src/lib/utils/search.ts
Normal file
28
src/lib/utils/search.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { disassemble, getChoseong } from "es-hangul";
|
||||||
|
|
||||||
|
const normalize = (s: string) => {
|
||||||
|
return s.normalize("NFC").toLowerCase().replace(/\s/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractHangul = (s: string) => {
|
||||||
|
return s.replace(/[^가-힣ㄱ-ㅎㅏ-ㅣ]/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const hangulSearch = (original: string, query: string) => {
|
||||||
|
original = extractHangul(original);
|
||||||
|
query = extractHangul(query);
|
||||||
|
if (!original || !query) return false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
disassemble(original).includes(disassemble(query)) ||
|
||||||
|
getChoseong(original).includes(getChoseong(query))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchString = (original: string, query: string) => {
|
||||||
|
original = normalize(original);
|
||||||
|
query = normalize(query);
|
||||||
|
if (!original || !query) return false;
|
||||||
|
|
||||||
|
return original.includes(query) || hangulSearch(original, query);
|
||||||
|
};
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snapshot } from "@sveltejs/kit";
|
||||||
|
import superjson, { type SuperJSONResult } from "superjson";
|
||||||
|
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";
|
||||||
@@ -8,7 +11,7 @@
|
|||||||
type MaybeDirectoryInfo,
|
type MaybeDirectoryInfo,
|
||||||
} from "$lib/modules/filesystem";
|
} from "$lib/modules/filesystem";
|
||||||
import { masterKeyStore } from "$lib/stores";
|
import { masterKeyStore } from "$lib/stores";
|
||||||
import { HybridPromise, sortEntries } from "$lib/utils";
|
import { HybridPromise, searchString, sortEntries } from "$lib/utils";
|
||||||
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";
|
||||||
@@ -17,15 +20,24 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
interface SearchFilters {
|
||||||
|
name: string;
|
||||||
|
includeImages: boolean;
|
||||||
|
includeVideos: boolean;
|
||||||
|
includeDirectories: boolean;
|
||||||
|
searchInDirectory: boolean;
|
||||||
|
categories: SearchFilter["categories"];
|
||||||
|
}
|
||||||
|
|
||||||
let directoryInfo: MaybeDirectoryInfo | undefined = $state();
|
let directoryInfo: MaybeDirectoryInfo | undefined = $state();
|
||||||
|
|
||||||
let filters = $state({
|
let filters: SearchFilters = $state({
|
||||||
name: "",
|
name: "",
|
||||||
includeImages: false,
|
includeImages: false,
|
||||||
includeVideos: false,
|
includeVideos: false,
|
||||||
includeDirectories: false,
|
includeDirectories: false,
|
||||||
searchInDirectory: false,
|
searchInDirectory: false,
|
||||||
categories: [] as SearchFilter["categories"],
|
categories: [],
|
||||||
});
|
});
|
||||||
let hasCategoryFilter = $derived(filters.categories.length > 0);
|
let hasCategoryFilter = $derived(filters.categories.length > 0);
|
||||||
let hasAnyFilter = $derived(
|
let hasAnyFilter = $derived(
|
||||||
@@ -36,11 +48,12 @@
|
|||||||
filters.name.trim().length > 0,
|
filters.name.trim().length > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let isRestoredFromSnapshot = $state(false);
|
||||||
let serverResult: SearchResult | undefined = $state();
|
let serverResult: SearchResult | undefined = $state();
|
||||||
let result = $derived.by(() => {
|
let result = $derived.by(() => {
|
||||||
if (!serverResult) return [];
|
if (!serverResult) return [];
|
||||||
|
|
||||||
const nameFilter = filters.name.trim().toLowerCase();
|
const nameFilter = filters.name.trim();
|
||||||
const hasTypeFilter =
|
const hasTypeFilter =
|
||||||
filters.includeImages || filters.includeVideos || filters.includeDirectories;
|
filters.includeImages || filters.includeVideos || filters.includeDirectories;
|
||||||
|
|
||||||
@@ -58,7 +71,7 @@
|
|||||||
|
|
||||||
return sortEntries(
|
return sortEntries(
|
||||||
[...directories, ...files].filter(
|
[...directories, ...files].filter(
|
||||||
({ name }) => !nameFilter || name.toLowerCase().includes(nameFilter),
|
({ name }) => !nameFilter || searchString(name, nameFilter),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -81,6 +94,20 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const snapshot: Snapshot<{
|
||||||
|
filters: SearchFilters;
|
||||||
|
serverResult: SuperJSONResult;
|
||||||
|
}> = {
|
||||||
|
capture() {
|
||||||
|
return { filters, serverResult: superjson.serialize(serverResult) };
|
||||||
|
},
|
||||||
|
restore(value) {
|
||||||
|
filters = value.filters;
|
||||||
|
serverResult = superjson.deserialize(value.serverResult, { inPlace: true });
|
||||||
|
isRestoredFromSnapshot = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (data.directoryId) {
|
if (data.directoryId) {
|
||||||
HybridPromise.resolve(getDirectoryInfo(data.directoryId, $masterKeyStore?.get(1)?.key!)).then(
|
HybridPromise.resolve(getDirectoryInfo(data.directoryId, $masterKeyStore?.get(1)?.key!)).then(
|
||||||
@@ -96,6 +123,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
// Svelte sucks
|
||||||
|
hasAnyFilter;
|
||||||
|
filters.searchInDirectory;
|
||||||
|
filters.categories.length;
|
||||||
|
|
||||||
|
if (untrack(() => isRestoredFromSnapshot)) {
|
||||||
|
isRestoredFromSnapshot = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAnyFilter) {
|
if (hasAnyFilter) {
|
||||||
requestSearch(
|
requestSearch(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -91,5 +91,5 @@ export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey)
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { directories, files };
|
return { directories, files } satisfies SearchResult;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user