feat: search messages inside a channel from its header

Channels had no way to search their own messages short of opening the global search and picking the channel by hand. The channel header now has a search button that opens the search window already limited to that channel and to messages. Clicking a result opens the message in its thread and highlights it, using the same jump the global search already uses.
This commit is contained in:
Classic298 2026-11-03 19:04:45 +01:00
parent 8957ac1397
commit 0c9147a6ec
3 changed files with 31 additions and 2 deletions

View file

@ -2,7 +2,7 @@
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { mobile, showSidebar, user } from '$lib/stores';
import { mobile, searchPreset, showSearch, showSidebar, user } from '$lib/stores';
import { slide } from 'svelte/transition';
import { page } from '$app/stores';
@ -18,6 +18,7 @@
import ChannelInfoModal from './ChannelInfoModal.svelte';
import Users from '../icons/Users.svelte';
import Pin from '../icons/Pin.svelte';
import Search from '../icons/Search.svelte';
import PinnedMessagesModal from './PinnedMessagesModal.svelte';
import ChannelMenu from './ChannelMenu.svelte';
@ -162,6 +163,22 @@
>
<slot name="actions">
{#if channel}
<Tooltip content={$i18n.t('Search')}>
<button
class=" flex size-7 items-center justify-center cursor-pointer rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition-colors"
aria-label={$i18n.t('Search')}
type="button"
on:click={() => {
searchPreset.set({ kind: 'message', scope: `channel:${channel.id}` });
showSearch.set(true);
}}
>
<div class=" flex items-center gap-1 m-auto self-center shrink-0">
<Search className=" size-4" strokeWidth="1.5" />
</div>
</button>
</Tooltip>
<Tooltip content={$i18n.t('Pinned Messages')}>
<button
class=" flex size-7 items-center justify-center cursor-pointer rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition-colors"

View file

@ -2,7 +2,7 @@
import { getContext, onDestroy, tick } from 'svelte';
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
import { folders, mobile, settings, tags } from '$lib/stores';
import { folders, mobile, searchPreset, settings, tags } from '$lib/stores';
import DropdownMenu from '$lib/components/common/DropdownMenu.svelte';
import DropdownSub from '$lib/components/common/DropdownSub.svelte';
import Check from '$lib/components/icons/Check.svelte';
@ -96,6 +96,16 @@
? window.matchMedia('(min-width: 768px)').matches
: savedPreview === 'true';
}
let previousFilters: { kind: 'all' | SearchKind; scope: string } | null = null;
$: if (show && $searchPreset) {
previousFilters = { kind, scope };
({ kind, scope } = $searchPreset);
searchPreset.set(null);
}
$: if (!show && previousFilters) {
({ kind, scope } = previousFilters);
previousFilters = null;
}
$: schedule(show, query, kind, sharing, scope);
function setPreview(value: boolean) {

View file

@ -8,6 +8,7 @@ import { APP_NAME } from '$lib/constants';
import { type Writable, derived, get, writable } from 'svelte/store';
import type { CommentTarget } from '$lib/apis/comments';
import type { ModelConfig } from '$lib/apis';
import type { SearchKind } from '$lib/apis/search';
import type { Banner } from '$lib/types';
import type { Socket } from 'socket.io-client';
import type { AudioQueue } from '$lib/utils/audio';
@ -171,6 +172,7 @@ export type SettingsModalRequest = {
export const showSidebar = writable(false);
export const showSearch = writable(false);
export const searchPreset: Writable<{ kind: SearchKind; scope: string } | null> = writable(null);
export const showSettings: Writable<boolean | string | SettingsModalRequest> = writable(false);
export const showChangelog = writable(false);