فهرست منبع

refac: sidebar

Timothy J. Baek 10 ماه پیش
والد
کامیت
d03a00758f
2فایلهای تغییر یافته به همراه306 افزوده شده و 291 حذف شده
  1. 8 291
      src/lib/components/layout/Sidebar.svelte
  2. 298 0
      src/lib/components/layout/Sidebar/ChatItem.svelte

+ 8 - 291
src/lib/components/layout/Sidebar.svelte

@@ -28,34 +28,18 @@
 	import { toast } from 'svelte-sonner';
 	import { fade, slide } from 'svelte/transition';
 	import { WEBUI_BASE_URL } from '$lib/constants';
-	import Tooltip from '../common/Tooltip.svelte';
-	import ChatMenu from './Sidebar/ChatMenu.svelte';
-	import ShareChatModal from '../chat/ShareChatModal.svelte';
-	import ArchiveBox from '../icons/ArchiveBox.svelte';
 	import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
 	import UserMenu from './Sidebar/UserMenu.svelte';
 	import { updateUserSettings } from '$lib/apis/users';
+	import ChatItem from './Sidebar/ChatItem.svelte';
 
 	const BREAKPOINT = 768;
 
-	let show = false;
 	let navElement;
-
-	let title: string = 'UI';
 	let search = '';
 
-	let shareChatId = null;
-
 	let selectedChatId = null;
-
-	let chatDeleteId = null;
-	let chatTitleEditId = null;
-	let chatTitle = '';
-
-	let showShareChatModal = false;
 	let showDropdown = false;
-	let isEditing = false;
-
 	let filteredChatList = [];
 
 	$: filteredChatList = $chats.filter((chat) => {
@@ -78,13 +62,6 @@
 		}
 	});
 
-	mobile;
-	const onResize = () => {
-		if ($showSidebar && window.innerWidth < BREAKPOINT) {
-			showSidebar.set(false);
-		}
-	};
-
 	onMount(async () => {
 		mobile.subscribe((e) => {
 			if ($showSidebar && e) {
@@ -149,69 +126,13 @@
 		await chats.set(enrichedChats);
 	};
 
-	const loadChat = async (id) => {
-		goto(`/c/${id}`);
-	};
-
-	const editChatTitle = async (id, _title) => {
-		if (_title === '') {
-			toast.error($i18n.t('Title cannot be an empty string.'));
-		} else {
-			title = _title;
-
-			await updateChatById(localStorage.token, id, {
-				title: _title
-			});
-			await chats.set(await getChatList(localStorage.token));
-		}
-	};
-
-	const deleteChat = async (id) => {
-		const res = await deleteChatById(localStorage.token, id).catch((error) => {
-			toast.error(error);
-			chatDeleteId = null;
-
-			return null;
-		});
-
-		if (res) {
-			if ($chatId === id) {
-				goto('/');
-			}
-
-			await chats.set(await getChatList(localStorage.token));
-		}
-	};
-
-	const cloneChatHandler = async (id) => {
-		const res = await cloneChatById(localStorage.token, id).catch((error) => {
-			toast.error(error);
-			return null;
-		});
-
-		if (res) {
-			goto(`/c/${res.id}`);
-			await chats.set(await getChatList(localStorage.token));
-		}
-	};
-
 	const saveSettings = async (updated) => {
 		await settings.set({ ...$settings, ...updated });
 		await updateUserSettings(localStorage.token, { ui: $settings });
 		location.href = '/';
 	};
-
-	const archiveChatHandler = async (id) => {
-		await archiveChatById(localStorage.token, id);
-		await chats.set(await getChatList(localStorage.token));
-	};
-
-	const focusEdit = async (node: HTMLInputElement) => {
-		node.focus();
-	};
 </script>
 
-<ShareChatModal bind:show={showShareChatModal} chatId={shareChatId} />
 <ArchivedChatsModal
 	bind:show={$showArchivedChats}
 	on:change={async () => {
@@ -252,12 +173,10 @@
 				draggable="false"
 				on:click={async () => {
 					selectedChatId = null;
-
 					await goto('/');
 					const newChatButton = document.getElementById('new-chat-button');
 					setTimeout(() => {
 						newChatButton?.click();
-
 						if ($mobile) {
 							showSidebar.set(false);
 						}
@@ -486,215 +405,13 @@
 						</div>
 					{/if}
 
-					<div class=" w-full pr-2 relative group">
-						{#if chatTitleEditId === chat.id}
-							<div
-								class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
-								chat.id === chatTitleEditId ||
-								chat.id === chatDeleteId
-									? 'bg-gray-200 dark:bg-gray-900'
-									: chat.id === selectedChatId
-									? 'bg-gray-100 dark:bg-gray-950'
-									: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
-							>
-								<input
-									use:focusEdit
-									bind:value={chatTitle}
-									class=" bg-transparent w-full outline-none mr-10"
-								/>
-							</div>
-						{:else}
-							<a
-								class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
-								chat.id === chatTitleEditId ||
-								chat.id === chatDeleteId
-									? 'bg-gray-200 dark:bg-gray-900'
-									: chat.id === selectedChatId
-									? 'bg-gray-100 dark:bg-gray-950'
-									: ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
-								href="/c/{chat.id}"
-								on:click={() => {
-									selectedChatId = chat.id;
-									if ($mobile) {
-										showSidebar.set(false);
-									}
-								}}
-								on:dblclick={() => {
-									chatTitle = chat.title;
-									chatTitleEditId = chat.id;
-								}}
-								draggable="false"
-							>
-								<div class=" flex self-center flex-1 w-full">
-									<div class=" text-left self-center overflow-hidden w-full h-[20px]">
-										{chat.title}
-									</div>
-								</div>
-							</a>
-						{/if}
-
-						<div
-							class="
-
-							{chat.id === $chatId || chat.id === chatTitleEditId || chat.id === chatDeleteId
-								? 'from-gray-200 dark:from-gray-900'
-								: chat.id === selectedChatId
-								? 'from-gray-100 dark:from-gray-950'
-								: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
-								absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
-
-								  to-transparent"
-						>
-							{#if chatTitleEditId === chat.id}
-								<div class="flex self-center space-x-1.5 z-10">
-									<button
-										class=" self-center dark:hover:text-white transition"
-										on:click={() => {
-											editChatTitle(chat.id, chatTitle);
-											chatTitleEditId = null;
-											chatTitle = '';
-										}}
-									>
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 20 20"
-											fill="currentColor"
-											class="w-4 h-4"
-										>
-											<path
-												fill-rule="evenodd"
-												d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
-												clip-rule="evenodd"
-											/>
-										</svg>
-									</button>
-									<button
-										class=" self-center dark:hover:text-white transition"
-										on:click={() => {
-											chatTitleEditId = null;
-											chatTitle = '';
-										}}
-									>
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 20 20"
-											fill="currentColor"
-											class="w-4 h-4"
-										>
-											<path
-												d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
-											/>
-										</svg>
-									</button>
-								</div>
-							{:else if chatDeleteId === chat.id}
-								<div class="flex self-center space-x-1.5 z-10">
-									<button
-										class=" self-center dark:hover:text-white transition"
-										on:click={() => {
-											deleteChat(chat.id);
-										}}
-									>
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 20 20"
-											fill="currentColor"
-											class="w-4 h-4"
-										>
-											<path
-												fill-rule="evenodd"
-												d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
-												clip-rule="evenodd"
-											/>
-										</svg>
-									</button>
-									<button
-										class=" self-center dark:hover:text-white transition"
-										on:click={() => {
-											chatDeleteId = null;
-										}}
-									>
-										<svg
-											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 20 20"
-											fill="currentColor"
-											class="w-4 h-4"
-										>
-											<path
-												d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
-											/>
-										</svg>
-									</button>
-								</div>
-							{:else}
-								<div class="flex self-center space-x-1 z-10">
-									<ChatMenu
-										chatId={chat.id}
-										cloneChatHandler={() => {
-											cloneChatHandler(chat.id);
-										}}
-										shareHandler={() => {
-											shareChatId = selectedChatId;
-											showShareChatModal = true;
-										}}
-										archiveChatHandler={() => {
-											archiveChatHandler(chat.id);
-										}}
-										renameHandler={() => {
-											chatTitle = chat.title;
-											chatTitleEditId = chat.id;
-										}}
-										deleteHandler={() => {
-											chatDeleteId = chat.id;
-										}}
-										onClose={() => {
-											selectedChatId = null;
-										}}
-									>
-										<button
-											aria-label="Chat Menu"
-											class=" self-center dark:hover:text-white transition"
-											on:click={() => {
-												selectedChatId = chat.id;
-											}}
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												viewBox="0 0 16 16"
-												fill="currentColor"
-												class="w-4 h-4"
-											>
-												<path
-													d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
-												/>
-											</svg>
-										</button>
-									</ChatMenu>
-
-									{#if chat.id === $chatId}
-										<button
-											id="delete-chat-button"
-											class="hidden"
-											on:click={() => {
-												chatDeleteId = chat.id;
-											}}
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												viewBox="0 0 16 16"
-												fill="currentColor"
-												class="w-4 h-4"
-											>
-												<path
-													d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
-												/>
-											</svg>
-										</button>
-									{/if}
-								</div>
-							{/if}
-						</div>
-					</div>
+					<ChatItem
+						{chat}
+						selected={selectedChatId === chat.id}
+						on:select={() => {
+							selectedChatId = chat.id;
+						}}
+					/>
 				{/each}
 			</div>
 		</div>

+ 298 - 0
src/lib/components/layout/Sidebar/ChatItem.svelte

@@ -0,0 +1,298 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import { goto } from '$app/navigation';
+	import { onMount, getContext, createEventDispatcher } from 'svelte';
+	const i18n = getContext('i18n');
+
+	const dispatch = createEventDispatcher();
+
+	import {
+		archiveChatById,
+		cloneChatById,
+		deleteChatById,
+		getChatList,
+		updateChatById
+	} from '$lib/apis/chats';
+	import { chatId, chats, mobile, showSidebar } from '$lib/stores';
+
+	import ChatMenu from './ChatMenu.svelte';
+	import ShareChatModal from '$lib/components/chat/ShareChatModal.svelte';
+
+	export let chat;
+
+	let showShareChatModal = false;
+
+	export let selected = false;
+
+	let confirmEdit = false;
+	let confirmDelete = false;
+
+	let chatTitle = '';
+
+	const editChatTitle = async (id, _title) => {
+		if (_title === '') {
+			toast.error($i18n.t('Title cannot be an empty string.'));
+		} else {
+			await updateChatById(localStorage.token, id, {
+				title: _title
+			});
+			await chats.set(await getChatList(localStorage.token));
+		}
+	};
+
+	const deleteChat = async (id) => {
+		const res = await deleteChatById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			confirmDelete = false;
+
+			return null;
+		});
+
+		if (res) {
+			if ($chatId === id) {
+				goto('/');
+			}
+			await chats.set(await getChatList(localStorage.token));
+		}
+	};
+
+	const cloneChatHandler = async (id) => {
+		const res = await cloneChatById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			goto(`/c/${res.id}`);
+			await chats.set(await getChatList(localStorage.token));
+		}
+	};
+
+	const archiveChatHandler = async (id) => {
+		await archiveChatById(localStorage.token, id);
+		await chats.set(await getChatList(localStorage.token));
+	};
+
+	const focusEdit = async (node: HTMLInputElement) => {
+		node.focus();
+	};
+</script>
+
+<ShareChatModal bind:show={showShareChatModal} chatId={chat.id} />
+
+<div class=" w-full pr-2 relative group">
+	{#if confirmEdit}
+		<div
+			class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
+			confirmEdit ||
+			confirmDelete
+				? 'bg-gray-200 dark:bg-gray-900'
+				: selected
+				? 'bg-gray-100 dark:bg-gray-950'
+				: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
+		>
+			<input
+				use:focusEdit
+				bind:value={chatTitle}
+				class=" bg-transparent w-full outline-none mr-10"
+			/>
+		</div>
+	{:else}
+		<a
+			class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
+			confirmEdit ||
+			confirmDelete
+				? 'bg-gray-200 dark:bg-gray-900'
+				: selected
+				? 'bg-gray-100 dark:bg-gray-950'
+				: ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'}  whitespace-nowrap text-ellipsis"
+			href="/c/{chat.id}"
+			on:click={() => {
+				dispatch('select');
+
+				if ($mobile) {
+					showSidebar.set(false);
+				}
+			}}
+			on:dblclick={() => {
+				chatTitle = chat.title;
+				confirmEdit = true;
+			}}
+			on:mouseover={(e) => {
+				if (e.shiftKey) {
+					// Your code here
+					console.log('hi');
+				}
+			}}
+			on:focus={(e) => {}}
+			draggable="false"
+		>
+			<div class=" flex self-center flex-1 w-full">
+				<div class=" text-left self-center overflow-hidden w-full h-[20px]">
+					{chat.title}
+				</div>
+			</div>
+		</a>
+	{/if}
+
+	<div
+		class="
+
+        {chat.id === $chatId || confirmEdit || confirmDelete
+			? 'from-gray-200 dark:from-gray-900'
+			: selected
+			? 'from-gray-100 dark:from-gray-950'
+			: 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
+            absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
+
+              to-transparent"
+	>
+		{#if confirmEdit}
+			<div class="flex self-center space-x-1.5 z-10">
+				<button
+					class=" self-center dark:hover:text-white transition"
+					on:click={() => {
+						editChatTitle(chat.id, chatTitle);
+						confirmEdit = false;
+						chatTitle = '';
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</button>
+				<button
+					class=" self-center dark:hover:text-white transition"
+					on:click={() => {
+						confirmEdit = false;
+						chatTitle = '';
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+						/>
+					</svg>
+				</button>
+			</div>
+		{:else if confirmDelete}
+			<div class="flex self-center space-x-1.5 z-10">
+				<button
+					class=" self-center dark:hover:text-white transition"
+					on:click={() => {
+						deleteChat(chat.id);
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							fill-rule="evenodd"
+							d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
+							clip-rule="evenodd"
+						/>
+					</svg>
+				</button>
+				<button
+					class=" self-center dark:hover:text-white transition"
+					on:click={() => {
+						confirmDelete = false;
+					}}
+				>
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-4 h-4"
+					>
+						<path
+							d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+						/>
+					</svg>
+				</button>
+			</div>
+		{:else}
+			<div class="flex self-center space-x-1 z-10">
+				<ChatMenu
+					chatId={chat.id}
+					cloneChatHandler={() => {
+						cloneChatHandler(chat.id);
+					}}
+					shareHandler={() => {
+						showShareChatModal = true;
+					}}
+					archiveChatHandler={() => {
+						archiveChatHandler(chat.id);
+					}}
+					renameHandler={() => {
+						confirmEdit = true;
+					}}
+					deleteHandler={() => {
+						confirmDelete = true;
+					}}
+					onClose={() => {
+						selected = false;
+					}}
+				>
+					<button
+						aria-label="Chat Menu"
+						class=" self-center dark:hover:text-white transition"
+						on:click={() => {
+							dispatch('select');
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
+							/>
+						</svg>
+					</button>
+				</ChatMenu>
+
+				{#if chat.id === $chatId}
+					<!-- Shortcut support using "delete-chat-button" id -->
+					<button
+						id="delete-chat-button"
+						class="hidden"
+						on:click={() => {
+							confirmDelete = true;
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
+							/>
+						</svg>
+					</button>
+				{/if}
+			</div>
+		{/if}
+	</div>
+</div>