From bb642268c06ca7901f3f63ae088b98adf058cce4 Mon Sep 17 00:00:00 2001 From: leafyzito Date: Mon, 13 Apr 2026 01:39:19 +0000 Subject: [PATCH 01/18] feat(logs): add hourly message activity chart --- src/routes/logs/+page.svelte | 52 +++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 12ddcbe..847ceb2 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -311,6 +311,27 @@ return chatLogs.length.toLocaleString(); }); + const hourlyActivity = $derived.by(() => { + const counts = Array.from({ length: 24 }, () => 0); + for (const msg of chatLogs) { + counts[new Date(msg.timestamp).getHours()]++; + } + const maxCount = Math.max(1, ...counts); + return { counts, maxCount }; + }); + + // the busier the hour, the stronger the purple gets + const hourlyBarFill = (count: number, maxCount: number, resolvedMode: string | undefined) => { + if (count <= 0) return "transparent"; + const strength = Math.min(1, count / maxCount); + if (resolvedMode === "dark") { + const alpha = 0.2 + 0.72 * strength; + return `hsl(270 88% 68% / ${alpha})`; + } + const alpha = 0.14 + 0.8 * strength; + return `hsl(271 78% 46% / ${alpha})`; + }; + $effect(() => { if (!filteredChatLogs) return; untrack(async () => { @@ -882,9 +903,34 @@ - - - + + + +
+

Messages by hour for the selected day

+ +
+ {#if statsError}

{statsError}

{:else} From 57e8b21893b441d3f305746d0fbb3c943a16d253 Mon Sep 17 00:00:00 2001 From: leafyzito Date: Mon, 13 Apr 2026 01:55:45 +0000 Subject: [PATCH 02/18] rephrase --- src/routes/logs/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 847ceb2..0b9bda7 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -907,7 +907,7 @@
-

Messages by hour for the selected day

+

Messages by hour for the selected timeframe

+
+ + +
+
- - {#if loading} + + {#if searchQueryInput.trim() ? searchLoading : loading} {/if}
@@ -876,9 +994,9 @@
-
+
- + @@ -942,126 +1060,128 @@
- {#if dateContent} + {#if !isSearch && dateContent} {#if dateContent.day} - - span]:line-clamp-1", - }) - )} - > - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} - - - - - adjustDate(date)} - onValueChange={(date) => updateDateValue(date)} - isDateUnavailable={(date) => !isDateAvailable(date)} - bind:value={calendarDate} + + span]:line-clamp-1", + }) + )} > - {#snippet children({ months, weekdays })} - - { - if (!v || !calendarDate) return; - if (v === `${calendarDate?.year}`) return; - calendarDate = calendarDate.set({ year: Number.parseInt(v) }); - }} - > - - {defaultYear?.label ?? "Year"} - - - {#each yearOptions as { value, label } (value)} - - {/each} - - - { - if (!calendarDate) return; - if (v === `${calendarDate.month}`) return; - calendarDate = calendarDate.set({ month: Number.parseInt(v) }); - }} - > - - {monthLabel} - - - {#each monthOptions as { value, label } (value)} - - {/each} - - - - - {#each months as month (month)} - - - - {#each weekdays as weekday (weekday)} - - {weekday.slice(0, 2)} - - {/each} - - - - {#each month.weeks as weekDates (weekDates)} - - {#each weekDates as date (date)} - - - + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} + + + + + adjustDate(date)} + onValueChange={(date) => updateDateValue(date)} + isDateUnavailable={(date) => !isDateAvailable(date)} + bind:value={calendarDate} + > + {#snippet children({ months, weekdays })} + + { + if (!v || !calendarDate) return; + if (v === `${calendarDate?.year}`) return; + calendarDate = calendarDate.set({ year: Number.parseInt(v) }); + }} + > + + {defaultYear?.label ?? "Year"} + + + {#each yearOptions as { value, label } (value)} + + {/each} + + + { + if (!calendarDate) return; + if (v === `${calendarDate.month}`) return; + calendarDate = calendarDate.set({ month: Number.parseInt(v) }); + }} + > + + {monthLabel} + + + {#each monthOptions as { value, label } (value)} + + {/each} + + + + + {#each months as month (month)} + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + {/each} - {/each} - - + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + + + {/each} + + {/each} + + + {/each} + + {/snippet} + + + + {:else} +
+ + + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} + + + + {#each availableDates as date, index (index)} + {#if index > 0 && date.year !== availableDates[index - 1].year} + + {/if} + {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} + {/each} - - {/snippet} - - - - {:else} -
- - - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} - - - - {#each availableDates as date, index (index)} - {#if index > 0 && date.year !== availableDates[index - 1].year} - - {/if} - {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} - - {/each} - - - -
+
+
+
+
+ {/if} {/if} - {#if chatLogs.length} + + {#if baseLogs.length}
@@ -1095,32 +1215,39 @@ {/if} - + {#if isSearch || dateContent} + + {/if}
{/if} - {/if}
- {#if error} -

{error}

- {:else if chatLogs.length} + {#if activeError} +

{activeError}

+ {:else if activeLogs.length}
- +
- {@const msg = filteredChatLogs[index]} + {@const msg = activeLogs[index]} {@const msgId = getMessageId(msg)} {@const dayKey = dayjs(msg.timestamp).format("YYYY-MM-DD")} - {@const prevDayKey = index > 0 ? dayjs(filteredChatLogs[index - 1].timestamp).format("YYYY-MM-DD") : null} + {@const prevDayKey = index > 0 ? dayjs(activeLogs[index - 1].timestamp).format("YYYY-MM-DD") : null} {@const isNewDay = index > 0 && prevDayKey !== dayKey} {@const isHashMatch = msgId === page.url.hash.slice(1)} {@const isJumpMatch = isJumpSearching && !isHashMatch && jumpHighlights?.has(msgId)} @@ -1144,7 +1271,7 @@ {/if} {#if msg.tags["target-msg-id"]} - {@const msgDeleted = chatLogs.find((m) => m.id === msg.tags["target-msg-id"])} + {@const msgDeleted = activeLogs.find((m) => m.id === msg.tags["target-msg-id"])} {#if msgDeleted} From 878f5dc99ff55b2bb87cf536a32e8bd575ef4c49 Mon Sep 17 00:00:00 2001 From: leafyzito Date: Thu, 30 Apr 2026 14:45:14 +0000 Subject: [PATCH 05/18] refactor(logs): simplify query search mode --- src/routes/logs/+page.svelte | 159 +++++++++++++++-------------------- 1 file changed, 67 insertions(+), 92 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index c7380ab..153a09b 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -188,9 +188,9 @@ searchValue = q.get("s") || ""; isJumpMode = (q.get("sm") || window.localStorage.getItem("logs-search-mode")) === "jump"; - searchQueryInput = searchQuery = q.get("q") || ""; + queryInput = query = q.get("q") || ""; - if (searchQuery.trim()) { + if (query.trim()) { scrollFromBottom = false; window.localStorage.setItem("logs-bottom-scroll-state", "false"); } @@ -216,15 +216,11 @@ let channelStats = $state(null); let statsError = $state(null); - // Search - let searchQueryInput = $state(""); - let searchQuery = $state(""); - let searchLoading = $state(false); - let searchError = $state(null); - let searchLogs: Message[] = $state([]); - let searchController: AbortController | null = null; - - let isSearch = $derived(Boolean(searchQuery.trim())); + // Query mode + let queryInput = $state(""); + let query = $state(""); + let isQueryMode = $derived(Boolean(query.trim())); + let queryController: AbortController | null = null; // Emotes const channelEmotes = new SvelteMap(); @@ -243,21 +239,21 @@ d: dateValue, s: searchValue, sm: searchValue && isJumpMode ? "jump" : null, - q: isSearch ? searchQuery : null, + q: isQueryMode ? query : null, }; untrack(() => { - const query = page.url.searchParams; + const params = page.url.searchParams; for (const [key, value] of Object.entries(search)) { if (!value) { - query.delete(key); - } else if (value !== query.get(key)) { - query.set(key, value); + params.delete(key); + } else if (value !== params.get(key)) { + params.set(key, value); } } - if (!isJumpSearching && (isSearch ? searchLogs.length : chatLogs.length)) page.url.hash = ""; + if (!isJumpSearching && chatLogs.length) page.url.hash = ""; goto(page.url.search + page.url.hash, { replaceState: true, keepFocus: true }); }); @@ -315,27 +311,23 @@ let contentRef = $state(null); let searchValue = $state(""); - let baseLogs = $derived(isSearch ? searchLogs : chatLogs); - let searchResults = $derived(messageSearch(searchValue, baseLogs, scrollFromBottom)); - let filteredChatLogs = $derived(isJumpMode ? messageSearch("", baseLogs, scrollFromBottom) : searchResults); + let searchResults = $derived(messageSearch(searchValue, chatLogs, scrollFromBottom)); + let filteredChatLogs = $derived(isJumpMode ? messageSearch("", chatLogs, scrollFromBottom) : searchResults); let isJumpSearching = $derived(isJumpMode && searchResults.length && searchValue); let jumpHighlights = $derived(isJumpSearching ? new Set(searchResults.map((m) => getMessageId(m))) : void 0); let jumpIndex = $derived(isJumpSearching ? searchResults.findIndex((m) => getMessageId(m) === page.url.hash.slice(1)) : -1); let jumpInputValue = $state(1); - let activeLogs = $derived(filteredChatLogs); - let activeError = $derived(isSearch ? searchError : error); - let displayMessageCount = $derived.by(() => { if (searchValue && !isJumpMode) { - return `${searchResults.length.toLocaleString()} / ${baseLogs.length.toLocaleString()}`; + return `${searchResults.length.toLocaleString()} / ${chatLogs.length.toLocaleString()}`; } - return baseLogs.length.toLocaleString(); + return chatLogs.length.toLocaleString(); }); $effect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions - activeLogs; + filteredChatLogs; untrack(async () => { await tick(); const virtualList = document.querySelector(".virtual-list-wrapper"); @@ -374,7 +366,7 @@ $effect(() => { const id = page.url.hash.slice(1); if (!id) return; - const msgIdx = baseLogs.findIndex((m) => getMessageId(m) === id); + const msgIdx = chatLogs.findIndex((m) => getMessageId(m) === id); if (msgIdx === -1) return; // eslint-disable-next-line @typescript-eslint/no-unused-expressions scrollFromBottom; @@ -456,7 +448,7 @@ $effect(() => { // fetch available dates - if (isSearch) return; + if (isQueryMode) return; if (!channelName) return; untrack(async () => { availableDates = []; @@ -482,7 +474,7 @@ let logsController: AbortController | null = null; $effect(() => { // fetch logs - if (isSearch) return; + if (isQueryMode) return; const date = dateContent; if (!date) return; @@ -511,58 +503,58 @@ }); }); - const fetchSearch = async () => { + const fetchQueryResults = async () => { if (!channelName) { - searchError = "Channel is required"; + error = "Channel is required"; return; } if (!userName) { - searchError = "User is required"; + error = "User is required"; return; } - if (!searchQuery.trim()) { - searchError = "Query is required"; + if (!query.trim()) { + error = "Query is required"; return; } - searchError = null; - searchLoading = true; - searchLogs = []; + error = null; + loading = true; + chatLogs = []; - searchController?.abort(); - searchController = new AbortController(); + queryController?.abort(); + queryController = new AbortController(); try { - const qs = `jsonBasic=1&q=${encodeURIComponent(searchQuery)}`; + const qs = `jsonBasic=1&q=${encodeURIComponent(query)}`; const res = await fetch(`https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/search?${qs}`, { - signal: searchController.signal, + signal: queryController.signal, }); if (!res.ok) { - if (res.status === 404) searchError = "No results found"; - else searchError = `Error from server: ${res.status} ${res.statusText}`; + if (res.status === 404) error = "No results found"; + else error = `Error from server: ${res.status} ${res.statusText}`; return; } const data: { messages: Message[] } = await res.json(); - searchLogs = data.messages; + chatLogs = data.messages; if (!data.messages.length) { - searchError = "No results found"; + error = "No results found"; return; } channelId = data.messages.find((m) => m.tags["room-id"])?.tags["room-id"] ?? ""; } catch (err) { if ((err as { name?: string })?.name === "AbortError") return; - searchError = err instanceof Error ? err.message : "Failed to fetch search results"; + error = err instanceof Error ? err.message : "Failed to fetch search results"; } finally { - searchLoading = false; + loading = false; } }; $effect(() => { - if (!isSearch) return; - if (!channelName || !searchQuery) return; + if (!isQueryMode) return; + if (!channelName || !userName || !query) return; untrack(() => { - fetchSearch(); + fetchQueryResults(); }); }); @@ -714,45 +706,30 @@ const formSubmit = (event: SubmitEvent) => { event.preventDefault(); - const nextQuery = searchQueryInput.trim(); - - if (!nextQuery) { - if (loading || !inputChannelName) return; + if (loading || !inputChannelName) return; - searchController?.abort(); - searchError = null; - searchLogs = []; - searchQuery = ""; + channelName = inputChannelName; + userName = inputUserName; + query = queryInput.trim(); - // force reload - channelName = ""; - userName = ""; + if (query) { + scrollFromBottom = false; + window.localStorage.setItem("logs-bottom-scroll-state", "false"); availableDates = []; dateValue = ""; - channelName = inputChannelName; - userName = inputUserName; + logsController?.abort(); return; } - if (searchLoading || !inputChannelName) return; - - scrollFromBottom = false; - window.localStorage.setItem("logs-bottom-scroll-state", "false"); + queryController?.abort(); + // force reload channelName = ""; userName = ""; - channelId = ""; - error = null; - chatLogs = []; availableDates = []; dateValue = ""; - - searchError = null; - searchLogs = []; - searchQuery = nextQuery; - channelName = inputChannelName; userName = inputUserName; }; @@ -979,14 +956,12 @@
- +
- - {#if searchQueryInput.trim() ? searchLoading : loading} + + {#if loading} {/if}
@@ -1060,7 +1035,7 @@
- {#if !isSearch && dateContent} + {#if !isQueryMode && dateContent} {#if dateContent.day}
@@ -1215,15 +1190,15 @@ {/if} - {#if isSearch || dateContent} + {#if isQueryMode || dateContent}
- {#if activeError} -

{activeError}

- {:else if activeLogs.length} + {#if error} +

{error}

+ {:else if filteredChatLogs.length}
- +
- {@const msg = activeLogs[index]} + {@const msg = filteredChatLogs[index]} {@const msgId = getMessageId(msg)} {@const dayKey = dayjs(msg.timestamp).format("YYYY-MM-DD")} - {@const prevDayKey = index > 0 ? dayjs(activeLogs[index - 1].timestamp).format("YYYY-MM-DD") : null} + {@const prevDayKey = index > 0 ? dayjs(filteredChatLogs[index - 1].timestamp).format("YYYY-MM-DD") : null} {@const isNewDay = index > 0 && prevDayKey !== dayKey} {@const isHashMatch = msgId === page.url.hash.slice(1)} {@const isJumpMatch = isJumpSearching && !isHashMatch && jumpHighlights?.has(msgId)} @@ -1271,7 +1246,7 @@ {/if} {#if msg.tags["target-msg-id"]} - {@const msgDeleted = activeLogs.find((m) => m.id === msg.tags["target-msg-id"])} + {@const msgDeleted = chatLogs.find((m) => m.id === msg.tags["target-msg-id"])} {#if msgDeleted} From 67bc480a1542bc7bff408f408c9ae3b8b2c41d39 Mon Sep 17 00:00:00 2001 From: leafyzito Date: Thu, 30 Apr 2026 14:53:01 +0000 Subject: [PATCH 06/18] undo changes? --- src/routes/logs/+page.svelte | 234 +++++++++++++++++------------------ 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 153a09b..47f5dea 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -243,13 +243,13 @@ }; untrack(() => { - const params = page.url.searchParams; + const query = page.url.searchParams; for (const [key, value] of Object.entries(search)) { if (!value) { - params.delete(key); - } else if (value !== params.get(key)) { - params.set(key, value); + query.delete(key); + } else if (value !== query.get(key)) { + query.set(key, value); } } @@ -1037,124 +1037,124 @@
{#if !isQueryMode && dateContent} {#if dateContent.day} - - span]:line-clamp-1", - }) - )} + + span]:line-clamp-1", + }) + )} + > + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} + + + + + adjustDate(date)} + onValueChange={(date) => updateDateValue(date)} + isDateUnavailable={(date) => !isDateAvailable(date)} + bind:value={calendarDate} > - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} - - - - - adjustDate(date)} - onValueChange={(date) => updateDateValue(date)} - isDateUnavailable={(date) => !isDateAvailable(date)} - bind:value={calendarDate} - > - {#snippet children({ months, weekdays })} - - { - if (!v || !calendarDate) return; - if (v === `${calendarDate?.year}`) return; - calendarDate = calendarDate.set({ year: Number.parseInt(v) }); - }} - > - - {defaultYear?.label ?? "Year"} - - - {#each yearOptions as { value, label } (value)} - - {/each} - - - { - if (!calendarDate) return; - if (v === `${calendarDate.month}`) return; - calendarDate = calendarDate.set({ month: Number.parseInt(v) }); - }} - > - - {monthLabel} - - - {#each monthOptions as { value, label } (value)} - - {/each} - - - - - {#each months as month (month)} - - - - {#each weekdays as weekday (weekday)} - - {weekday.slice(0, 2)} - + {#snippet children({ months, weekdays })} + + { + if (!v || !calendarDate) return; + if (v === `${calendarDate?.year}`) return; + calendarDate = calendarDate.set({ year: Number.parseInt(v) }); + }} + > + + {defaultYear?.label ?? "Year"} + + + {#each yearOptions as { value, label } (value)} + + {/each} + + + { + if (!calendarDate) return; + if (v === `${calendarDate.month}`) return; + calendarDate = calendarDate.set({ month: Number.parseInt(v) }); + }} + > + + {monthLabel} + + + {#each monthOptions as { value, label } (value)} + + {/each} + + + + + {#each months as month (month)} + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + + {/each} - - - {#each month.weeks as weekDates (weekDates)} - - {#each weekDates as date (date)} - - - - {/each} - - {/each} - - - {/each} - - {/snippet} - - - - {:else} -
- - - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} - - - - {#each availableDates as date, index (index)} - {#if index > 0 && date.year !== availableDates[index - 1].year} - - {/if} - {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} - + {/each} + + {/each} - - - -
- {/if} + + {/snippet} + + + + {:else} +
+ + + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} + + + + {#each availableDates as date, index (index)} + {#if index > 0 && date.year !== availableDates[index - 1].year} + + {/if} + {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} + + {/each} + + + +
{/if} + {/if} {#if chatLogs.length}
From dd142389cff0f88459bf3cee6779c31051cbf84e Mon Sep 17 00:00:00 2001 From: leafyzito Date: Thu, 30 Apr 2026 16:03:22 +0000 Subject: [PATCH 07/18] more undos --- src/routes/logs/+page.svelte | 111 +++++++++++++++++------------------ 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 47f5dea..e868572 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -326,8 +326,7 @@ }); $effect(() => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - filteredChatLogs; + if (!filteredChatLogs) return; untrack(async () => { await tick(); const virtualList = document.querySelector(".virtual-list-wrapper"); @@ -969,9 +968,9 @@
-
+
- + @@ -1156,65 +1155,65 @@ {/if} {/if} - {#if chatLogs.length} -
-
-
- - - {displayMessageCount} - -
-
- {#if isJumpSearching} - {@const width = searchResults.length.toString().length + 5} -
- - / - -
+ {#if chatLogs.length} +
+
+
+ + + {displayMessageCount} + +
+
+ {#if isJumpSearching} + {@const width = searchResults.length.toString().length + 5} +
+ + / + +
+ {/if} +
+
+
-
- - - {#if isQueryMode || dateContent} - + +
- {/if} + + {#if isQueryMode || dateContent} + + {/if} +
+ {/if}
{#if error}

{error}

- {:else if filteredChatLogs.length} + {:else if chatLogs.length}
From b89eafe2122a2865066b6bab1da2c253ce8ae6e7 Mon Sep 17 00:00:00 2001 From: leafyzito Date: Thu, 30 Apr 2026 16:06:04 +0000 Subject: [PATCH 08/18] omgbruh --- src/routes/logs/+page.svelte | 331 ++++++++++++++++++----------------- 1 file changed, 166 insertions(+), 165 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index e868572..afa8cf2 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -1034,179 +1034,180 @@
- {#if !isQueryMode && dateContent} - {#if dateContent.day} - - span]:line-clamp-1", - }) - )} - > - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} - - - - - adjustDate(date)} - onValueChange={(date) => updateDateValue(date)} - isDateUnavailable={(date) => !isDateAvailable(date)} - bind:value={calendarDate} + {#if dateContent || isQueryMode} + {#if !isQueryMode && dateContent} + {#if dateContent.day} + + span]:line-clamp-1", + }) + )} > - {#snippet children({ months, weekdays })} - - { - if (!v || !calendarDate) return; - if (v === `${calendarDate?.year}`) return; - calendarDate = calendarDate.set({ year: Number.parseInt(v) }); - }} - > - - {defaultYear?.label ?? "Year"} - - - {#each yearOptions as { value, label } (value)} - - {/each} - - - { - if (!calendarDate) return; - if (v === `${calendarDate.month}`) return; - calendarDate = calendarDate.set({ month: Number.parseInt(v) }); - }} - > - - {monthLabel} - - - {#each monthOptions as { value, label } (value)} - - {/each} - - - - - {#each months as month (month)} - - - - {#each weekdays as weekday (weekday)} - - {weekday.slice(0, 2)} - - {/each} - - - - {#each month.weeks as weekDates (weekDates)} - - {#each weekDates as date (date)} - - - + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} + + + + + adjustDate(date)} + onValueChange={(date) => updateDateValue(date)} + isDateUnavailable={(date) => !isDateAvailable(date)} + bind:value={calendarDate} + > + {#snippet children({ months, weekdays })} + + { + if (!v || !calendarDate) return; + if (v === `${calendarDate?.year}`) return; + calendarDate = calendarDate.set({ year: Number.parseInt(v) }); + }} + > + + {defaultYear?.label ?? "Year"} + + + {#each yearOptions as { value, label } (value)} + + {/each} + + + { + if (!calendarDate) return; + if (v === `${calendarDate.month}`) return; + calendarDate = calendarDate.set({ month: Number.parseInt(v) }); + }} + > + + {monthLabel} + + + {#each monthOptions as { value, label } (value)} + + {/each} + + + + + {#each months as month (month)} + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + {/each} - {/each} - - + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + + + {/each} + + {/each} + + + {/each} + + {/snippet} + + + + {:else} +
+ + + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} + + + + {#each availableDates as date, index (index)} + {#if index > 0 && date.year !== availableDates[index - 1].year} + + {/if} + {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} + {/each} - - {/snippet} - - - - {:else} -
- - - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} - - - - {#each availableDates as date, index (index)} - {#if index > 0 && date.year !== availableDates[index - 1].year} - - {/if} - {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} - - {/each} - - - -
- {/if} - {/if} - - {#if chatLogs.length} -
-
-
- - - {displayMessageCount} - -
-
- {#if isJumpSearching} - {@const width = searchResults.length.toString().length + 5} -
- - / - + + +
{/if} -
-
- - - {#if isQueryMode || dateContent} -
+
+ - {/if} -
+ + {#if isQueryMode || dateContent} + + {/if} +
+ {/if} {/if}
From 767c91c1b5a52387f47cdcd1f65d9c274aea6b6e Mon Sep 17 00:00:00 2001 From: leafyzito Date: Thu, 30 Apr 2026 16:06:41 +0000 Subject: [PATCH 09/18] Revert "omgbruh" This reverts commit b89eafe2122a2865066b6bab1da2c253ce8ae6e7. --- src/routes/logs/+page.svelte | 331 +++++++++++++++++------------------ 1 file changed, 165 insertions(+), 166 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index afa8cf2..e868572 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -1034,180 +1034,179 @@
- {#if dateContent || isQueryMode} - {#if !isQueryMode && dateContent} - {#if dateContent.day} - - span]:line-clamp-1", - }) - )} + {#if !isQueryMode && dateContent} + {#if dateContent.day} + + span]:line-clamp-1", + }) + )} + > + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} + + + + + adjustDate(date)} + onValueChange={(date) => updateDateValue(date)} + isDateUnavailable={(date) => !isDateAvailable(date)} + bind:value={calendarDate} > - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} - - - - - adjustDate(date)} - onValueChange={(date) => updateDateValue(date)} - isDateUnavailable={(date) => !isDateAvailable(date)} - bind:value={calendarDate} - > - {#snippet children({ months, weekdays })} - - { - if (!v || !calendarDate) return; - if (v === `${calendarDate?.year}`) return; - calendarDate = calendarDate.set({ year: Number.parseInt(v) }); - }} - > - - {defaultYear?.label ?? "Year"} - - - {#each yearOptions as { value, label } (value)} - - {/each} - - - { - if (!calendarDate) return; - if (v === `${calendarDate.month}`) return; - calendarDate = calendarDate.set({ month: Number.parseInt(v) }); - }} - > - - {monthLabel} - - - {#each monthOptions as { value, label } (value)} - - {/each} - - - - - {#each months as month (month)} - - - - {#each weekdays as weekday (weekday)} - - {weekday.slice(0, 2)} - + {#snippet children({ months, weekdays })} + + { + if (!v || !calendarDate) return; + if (v === `${calendarDate?.year}`) return; + calendarDate = calendarDate.set({ year: Number.parseInt(v) }); + }} + > + + {defaultYear?.label ?? "Year"} + + + {#each yearOptions as { value, label } (value)} + + {/each} + + + { + if (!calendarDate) return; + if (v === `${calendarDate.month}`) return; + calendarDate = calendarDate.set({ month: Number.parseInt(v) }); + }} + > + + {monthLabel} + + + {#each monthOptions as { value, label } (value)} + + {/each} + + + + + {#each months as month (month)} + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + + {/each} - - - {#each month.weeks as weekDates (weekDates)} - - {#each weekDates as date (date)} - - - - {/each} - - {/each} - - - {/each} - - {/snippet} - - - - {:else} -
- - - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} - - - - {#each availableDates as date, index (index)} - {#if index > 0 && date.year !== availableDates[index - 1].year} - - {/if} - {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} - + {/each} + + {/each} - - - + + {/snippet} + + + + {:else} +
+ + + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} + + + + {#each availableDates as date, index (index)} + {#if index > 0 && date.year !== availableDates[index - 1].year} + + {/if} + {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} + + {/each} + + + +
+ {/if} + {/if} + + {#if chatLogs.length} +
+
+
+ + + {displayMessageCount} + +
+
+ {#if isJumpSearching} + {@const width = searchResults.length.toString().length + 5} +
+ + / +
{/if} - {/if} - {#if chatLogs.length} -
-
-
- - - {displayMessageCount} - -
-
- {#if isJumpSearching} - {@const width = searchResults.length.toString().length + 5} -
- - / - -
+
+
+
-
- - - {#if isQueryMode || dateContent} - + +
- {/if} + + {#if isQueryMode || dateContent} + + {/if} +
{/if}
From 37a8230d1aa7d51e76b6dc2761b4009ddf497dc0 Mon Sep 17 00:00:00 2001 From: leafyzito Date: Thu, 30 Apr 2026 16:10:23 +0000 Subject: [PATCH 10/18] refactor(logs): minimize query mode diff Keep the logs toolbar structure unchanged and gate only the date UI for query mode. Made-with: Cursor --- src/routes/logs/+page.svelte | 108 ++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index e868572..fac5c4d 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -1034,8 +1034,9 @@
- {#if !isQueryMode && dateContent} - {#if dateContent.day} + {#if dateContent || isQueryMode} + {#if !isQueryMode && dateContent} + {#if dateContent.day}
{/if} - {/if} + {/if} - {#if chatLogs.length} -
-
-
- - - {displayMessageCount} - -
-
- {#if isJumpSearching} - {@const width = searchResults.length.toString().length + 5} -
- - / - -
- {/if} -
-
- - - {#if isQueryMode || dateContent} -
+
+ - {/if} -
+ + {#if isQueryMode || dateContent} + + {/if} +
+ {/if} {/if}
From 18f6581e88750d5d98b4550b6e2c770a59880cdc Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 19:30:02 +0300 Subject: [PATCH 11/18] chore: prettier --- src/routes/logs/+page.svelte | 238 +++++++++++++++++------------------ 1 file changed, 118 insertions(+), 120 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index fac5c4d..2a85de5 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -1037,123 +1037,123 @@ {#if dateContent || isQueryMode} {#if !isQueryMode && dateContent} {#if dateContent.day} - - span]:line-clamp-1", - }) - )} - > - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} - - - - - adjustDate(date)} - onValueChange={(date) => updateDateValue(date)} - isDateUnavailable={(date) => !isDateAvailable(date)} - bind:value={calendarDate} + + span]:line-clamp-1", + }) + )} > - {#snippet children({ months, weekdays })} - - { - if (!v || !calendarDate) return; - if (v === `${calendarDate?.year}`) return; - calendarDate = calendarDate.set({ year: Number.parseInt(v) }); - }} - > - - {defaultYear?.label ?? "Year"} - - - {#each yearOptions as { value, label } (value)} - - {/each} - - - { - if (!calendarDate) return; - if (v === `${calendarDate.month}`) return; - calendarDate = calendarDate.set({ month: Number.parseInt(v) }); - }} - > - - {monthLabel} - - - {#each monthOptions as { value, label } (value)} - - {/each} - - - - - {#each months as month (month)} - - - - {#each weekdays as weekday (weekday)} - - {weekday.slice(0, 2)} - - {/each} - - - - {#each month.weeks as weekDates (weekDates)} - - {#each weekDates as date (date)} - - - + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} + + + + + adjustDate(date)} + onValueChange={(date) => updateDateValue(date)} + isDateUnavailable={(date) => !isDateAvailable(date)} + bind:value={calendarDate} + > + {#snippet children({ months, weekdays })} + + { + if (!v || !calendarDate) return; + if (v === `${calendarDate?.year}`) return; + calendarDate = calendarDate.set({ year: Number.parseInt(v) }); + }} + > + + {defaultYear?.label ?? "Year"} + + + {#each yearOptions as { value, label } (value)} + + {/each} + + + { + if (!calendarDate) return; + if (v === `${calendarDate.month}`) return; + calendarDate = calendarDate.set({ month: Number.parseInt(v) }); + }} + > + + {monthLabel} + + + {#each monthOptions as { value, label } (value)} + + {/each} + + + + + {#each months as month (month)} + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + {/each} - {/each} - - + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + + + {/each} + + {/each} + + + {/each} + + {/snippet} + + + + {:else} +
+ + + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} + + + + {#each availableDates as date, index (index)} + {#if index > 0 && date.year !== availableDates[index - 1].year} + + {/if} + {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} + {/each} - - {/snippet} - - - - {:else} -
- - - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} - - - - {#each availableDates as date, index (index)} - {#if index > 0 && date.year !== availableDates[index - 1].year} - - {/if} - {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} - - {/each} - - - -
- {/if} +
+
+
+
+ {/if} {/if} {#if chatLogs.length} @@ -1196,13 +1196,11 @@ size="icon" class="size-8 border" target="_blank" - href={ - isQueryMode - ? `https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/search?jsonBasic=1&q=${encodeURIComponent(query)}` - : dateContent - ? `https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/${dateContent.year}/${dateContent.month}${dateContent.day ? `/${dateContent.day}` : ""}` - : "" - } + href={isQueryMode + ? `https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/search?jsonBasic=1&q=${encodeURIComponent(query)}` + : dateContent + ? `https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/${dateContent.year}/${dateContent.month}${dateContent.day ? `/${dateContent.day}` : ""}` + : ""} > From d3903d1a7c64436924a969a274d163ff31350e94 Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 19:31:17 +0300 Subject: [PATCH 12/18] chore: queryInput -> inputQuery --- src/routes/logs/+page.svelte | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 2a85de5..e8e5ea0 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -188,7 +188,7 @@ searchValue = q.get("s") || ""; isJumpMode = (q.get("sm") || window.localStorage.getItem("logs-search-mode")) === "jump"; - queryInput = query = q.get("q") || ""; + inputQuery = query = q.get("q") || ""; if (query.trim()) { scrollFromBottom = false; @@ -217,7 +217,7 @@ let statsError = $state(null); // Query mode - let queryInput = $state(""); + let inputQuery = $state(""); let query = $state(""); let isQueryMode = $derived(Boolean(query.trim())); let queryController: AbortController | null = null; @@ -709,7 +709,7 @@ channelName = inputChannelName; userName = inputUserName; - query = queryInput.trim(); + query = inputQuery.trim(); if (query) { scrollFromBottom = false; @@ -955,7 +955,7 @@
- +
From 14ff0650b7f6d3e594824c1c452659fce74d15cb Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 19:33:40 +0300 Subject: [PATCH 13/18] tweak: keep scrollFromBottom state --- src/routes/logs/+page.svelte | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index e8e5ea0..9cb3a5e 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -184,16 +184,10 @@ inputChannelName = channelName = q.get("c") || ""; inputUserName = userName = q.get("u") || ""; + inputQuery = query = q.get("q") || ""; dateValue = q.get("d") || ""; searchValue = q.get("s") || ""; isJumpMode = (q.get("sm") || window.localStorage.getItem("logs-search-mode")) === "jump"; - - inputQuery = query = q.get("q") || ""; - - if (query.trim()) { - scrollFromBottom = false; - window.localStorage.setItem("logs-bottom-scroll-state", "false"); - } }); onDestroy(() => { @@ -712,9 +706,6 @@ query = inputQuery.trim(); if (query) { - scrollFromBottom = false; - window.localStorage.setItem("logs-bottom-scroll-state", "false"); - availableDates = []; dateValue = ""; logsController?.abort(); From f3d4c77e6f311737f1d567e82f939fbc449905d8 Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 19:44:14 +0300 Subject: [PATCH 14/18] fix: only set query if userName provided --- src/routes/logs/+page.svelte | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 9cb3a5e..06ec90a 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -703,13 +703,15 @@ channelName = inputChannelName; userName = inputUserName; - query = inputQuery.trim(); - if (query) { - availableDates = []; - dateValue = ""; - logsController?.abort(); - return; + if (userName) { + query = inputQuery.trim(); + if (query) { + availableDates = []; + dateValue = ""; + logsController?.abort(); + return; + } } queryController?.abort(); From 4146ba63842bb287681dc8a69a151bf7e93ff8f3 Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 20:24:58 +0300 Subject: [PATCH 15/18] refactor: merge fetchQueryResults function and cleanup logic --- src/routes/logs/+page.svelte | 104 ++++++++--------------------------- 1 file changed, 23 insertions(+), 81 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 06ec90a..b32b329 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -214,7 +214,6 @@ let inputQuery = $state(""); let query = $state(""); let isQueryMode = $derived(Boolean(query.trim())); - let queryController: AbortController | null = null; // Emotes const channelEmotes = new SvelteMap(); @@ -441,12 +440,8 @@ $effect(() => { // fetch available dates - if (isQueryMode) return; - if (!channelName) return; + if (isQueryMode || !channelName) return; untrack(async () => { - availableDates = []; - chatLogs = []; - channelStats = null; loading = true; const res = await fetch(`https://logs.zonian.dev/list?${parseChannelUser(channelName, userName, true)}`); @@ -467,9 +462,8 @@ let logsController: AbortController | null = null; $effect(() => { // fetch logs - if (isQueryMode) return; const date = dateContent; - if (!date) return; + if (!date && !query) return; untrack(async () => { error = null; @@ -478,11 +472,21 @@ logsController?.abort(); logsController = new AbortController(); - const res = await fetch(`https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/${date.year}/${date.month}${date.day ? `/${date.day}` : ""}?jsonBasic=1`, { - signal: logsController.signal, + const queryParams = new URLSearchParams({ + jsonBasic: "1", + q: isQueryMode ? query : "", }); + const res = await fetch( + `https://logs.zonian.dev/ + ${parseChannelUser(channelName, userName, false)} + ${date ? `/${date.year}/${date.month}${date.day ? `/${date.day}` : ""}` : "/search"} + ?${queryParams}`, + { + signal: logsController.signal, + } + ); if (!res.ok) { - if (res.status === 404) error = "No logs found for this date"; + if (res.status === 404) error = "No results found"; else error = `Error from server: ${res.status} ${res.statusText}`; loading = false; throw error; @@ -496,61 +500,6 @@ }); }); - const fetchQueryResults = async () => { - if (!channelName) { - error = "Channel is required"; - return; - } - if (!userName) { - error = "User is required"; - return; - } - if (!query.trim()) { - error = "Query is required"; - return; - } - - error = null; - loading = true; - chatLogs = []; - - queryController?.abort(); - queryController = new AbortController(); - - try { - const qs = `jsonBasic=1&q=${encodeURIComponent(query)}`; - const res = await fetch(`https://logs.zonian.dev/${parseChannelUser(channelName, userName, false)}/search?${qs}`, { - signal: queryController.signal, - }); - if (!res.ok) { - if (res.status === 404) error = "No results found"; - else error = `Error from server: ${res.status} ${res.statusText}`; - return; - } - - const data: { messages: Message[] } = await res.json(); - chatLogs = data.messages; - if (!data.messages.length) { - error = "No results found"; - return; - } - channelId = data.messages.find((m) => m.tags["room-id"])?.tags["room-id"] ?? ""; - } catch (err) { - if ((err as { name?: string })?.name === "AbortError") return; - error = err instanceof Error ? err.message : "Failed to fetch search results"; - } finally { - loading = false; - } - }; - - $effect(() => { - if (!isQueryMode) return; - if (!channelName || !userName || !query) return; - untrack(() => { - fetchQueryResults(); - }); - }); - $effect(() => { // fetch badges channelBadges.clear(); @@ -701,29 +650,22 @@ event.preventDefault(); if (loading || !inputChannelName) return; - channelName = inputChannelName; - userName = inputUserName; - - if (userName) { - query = inputQuery.trim(); - if (query) { - availableDates = []; - dateValue = ""; - logsController?.abort(); - return; - } - } - - queryController?.abort(); - // force reload channelName = ""; userName = ""; + query = ""; + // reset availableDates = []; dateValue = ""; + chatLogs = []; + channelStats = null; + channelName = inputChannelName; userName = inputUserName; + if (userName) { + query = inputQuery.trim(); + } }; const selectResult = (index: number) => { From 4f872ddddea4801434934fae8bfa68a15c3fdc25 Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 20:46:35 +0300 Subject: [PATCH 16/18] refactor: ui nested logic insanity --- src/routes/logs/+page.svelte | 322 +++++++++++++++++------------------ 1 file changed, 157 insertions(+), 165 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index b32b329..42f5e66 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -969,179 +969,171 @@
- {#if dateContent || isQueryMode} - {#if !isQueryMode && dateContent} - {#if dateContent.day} - - span]:line-clamp-1", - }) - )} + {#if dateContent} + {#if dateContent.day} + + span]:line-clamp-1", + }) + )} + > + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} + + + + + adjustDate(date)} + onValueChange={(date) => updateDateValue(date)} + isDateUnavailable={(date) => !isDateAvailable(date)} + bind:value={calendarDate} > - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}-{String(dateContent.day).padStart(2, "0")} - - - - - adjustDate(date)} - onValueChange={(date) => updateDateValue(date)} - isDateUnavailable={(date) => !isDateAvailable(date)} - bind:value={calendarDate} - > - {#snippet children({ months, weekdays })} - - { - if (!v || !calendarDate) return; - if (v === `${calendarDate?.year}`) return; - calendarDate = calendarDate.set({ year: Number.parseInt(v) }); - }} - > - - {defaultYear?.label ?? "Year"} - - - {#each yearOptions as { value, label } (value)} - - {/each} - - - { - if (!calendarDate) return; - if (v === `${calendarDate.month}`) return; - calendarDate = calendarDate.set({ month: Number.parseInt(v) }); - }} - > - - {monthLabel} - - - {#each monthOptions as { value, label } (value)} - - {/each} - - - - - {#each months as month (month)} - - - - {#each weekdays as weekday (weekday)} - - {weekday.slice(0, 2)} - + {#snippet children({ months, weekdays })} + + { + if (!v || !calendarDate) return; + if (v === `${calendarDate?.year}`) return; + calendarDate = calendarDate.set({ year: Number.parseInt(v) }); + }} + > + + {defaultYear?.label ?? "Year"} + + + {#each yearOptions as { value, label } (value)} + + {/each} + + + { + if (!calendarDate) return; + if (v === `${calendarDate.month}`) return; + calendarDate = calendarDate.set({ month: Number.parseInt(v) }); + }} + > + + {monthLabel} + + + {#each monthOptions as { value, label } (value)} + + {/each} + + + + + {#each months as month (month)} + + + + {#each weekdays as weekday (weekday)} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates (weekDates)} + + {#each weekDates as date (date)} + + + {/each} - - - {#each month.weeks as weekDates (weekDates)} - - {#each weekDates as date (date)} - - - - {/each} - - {/each} - - - {/each} - - {/snippet} - - - - {:else} -
- - - {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} - - - - {#each availableDates as date, index (index)} - {#if index > 0 && date.year !== availableDates[index - 1].year} - - {/if} - {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} - + {/each} + + {/each} - - - + + {/snippet} + + + + {:else} +
+ + + {dateContent.year}-{String(dateContent.month).padStart(2, "0")}{dateContent.day ? `-${String(dateContent.day).padStart(2, "0")}` : ""} + + + + {#each availableDates as date, index (index)} + {#if index > 0 && date.year !== availableDates[index - 1].year} + + {/if} + {@const str = `${date.year}-${date.month.padStart(2, "0")}${date.day ? `-${date.day.padStart(2, "0")}` : ""}`} + + {/each} + + + +
+ {/if} + {/if} + {#if chatLogs.length} +
+
+
+ + + {displayMessageCount} + +
+
+ {#if isJumpSearching} + {@const width = searchResults.length.toString().length + 5} +
+ + / +
{/if} - {/if} - - {#if chatLogs.length} -
-
-
- - - {displayMessageCount} - -
-
- {#if isJumpSearching} - {@const width = searchResults.length.toString().length + 5} -
- - / - -
+
+
+
-
- - - {#if isQueryMode || dateContent} - + +
- {/if} + + +
{/if}
From 9b82fe9977fc63bee42e4e52c0d71143f5268132 Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 21:02:32 +0300 Subject: [PATCH 17/18] feat: error if no user provided on query mode --- src/routes/logs/+page.svelte | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index 42f5e66..e180836 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -661,11 +661,14 @@ chatLogs = []; channelStats = null; + if (inputQuery.trim() && !inputUserName) { + error = "User is required for query mode"; + return; + } + channelName = inputChannelName; userName = inputUserName; - if (userName) { - query = inputQuery.trim(); - } + query = inputQuery.trim(); }; const selectResult = (index: number) => { @@ -884,7 +887,9 @@
- +
From e65f93ed884603622ebf7bfef275647dec6d15f1 Mon Sep 17 00:00:00 2001 From: Supa Date: Thu, 30 Apr 2026 21:11:03 +0300 Subject: [PATCH 18/18] tweak: placeholders --- src/routes/logs/+page.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/logs/+page.svelte b/src/routes/logs/+page.svelte index e180836..7b98c3d 100644 --- a/src/routes/logs/+page.svelte +++ b/src/routes/logs/+page.svelte @@ -662,7 +662,7 @@ channelStats = null; if (inputQuery.trim() && !inputUserName) { - error = "User is required for query mode"; + error = "User is required for global search"; return; } @@ -863,7 +863,7 @@ - + {#if foundChannels.length && foundChannels[0].target !== inputChannelName.toLowerCase()}
@@ -890,12 +890,12 @@ - +
- +
@@ -1098,7 +1098,7 @@
- + {displayMessageCount}