feat: search bar in settings & update changelog

This commit is contained in:
2026-08-26 17:24:55 +09:30
parent de75cb4666
commit 8ccddc1ae0
4 changed files with 77 additions and 6 deletions
+36 -2
View File
@@ -61,6 +61,7 @@
let selectedBackgroundCategory = $state("All");
let backgroundCategories = $state<string[]>([]);
let storeSearchTerm = $state("");
let settingsSearch = $state("");
let showDisclaimerModal = $state(false);
let disclaimerCallbacks = $state<{ onConfirm: () => void; onCancel: () => void } | null>(null);
@@ -142,6 +143,7 @@
);
const sectionTitle = $derived.by(() => {
if (activePage === "settings" && settingsSearch.trim()) return "Search results";
if (activePage === "themes") return "Themes";
if (activePage === "backgrounds") return "Backgrounds";
return [...userNav, ...appNav].find((item) => item.id === activeSection)?.label ?? "Settings";
@@ -251,6 +253,7 @@
const selectNavItem = (id: string) => {
if (activePage === "settings") {
settingsSearch = "";
activeSection = id;
return;
}
@@ -281,6 +284,7 @@
const applyDestination = (destination: SettingsDestination) => {
activePage = destination.page;
if (destination.page === "settings" && destination.section) {
settingsSearch = "";
activeSection = destination.section;
} else if (destination.page === "themes" && destination.view) {
activeThemeView = destination.view === "store" ? "theme-store" : "theme-settings";
@@ -373,6 +377,32 @@
: 'w-[260px] px-4 py-5'}"
aria-label="Settings categories"
>
{#if activePage === "settings"}
<label class="relative mb-4 block shrink-0">
<span class="sr-only">Search settings</span>
<svg
class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-zinc-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m21 21-4.35-4.35m1.35-5.65a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"
/>
</svg>
<input
type="search"
placeholder="Search settings"
bind:value={settingsSearch}
class="h-10 w-full rounded-lg bg-zinc-200/70 pl-9 pr-3 text-sm text-zinc-900 placeholder:text-zinc-400 transition-colors duration-200 focus:bg-white focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:bg-zinc-800/80 dark:text-white dark:placeholder:text-zinc-500 dark:focus:bg-zinc-800 dark:focus:ring-offset-zinc-900"
/>
</label>
{/if}
<SidebarNav
groups={navGroups}
selectedId={selectedNavId}
@@ -455,7 +485,7 @@
</div>
<div class="flex-1 min-h-0 px-4 pb-8 overflow-y-auto no-scrollbar">
{#if activePage === "settings"}
{#if activeSection === "shortcuts"}
{#if activeSection === "shortcuts" && !settingsSearch.trim()}
<Shortcuts />
{:else}
<Settings
@@ -463,8 +493,12 @@
showFontPicker={openFontPicker}
{showDisclaimer}
showCloudPanel={openCloudPanel}
{activeSection}
activeSection={settingsSearch.trim() ? "all" : activeSection}
searchQuery={settingsSearch}
/>
{#if settingsSearch.trim()}
<Shortcuts searchQuery={settingsSearch} />
{/if}
{/if}
{:else if activePage === "themes"}
<Theme section="themes" />
+31 -4
View File
@@ -136,12 +136,13 @@
void loadPluginSettings();
});
const { showColourPicker, showFontPicker, showDisclaimer, showCloudPanel, activeSection = "general" } = $props<{
const { showColourPicker, showFontPicker, showDisclaimer, showCloudPanel, activeSection = "general", searchQuery = "" } = $props<{
showColourPicker: () => void;
showFontPicker: () => void;
showDisclaimer: (onConfirm: () => void, onCancel: () => void, title?: string, message?: string) => void;
showCloudPanel: () => void;
activeSection?: string;
searchQuery?: string;
}>();
/** Map each plugin into the settings sidebar category that fits its purpose. */
@@ -167,6 +168,13 @@
activeSection === "all" ||
(pluginSectionById[pluginId] ?? "features") === activeSection;
const matchesSearch = (...parts: Array<string | undefined>) => {
const q = searchQuery.trim().toLowerCase();
return !q || parts.some((part) => part?.toLowerCase().includes(q));
};
const pluginHit = (plugin: Plugin) =>
matchesSearch(plugin.name, plugin.description, ...Object.values(plugin.settings).flatMap((s) => [s.title, s.description]));
async function exportCloudSettingsJsonToFile() {
const payload = await getSnapshotForUpload();
@@ -183,6 +191,7 @@
</script>
{#snippet Setting({ title, description, Component, props }: SettingsList) }
{#if matchesSearch(title, description)}
<div class="flex justify-between items-center px-5 py-5">
<div class="pr-5">
<h2 class="text-xl font-bold">{title}</h2>
@@ -192,6 +201,7 @@
<Component {...props} />
</div>
</div>
{/if}
{/snippet}
<div class="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700">
@@ -204,6 +214,7 @@
props: {}
})}
{#if matchesSearch("BetterSEQTA Cloud", "Account & sync")}
<div class="border-none">
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
<div class="flex justify-between items-center px-5 py-4">
@@ -223,6 +234,7 @@
</div>
</div>
{/if}
{/if}
{#if showsSection("general")}
{#each [
@@ -354,12 +366,13 @@
{@render Setting(option)}
{/each}
{#if !isEngage}
{#if !isEngage && matchesSearch("Sidebar Style", "Item Size", "Corner Radius", "Active Indicator", "Sidebar Width", "Transparency Effects", "Blur Strength")}
<div class="border-none">
<SidebarAppearance />
</div>
{/if}
{#if matchesSearch("Adaptive Theme Colour", "Soft Gradient", "Smooth colour transition")}
<div class="border-none">
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
<div class="flex justify-between items-center px-5 py-4">
@@ -403,8 +416,10 @@
</div>
</div>
{/if}
{/if}
{#if showsSection("general")}
{#if matchesSearch("Home Page Assessments", "Include Past Assessments", "Maximum Subjects", "Maximum Assessments per Subject")}
<div class="border-none">
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
<div class="flex justify-between items-center px-5 py-4">
@@ -464,9 +479,10 @@
</div>
</div>
{/if}
{/if}
{#each pluginSettings as plugin (plugin.pluginId)}
{#if pluginBelongsInSection(plugin.pluginId)}
{#if pluginBelongsInSection(plugin.pluginId) && pluginHit(plugin)}
<div class="border-none">
<div class="p-1 my-1 from-white to-zinc-100 bg-gradient-to-br rounded-xl border shadow-sm border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40 {!(plugin as any).disableToggle && Object.keys(plugin.settings).length === 0 ? 'hidden' : ''}">
<!-- Always show enable toggle if disableToggle is true -->
@@ -596,7 +612,18 @@
}
})}
{#if $settingsState.devMode}
{#if $settingsState.devMode && matchesSearch(
"Developer Mode",
"Verbose logging",
"Delay loading screen",
"Sensitive Hider",
"Mock Notices",
"Show Privacy Notification",
"Show Theme of the Month",
"Export cloud settings JSON",
"API Base URL",
"GitHub latest version override",
)}
<div class="flex-col p-1 my-1 bg-gradient-to-br from-white rounded-xl border shadow-sm to-zinc-100 border-zinc-200/50 dark:border-zinc-700/40 dark:to-zinc-900/50 dark:from-zinc-900/40">
<div class="flex justify-between items-center px-5 py-4">
<div class="pr-4">
@@ -5,6 +5,9 @@
import { onMount } from 'svelte';
import Shortcuts from "@/seqta/content/links.json"
let { searchQuery = "" } = $props<{ searchQuery?: string }>();
const q = $derived(searchQuery.trim().toLowerCase());
let isLoaded = $state(false);
let fileInput = $state<HTMLInputElement | null>(null);
@@ -106,6 +109,7 @@
<div class="flex flex-col pt-4 divide-y divide-zinc-100 dark:divide-zinc-700">
{#if isLoaded}
{#if !q}
<div>
<MotionDiv
initial={{ opacity: 0, height: 0 }}
@@ -207,9 +211,11 @@
</button>
</MotionDiv>
</div>
{/if}
<!-- Custom Shortcuts Section -->
{#each ($settingsState.customshortcuts ?? []) as shortcut, index}
{#if !q || shortcut.name.toLowerCase().includes(q)}
<div class="flex justify-between items-center px-4 py-3">
{shortcut.name}
<button aria-label="Delete Shortcut" onclick={() => deleteCustomShortcut(index)}>
@@ -218,9 +224,11 @@
</svg>
</button>
</div>
{/if}
{/each}
{#each Object.entries(Shortcuts) as shortcut}
{#if !q || shortcut[1].DisplayName?.toLowerCase().includes(q) || shortcut[0].toLowerCase().includes(q)}
<div class="flex justify-between items-center px-4 py-3">
<div class="pr-4">
<!-- Use DisplayName if it exists, otherwise use the key (shortcut[0]) as a fallback -->
@@ -228,6 +236,7 @@
</div>
<Switch state={$settingsState.shortcuts.find(s => s.name === shortcut[0])?.enabled ?? false} onChange={() => switchChange(shortcut[0])} />
</div>
{/if}
{/each}
{:else}
<div class="p-4 text-center">
@@ -15,6 +15,7 @@ export const WHATS_NEW_CHANGELOG: WhatsNewRelease[] = [
"Removed white background around SEQTA Learn mobile App icons in settings.",
"Re-added the kitten back to the 404 Error pages.",
"Improved the sidebar to be more stable and performant.",
"Fixed Analytics page not respecting no animations setting. (#474)",
"Fixed dropdown contrast and readability in settings and across SEQTA pages.",
"Fixed Analytics sidebar item not hiding when toggled off in Edit Sidebar.",
"Fixed timetable subject colour picker not reopening after closing (#221).",