This commit is contained in:
Timothy Jaeryang Baek committed 2026-07-21 13:53:30 -04:00
1 parent 86efecd9ad
commit 409fb39717
9 files changed
+374 -98

No files matched your search

@@ -44,7 +44,6 @@
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import FolderModal from './Folders/FolderModal.svelte';
import Emoji from '$lib/components/common/Emoji.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
export let folderRegistry = {};
export let open = false;
@@ -385,24 +384,46 @@
}, 500);
};
const SIDEBAR_CHATS_PAGE_SIZE = 10;
/** @type {any[] | null} */
let chats = null;
export const setFolderItems = async () => {
let chatsPage = 1;
let hasMoreChats = false;
let chatsLoading = false;
export const setFolderItems = async (append = false) => {
await tick();
if (open) {
if (open && !chatsLoading) {
// Always use getSharedFolderChats so owners also see chats
// created by users who have write access to this folder.
const nextPage = append ? chatsPage + 1 : 1;
chatsLoading = true;
try {
const res = await getSharedFolderChats(localStorage.token, folderId);
chats = res?.chats ?? [];
const res = await getSharedFolderChats(localStorage.token, folderId, {
page: nextPage
});
const nextChats = res?.chats ?? [];
chats = append ? [...(chats ?? []), ...nextChats] : nextChats;
chatsPage = nextPage;
hasMoreChats = res?.has_more ?? nextChats.length === SIDEBAR_CHATS_PAGE_SIZE;
} catch (error) {
// Fallback to regular API
chats = await getChatListByFolderId(localStorage.token, folderId).catch((error) => {
toast.error(`${error}`);
return [];
});
const fallback = await getChatListByFolderId(localStorage.token, folderId, nextPage).catch(
(error) => {
toast.error(`${error}`);
return [];
}
);
chats = append ? [...(chats ?? []), ...(fallback ?? [])] : (fallback ?? []);
chatsPage = nextPage;
hasMoreChats = (fallback?.length ?? 0) === SIDEBAR_CHATS_PAGE_SIZE;
} finally {
chatsLoading = false;
}
} else {
chats = null;
chatsPage = 1;
hasMoreChats = false;
}
};
@@ -564,7 +585,7 @@
await selectedFolder.set({ ...folders[folderId], ...folder });
}
await goto('/');
await goto(`/folders/${folderId}`);
if ($mobile) {
showSidebar.set(!$showSidebar);
@@ -678,7 +699,7 @@
</div>
<div slot="content" class="w-full">
{#if (folders[folderId]?.childrenIds ?? []).length > 0 || (chats ?? []).length > 0}
{#if (folders[folderId]?.childrenIds ?? []).length > 0 || (chats ?? []).length > 0 || hasMoreChats}
<div
class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
>
@@ -731,12 +752,41 @@
}}
/>
{/each}
{#if hasMoreChats}
<button
class="w-full px-2 py-0.5 text-left text-[11px] text-gray-400 transition hover:text-gray-700 disabled:cursor-not-allowed dark:text-gray-600 dark:hover:text-gray-300"
disabled={chatsLoading}
on:click={() => setFolderItems(true)}
>
{#if chatsLoading}
<div class="flex gap-1 px-2 py-1.5" aria-label="Loading">
<span class="size-1 rounded-full bg-gray-400 animate-pulse dark:bg-gray-600"
></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:150ms] dark:bg-gray-600"
></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:300ms] dark:bg-gray-600"
></span>
</div>
{:else}
{$i18n.t('Show more')}
{/if}
</button>
{/if}
</div>
{/if}
{#if chats === null}
<div class="flex justify-center items-center p-2">
<Spinner className="size-4 text-gray-500" />
{#if chats === null && chatsLoading}
<div class="flex gap-1 px-2 py-1.5" aria-label="Loading">
<span class="size-1 rounded-full bg-gray-400 animate-pulse dark:bg-gray-600"></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:150ms] dark:bg-gray-600"
></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:300ms] dark:bg-gray-600"
></span>
</div>
{/if}
</div>
@@ -1,6 +1,8 @@
<script lang="ts">
import { getContext, createEventDispatcher } from 'svelte';
const i18n = getContext('i18n');
import type { Writable } from 'svelte/store';
const i18n: Writable<any> = getContext('i18n');
const dispatch = createEventDispatcher();
import { goto } from '$app/navigation';
@@ -13,16 +15,18 @@
import ChevronRight from './icons/ChevronRight.svelte';
import Eye from './icons/Eye.svelte';
import FolderIcon from './icons/Folder.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
export let folder: any;
export let allSharedFolders: any[] = [];
export let className = '';
const SIDEBAR_CHATS_PAGE_SIZE = 10;
let expanded = false;
let chats: any[] = [];
let loading = false;
let loaded = false;
let page = 1;
let hasMoreChats = false;
$: children = allSharedFolders.filter((f) => f.parent_id === folder.id);
$: permission = folder.permission || 'read';
@@ -35,16 +39,27 @@
}
};
const loadChats = async () => {
const loadChats = async (append = false) => {
if (loading) {
return;
}
loading = true;
try {
const res = await getSharedFolderChats(localStorage.token, folder.id);
const nextPage = append ? page + 1 : 1;
const res = await getSharedFolderChats(localStorage.token, folder.id, {
page: nextPage
});
if (res) {
chats = res.chats || [];
const nextChats = res.chats || [];
chats = append ? [...chats, ...nextChats] : nextChats;
page = nextPage;
hasMoreChats = res.has_more ?? nextChats.length === SIDEBAR_CHATS_PAGE_SIZE;
}
} catch (e) {
console.error('Failed to load shared folder chats', e);
chats = [];
hasMoreChats = false;
} finally {
loading = false;
loaded = true;
@@ -92,10 +107,15 @@
{#if expanded}
<div class="pl-3">
{#if loading}
<div class="flex items-center gap-1.5 py-1 px-2 text-xs text-gray-400">
<Spinner className="size-3" />
<span>{$i18n.t('Loading...')}</span>
{#if loading && !loaded}
<div class="flex gap-1 px-2 py-1.5" aria-label="Loading">
<span class="size-1 rounded-full bg-gray-400 animate-pulse dark:bg-gray-600"></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:150ms] dark:bg-gray-600"
></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:300ms] dark:bg-gray-600"
></span>
</div>
{:else}
{#each chats as chat (chat.id)}
@@ -115,7 +135,29 @@
<svelte:self folder={child} {allSharedFolders} />
{/each}
{#if chats.length === 0 && children.length === 0}
{#if hasMoreChats}
<button
class="w-full px-2 py-0.5 text-left text-[11px] text-gray-400 transition hover:text-gray-700 disabled:cursor-not-allowed dark:text-gray-600 dark:hover:text-gray-300"
disabled={loading}
on:click={() => loadChats(true)}
>
{#if loading}
<div class="flex gap-1 px-2 py-1.5" aria-label="Loading">
<span class="size-1 rounded-full bg-gray-400 animate-pulse dark:bg-gray-600"></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:150ms] dark:bg-gray-600"
></span>
<span
class="size-1 rounded-full bg-gray-400 animate-pulse [animation-delay:300ms] dark:bg-gray-600"
></span>
</div>
{:else}
{$i18n.t('Show more')}
{/if}
</button>
{/if}
{#if chats.length === 0 && children.length === 0 && !hasMoreChats}
<div class="text-[11px] text-gray-400 dark:text-gray-600 py-1 px-2">
{$i18n.t('Empty')}
</div>