|
1 | 1 | <script lang="ts"> |
2 | | - import { getContext, onMount } from 'svelte'; |
3 | | - const i18n = getContext('i18n'); |
| 2 | + import { getContext } from 'svelte'; |
| 3 | + import type { Writable } from 'svelte/store'; |
| 4 | +
|
| 5 | + const i18n: Writable<any> = getContext('i18n'); |
4 | 6 |
|
5 | 7 | import dayjs from 'dayjs'; |
6 | 8 | import localizedFormat from 'dayjs/plugin/localizedFormat'; |
7 | 9 | import { getTimeRange } from '$lib/utils'; |
8 | 10 | import ChevronUp from '$lib/components/icons/ChevronUp.svelte'; |
9 | 11 | import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; |
10 | | - import Loader from '$lib/components/common/Loader.svelte'; |
11 | | - import Spinner from '$lib/components/common/Spinner.svelte'; |
| 12 | + import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte'; |
| 13 | + import ChevronRight from '$lib/components/icons/ChevronRight.svelte'; |
12 | 14 | import Tooltip from '$lib/components/common/Tooltip.svelte'; |
13 | 15 |
|
14 | 16 | dayjs.extend(localizedFormat); |
15 | 17 |
|
16 | | - export let chats = []; |
| 18 | + export let chats: any[] = []; |
17 | 19 |
|
18 | 20 | export let chatListLoading = false; |
19 | | - export let allChatsLoaded = false; |
20 | | -
|
21 | | - export let loadHandler: Function = null; |
22 | 21 | export let showOwnerInfo = false; |
23 | | -
|
24 | | - let chatList = null; |
| 22 | + export let page = 1; |
| 23 | + export let total = 0; |
| 24 | + export let perPage = 10; |
| 25 | + export let orderBy: 'title' | 'updated_at' = 'updated_at'; |
| 26 | + export let direction: 'asc' | 'desc' = 'desc'; |
| 27 | + export let onPageChange: Function = () => {}; |
| 28 | + export let onSort: Function = () => {}; |
| 29 | +
|
| 30 | + let chatList: any[] | null = null; |
| 31 | + let totalPages = 1; |
| 32 | + let pages: (number | 'ellipsis')[] = []; |
25 | 33 |
|
26 | 34 | const init = async () => { |
27 | 35 | if (chats.length === 0) { |
|
31 | 39 | ...chat, |
32 | 40 | time_range: getTimeRange(chat.updated_at) |
33 | 41 | })); |
34 | | -
|
35 | | - chatList.sort((a, b) => { |
36 | | - if (direction === 'asc') { |
37 | | - return a[orderBy] > b[orderBy] ? 1 : -1; |
38 | | - } else { |
39 | | - return a[orderBy] < b[orderBy] ? 1 : -1; |
40 | | - } |
41 | | - }); |
42 | 42 | } |
43 | 43 | }; |
44 | 44 |
|
45 | | - const setSortKey = (key) => { |
46 | | - if (orderBy === key) { |
47 | | - direction = direction === 'asc' ? 'desc' : 'asc'; |
48 | | - } else { |
49 | | - orderBy = key; |
50 | | - direction = 'asc'; |
| 45 | + const setSortKey = (key: 'title' | 'updated_at') => { |
| 46 | + onSort(key); |
| 47 | + }; |
| 48 | +
|
| 49 | + const buildPages = (currentPage: number, pageCount: number): (number | 'ellipsis')[] => { |
| 50 | + if (pageCount <= 7) { |
| 51 | + return Array.from({ length: pageCount }, (_, i) => i + 1); |
51 | 52 | } |
52 | 53 |
|
53 | | - init(); |
54 | | - }; |
| 54 | + const items: (number | 'ellipsis')[] = [1]; |
| 55 | + if (currentPage > 3) { |
| 56 | + items.push('ellipsis'); |
| 57 | + } |
55 | 58 |
|
56 | | - let orderBy = 'updated_at'; |
57 | | - let direction = 'desc'; // 'asc' or 'desc' |
| 59 | + const start = Math.max(2, currentPage - 1); |
| 60 | + const end = Math.min(pageCount - 1, currentPage + 1); |
| 61 | + for (let i = start; i <= end; i++) { |
| 62 | + items.push(i); |
| 63 | + } |
| 64 | +
|
| 65 | + if (currentPage < pageCount - 2) { |
| 66 | + items.push('ellipsis'); |
| 67 | + } |
| 68 | + items.push(pageCount); |
| 69 | +
|
| 70 | + return items; |
| 71 | + }; |
58 | 72 |
|
59 | 73 | $: if (chats) { |
60 | 74 | init(); |
61 | 75 | } |
| 76 | +
|
| 77 | + $: totalPages = Math.max(1, Math.ceil(total / perPage)); |
| 78 | + $: pages = buildPages(page, totalPages); |
62 | 79 | </script> |
63 | 80 |
|
64 | 81 | {#if chatList} |
|
153 | 170 | class=" w-full flex justify-between items-center rounded-lg text-sm py-2 px-3 hover:bg-gray-50 dark:hover:bg-gray-850" |
154 | 171 | draggable="false" |
155 | 172 | href={`/c/${chat.id}`} |
156 | | - on:click={() => (show = false)} |
157 | 173 | > |
158 | 174 | <div class="text-ellipsis line-clamp-1 w-full sm:basis-3/5"> |
159 | 175 | {chat?.title} |
|
177 | 193 | </a> |
178 | 194 | {/each} |
179 | 195 |
|
180 | | - {#if !allChatsLoaded && loadHandler} |
181 | | - <Loader |
182 | | - on:visible={(e) => { |
183 | | - if (!chatListLoading) { |
184 | | - loadHandler(); |
185 | | - } |
186 | | - }} |
187 | | - > |
188 | | - <div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"> |
189 | | - <Spinner className=" size-4" /> |
190 | | - <div class=" ">Loading...</div> |
191 | | - </div> |
192 | | - </Loader> |
| 196 | + {#if totalPages > 1} |
| 197 | + <div class="flex items-center justify-center gap-1.5 pt-2"> |
| 198 | + <button |
| 199 | + class="inline-flex size-7 items-center justify-center rounded-lg text-xs font-medium text-gray-500 transition hover:bg-gray-50 hover:text-gray-700 disabled:cursor-not-allowed disabled:opacity-25 dark:text-gray-500 dark:hover:bg-gray-850 dark:hover:text-gray-300" |
| 200 | + disabled={chatListLoading || page <= 1} |
| 201 | + aria-label={$i18n.t('Previous page')} |
| 202 | + on:click={() => onPageChange(page - 1)} |
| 203 | + > |
| 204 | + <ChevronLeft className="size-3.5" strokeWidth="2" /> |
| 205 | + </button> |
| 206 | + |
| 207 | + {#each pages as item, index (item === 'ellipsis' ? `ellipsis-${index}` : item)} |
| 208 | + {#if item === 'ellipsis'} |
| 209 | + <span |
| 210 | + class="inline-flex size-7 items-center justify-center text-xs text-gray-400 select-none dark:text-gray-600" |
| 211 | + > |
| 212 | + ... |
| 213 | + </span> |
| 214 | + {:else} |
| 215 | + <button |
| 216 | + class="inline-flex size-7 items-center justify-center rounded-lg text-xs font-medium transition hover:bg-gray-50 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300 {item === |
| 217 | + page |
| 218 | + ? 'bg-gray-50 text-gray-800 dark:bg-gray-850 dark:text-gray-100' |
| 219 | + : 'text-gray-500 dark:text-gray-500'}" |
| 220 | + aria-label={`Page ${item}`} |
| 221 | + aria-current={item === page ? 'page' : undefined} |
| 222 | + on:click={() => onPageChange(item)} |
| 223 | + > |
| 224 | + {item} |
| 225 | + </button> |
| 226 | + {/if} |
| 227 | + {/each} |
| 228 | + |
| 229 | + <button |
| 230 | + class="inline-flex size-7 items-center justify-center rounded-lg text-xs font-medium text-gray-500 transition hover:bg-gray-50 hover:text-gray-700 disabled:cursor-not-allowed disabled:opacity-25 dark:text-gray-500 dark:hover:bg-gray-850 dark:hover:text-gray-300" |
| 231 | + disabled={chatListLoading || page >= totalPages} |
| 232 | + aria-label={$i18n.t('Next page')} |
| 233 | + on:click={() => onPageChange(page + 1)} |
| 234 | + > |
| 235 | + <ChevronRight className="size-3.5" strokeWidth="2" /> |
| 236 | + </button> |
| 237 | + </div> |
193 | 238 | {/if} |
194 | 239 | </div> |
195 | 240 | {/if} |
0 commit comments