fix: resolve all linter errors and a11y warnings
- Fix TS errors: editMode property order, implicit any, string|undefined - Add $state() to bind:this element refs (IconGrid, EntityPicker, etc.) - Fix a11y: labels, aria-labels, roles, tabindex on dialogs - Remove unused imports (tick, svelte-i18n) - Make AutocompleteInput/TagsInput accept optional string values
This commit is contained in:
@@ -133,6 +133,7 @@
|
|||||||
<h3 class="mb-3 text-sm font-semibold text-card-foreground">{$t('admin.perm_title')}</h3>
|
<h3 class="mb-3 text-sm font-semibold text-card-foreground">{$t('admin.perm_title')}</h3>
|
||||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-5">
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-5">
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_entity_type')}</label>
|
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_entity_type')}</label>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
items={entityTypeItems}
|
items={entityTypeItems}
|
||||||
@@ -142,6 +143,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_entity')}</label>
|
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_entity')}</label>
|
||||||
<EntityPicker
|
<EntityPicker
|
||||||
items={entityPickerItems}
|
items={entityPickerItems}
|
||||||
@@ -151,6 +153,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_target_type')}</label>
|
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_target_type')}</label>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
items={targetTypeItems}
|
items={targetTypeItems}
|
||||||
@@ -160,6 +163,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_target')}</label>
|
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_target')}</label>
|
||||||
<EntityPicker
|
<EntityPicker
|
||||||
items={targetPickerItems}
|
items={targetPickerItems}
|
||||||
@@ -169,6 +173,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_level')}</label>
|
<label class="mb-1 block text-xs text-muted-foreground">{$t('admin.perm_level')}</label>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
@@ -227,6 +227,7 @@
|
|||||||
{#if $form.healthcheckEnabled}
|
{#if $form.healthcheckEnabled}
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label
|
<label
|
||||||
class="mb-1 block text-sm font-medium text-card-foreground"
|
class="mb-1 block text-sm font-medium text-card-foreground"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="block text-sm font-medium text-card-foreground">{$t('app.icon')}</label>
|
<label class="block text-sm font-medium text-card-foreground">{$t('app.icon')}</label>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
|||||||
@@ -144,6 +144,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => removeLink(link.id)}
|
onclick={() => removeLink(link.id)}
|
||||||
|
aria-label="Remove link"
|
||||||
class="flex-shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
|
class="flex-shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={onClose}
|
onclick={onClose}
|
||||||
|
aria-label="Close"
|
||||||
class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
class="rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
@@ -111,6 +112,7 @@
|
|||||||
|
|
||||||
<!-- Icon -->
|
<!-- Icon -->
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">{$t('app.icon') ?? 'Icon'}</label>
|
<label class="mb-1 block text-sm font-medium text-foreground">{$t('app.icon') ?? 'Icon'}</label>
|
||||||
<IconPickerButton value={icon} onchange={(v) => { icon = v; }} />
|
<IconPickerButton value={icon} onchange={(v) => { icon = v; }} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -148,10 +148,11 @@
|
|||||||
<svelte:window onkeydown={handleKeydown} />
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
<!-- Backdrop -->
|
<!-- Backdrop -->
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||||
onclick={handleBackdropClick}
|
onclick={handleBackdropClick}
|
||||||
|
role="presentation"
|
||||||
>
|
>
|
||||||
<div class="mx-4 w-full max-w-lg rounded-xl border border-border bg-card p-6 shadow-xl" role="dialog" aria-modal="true">
|
<div class="mx-4 w-full max-w-lg rounded-xl border border-border bg-card p-6 shadow-xl" role="dialog" aria-modal="true">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import SearchResult from './SearchResult.svelte';
|
import SearchResult from './SearchResult.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement = $state() as HTMLInputElement;
|
||||||
let resultsEl: HTMLDivElement;
|
let resultsEl: HTMLDivElement = $state() as HTMLDivElement;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (search.open && inputEl) {
|
if (search.open && inputEl) {
|
||||||
@@ -68,10 +68,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if search.open}
|
{#if search.open}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-50 flex items-start justify-center bg-black/50 pt-[15vh] backdrop-blur-sm"
|
class="fixed inset-0 z-50 flex items-start justify-center bg-black/50 pt-[15vh] backdrop-blur-sm"
|
||||||
onclick={handleBackdropClick}
|
onclick={handleBackdropClick}
|
||||||
|
role="presentation"
|
||||||
style="animation: searchFadeIn 0.15s ease-out"
|
style="animation: searchFadeIn 0.15s ease-out"
|
||||||
>
|
>
|
||||||
<!-- Dialog -->
|
<!-- Dialog -->
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#if label}
|
{#if label}
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="block text-sm font-medium text-foreground">{label}</label>
|
<label class="block text-sm font-medium text-foreground">{label}</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,7 @@
|
|||||||
|
|
||||||
<!-- Hue slider -->
|
<!-- Hue slider -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1.5 block text-sm text-muted-foreground">{$t('settings.hue')}</label>
|
<label class="mb-1.5 block text-sm text-muted-foreground">{$t('settings.hue')}</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div
|
<div
|
||||||
@@ -174,7 +175,8 @@
|
|||||||
|
|
||||||
<!-- Saturation slider -->
|
<!-- Saturation slider -->
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-1.5 block text-sm text-muted-foreground">{$t('settings.saturation')}</label>
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
|
<label class="mb-1.5 block text-sm text-muted-foreground">{$t('settings.saturation')}</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div
|
<div
|
||||||
class="h-3 w-full rounded-full"
|
class="h-3 w-full rounded-full"
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from 'svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
value: string;
|
value: string | undefined;
|
||||||
suggestions: string[];
|
suggestions: string[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -25,7 +23,7 @@
|
|||||||
let containerEl: HTMLDivElement | undefined = $state();
|
let containerEl: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
const filtered = $derived.by(() => {
|
const filtered = $derived.by(() => {
|
||||||
const q = value.trim().toLowerCase();
|
const q = (value ?? '').trim().toLowerCase();
|
||||||
if (!q) return suggestions;
|
if (!q) return suggestions;
|
||||||
return suggestions.filter((s) => s.toLowerCase().includes(q));
|
return suggestions.filter((s) => s.toLowerCase().includes(q));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
onclick={(e) => e.stopPropagation()}
|
onclick={(e) => e.stopPropagation()}
|
||||||
transition:scale={{ start: 0.95, duration: 150 }}
|
transition:scale={{ start: 0.95, duration: 150 }}
|
||||||
role="alertdialog"
|
role="alertdialog"
|
||||||
|
tabindex="-1"
|
||||||
aria-labelledby="confirm-dialog-title"
|
aria-labelledby="confirm-dialog-title"
|
||||||
aria-describedby="confirm-dialog-message"
|
aria-describedby="confirm-dialog-message"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
let open = $state(false);
|
let open = $state(false);
|
||||||
let query = $state('');
|
let query = $state('');
|
||||||
let highlightIdx = $state(0);
|
let highlightIdx = $state(0);
|
||||||
let listEl: HTMLDivElement;
|
let listEl = $state<HTMLDivElement>();
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl = $state<HTMLInputElement>();
|
||||||
|
|
||||||
const selectedItem = $derived(items.find((i) => i.value === value));
|
const selectedItem = $derived(items.find((i) => i.value === value));
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
$props();
|
$props();
|
||||||
|
|
||||||
let open = $state(false);
|
let open = $state(false);
|
||||||
let triggerEl: HTMLButtonElement;
|
let triggerEl = $state<HTMLButtonElement>();
|
||||||
let popupEl: HTMLDivElement;
|
let popupEl = $state<HTMLDivElement>();
|
||||||
let filterQuery = $state('');
|
let filterQuery = $state('');
|
||||||
|
|
||||||
const selectedItem = $derived(items.find((i) => i.value === value));
|
const selectedItem = $derived(items.find((i) => i.value === value));
|
||||||
@@ -135,6 +135,7 @@
|
|||||||
>
|
>
|
||||||
{#if showFilter}
|
{#if showFilter}
|
||||||
<div class="border-b border-border px-3 py-2">
|
<div class="border-b border-border px-3 py-2">
|
||||||
|
<!-- svelte-ignore a11y_autofocus -->
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={filterQuery}
|
bind:value={filterQuery}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
value: string;
|
value: string | undefined;
|
||||||
suggestions: string[];
|
suggestions: string[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -25,11 +23,11 @@
|
|||||||
let containerEl: HTMLDivElement | undefined = $state();
|
let containerEl: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
// Parse comma-separated tags
|
// Parse comma-separated tags
|
||||||
const tags = $derived(value.split(',').map((t) => t.trim()).filter(Boolean));
|
const tags = $derived((value ?? '').split(',').map((t) => t.trim()).filter(Boolean));
|
||||||
|
|
||||||
// Get the current partial tag being typed (after last comma)
|
// Get the current partial tag being typed (after last comma)
|
||||||
const currentPartial = $derived.by(() => {
|
const currentPartial = $derived.by(() => {
|
||||||
const parts = value.split(',');
|
const parts = (value ?? '').split(',');
|
||||||
return parts[parts.length - 1]?.trim() ?? '';
|
return parts[parts.length - 1]?.trim() ?? '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@
|
|||||||
|
|
||||||
function selectItem(item: string) {
|
function selectItem(item: string) {
|
||||||
// Replace the current partial with the selected tag
|
// Replace the current partial with the selected tag
|
||||||
const parts = value.split(',').map((p) => p.trim());
|
const parts = (value ?? '').split(',').map((p) => p.trim());
|
||||||
parts[parts.length - 1] = item;
|
parts[parts.length - 1] = item;
|
||||||
value = parts.join(',') + ',';
|
value = parts.join(',') + ',';
|
||||||
open = false;
|
open = false;
|
||||||
|
|||||||
@@ -196,6 +196,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else if cardSize === 'large'}
|
{:else if cardSize === 'large'}
|
||||||
<!-- Large: icon + name + description + sparkline + tags + links -->
|
<!-- Large: icon + name + description + sparkline + tags + links -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="card-hover group rounded-xl {cardStyleClass} p-5 transition-colors hover:border-primary/50"
|
class="card-hover group rounded-xl {cardStyleClass} p-5 transition-colors hover:border-primary/50"
|
||||||
data-app-widget
|
data-app-widget
|
||||||
@@ -291,6 +292,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Medium (default): icon + name + status + sparkline on hover + links -->
|
<!-- Medium (default): icon + name + status + sparkline on hover + links -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="card-hover group rounded-xl {cardStyleClass} p-4 transition-colors hover:border-primary/50"
|
class="card-hover group rounded-xl {cardStyleClass} p-4 transition-colors hover:border-primary/50"
|
||||||
data-app-widget
|
data-app-widget
|
||||||
|
|||||||
@@ -363,6 +363,7 @@
|
|||||||
<div class="mb-3 rounded-lg border border-border bg-muted/50 p-3">
|
<div class="mb-3 rounded-lg border border-border bg-muted/50 p-3">
|
||||||
<!-- Widget Type Selector (Icon Grid) -->
|
<!-- Widget Type Selector (Icon Grid) -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">
|
<label class="mb-1 block text-sm font-medium text-foreground">
|
||||||
Widget Type
|
Widget Type
|
||||||
</label>
|
</label>
|
||||||
@@ -376,6 +377,7 @@
|
|||||||
<!-- Type-specific config forms -->
|
<!-- Type-specific config forms -->
|
||||||
{#if selectedWidgetType === 'app'}
|
{#if selectedWidgetType === 'app'}
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">
|
<label class="mb-1 block text-sm font-medium text-foreground">
|
||||||
{$t('widget.select_app')}
|
{$t('widget.select_app')}
|
||||||
</label>
|
</label>
|
||||||
@@ -434,6 +436,7 @@
|
|||||||
{:else if selectedWidgetType === 'note'}
|
{:else if selectedWidgetType === 'note'}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">Format</label>
|
<label class="mb-1 block text-sm font-medium text-foreground">Format</label>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
items={noteFormatItems}
|
items={noteFormatItems}
|
||||||
@@ -516,6 +519,7 @@
|
|||||||
{:else if selectedWidgetType === 'clock'}
|
{:else if selectedWidgetType === 'clock'}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">Clock Style</label>
|
<label class="mb-1 block text-sm font-medium text-foreground">Clock Style</label>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
items={clockStyleItems}
|
items={clockStyleItems}
|
||||||
@@ -760,6 +764,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">Source Type</label>
|
<label class="mb-1 block text-sm font-medium text-foreground">Source Type</label>
|
||||||
<IconGrid
|
<IconGrid
|
||||||
items={metricSourceItems}
|
items={metricSourceItems}
|
||||||
|
|||||||
+15
-11
@@ -5,7 +5,7 @@
|
|||||||
import type { LayoutData } from './$types.js';
|
import type { LayoutData } from './$types.js';
|
||||||
import MainLayout from '$lib/components/layout/MainLayout.svelte';
|
import MainLayout from '$lib/components/layout/MainLayout.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { fade } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { theme } from '$lib/stores/theme.svelte';
|
import { theme } from '$lib/stores/theme.svelte';
|
||||||
import { ui } from '$lib/stores/ui.svelte';
|
import { ui } from '$lib/stores/ui.svelte';
|
||||||
import { search } from '$lib/stores/search.svelte';
|
import { search } from '$lib/stores/search.svelte';
|
||||||
@@ -22,12 +22,14 @@
|
|||||||
let { data, children }: { data: LayoutData; children: Snippet } = $props();
|
let { data, children }: { data: LayoutData; children: Snippet } = $props();
|
||||||
|
|
||||||
// Apply user preferences from server (overrides localStorage defaults)
|
// Apply user preferences from server (overrides localStorage defaults)
|
||||||
if (data.userPreferences) {
|
$effect(() => {
|
||||||
theme.loadFromServer(data.userPreferences);
|
if (data.userPreferences) {
|
||||||
if (data.userPreferences.locale) {
|
theme.loadFromServer(data.userPreferences);
|
||||||
i18nLocale.set(data.userPreferences.locale);
|
if (data.userPreferences.locale) {
|
||||||
|
i18nLocale.set(data.userPreferences.locale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// Initialize store effects within component context
|
// Initialize store effects within component context
|
||||||
theme.initEffects();
|
theme.initEffects();
|
||||||
@@ -38,9 +40,11 @@
|
|||||||
keyboard.init();
|
keyboard.init();
|
||||||
|
|
||||||
// Load favorites for authenticated users
|
// Load favorites for authenticated users
|
||||||
if (data.user) {
|
$effect(() => {
|
||||||
favorites.load();
|
if (data.user) {
|
||||||
}
|
favorites.load();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Listen for cross-tab sync messages (theme changes & data invalidation)
|
// Listen for cross-tab sync messages (theme changes & data invalidation)
|
||||||
const cleanupSync = onSyncMessage((msg) => {
|
const cleanupSync = onSyncMessage((msg) => {
|
||||||
@@ -82,14 +86,14 @@
|
|||||||
boards={data.sidebarBoards ?? []}
|
boards={data.sidebarBoards ?? []}
|
||||||
>
|
>
|
||||||
{#key pageKey}
|
{#key pageKey}
|
||||||
<div in:fade={{ duration: 150, delay: 75 }} out:fade={{ duration: 75 }}>
|
<div in:fly={{ y: 6, duration: 150 }}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
{:else}
|
{:else}
|
||||||
{#key pageKey}
|
{#key pageKey}
|
||||||
<div in:fade={{ duration: 150, delay: 75 }} out:fade={{ duration: 75 }}>
|
<div in:fly={{ y: 6, duration: 150 }}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
|||||||
const appHistories: Record<string, { history: { status: string; responseTime: number | null; checkedAt: string }[]; uptimePercent: number }> = {};
|
const appHistories: Record<string, { history: { status: string; responseTime: number | null; checkedAt: string }[]; uptimePercent: number }> = {};
|
||||||
for (const [appId, data] of historyMap) {
|
for (const [appId, data] of historyMap) {
|
||||||
appHistories[appId] = {
|
appHistories[appId] = {
|
||||||
history: data.history.map((h) => ({ ...h, checkedAt: h.checkedAt.toISOString() })),
|
history: data.history.map((h: { status: string; responseTime: number | null; checkedAt: Date }) => ({ ...h, checkedAt: h.checkedAt.toISOString() })),
|
||||||
uptimePercent: data.uptimePercent
|
uptimePercent: data.uptimePercent
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,6 +318,7 @@
|
|||||||
|
|
||||||
<!-- Background Type -->
|
<!-- Background Type -->
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">{$t('settings.background') ?? 'Background'}</label>
|
<label class="mb-1 block text-sm font-medium text-foreground">{$t('settings.background') ?? 'Background'}</label>
|
||||||
<div class="flex flex-wrap gap-1 rounded-lg border border-border bg-muted/50 p-1">
|
<div class="flex flex-wrap gap-1 rounded-lg border border-border bg-muted/50 p-1">
|
||||||
{#each ['mesh', 'particles', 'aurora', 'wallpaper', 'none'] as bg (bg)}
|
{#each ['mesh', 'particles', 'aurora', 'wallpaper', 'none'] as bg (bg)}
|
||||||
@@ -333,6 +334,7 @@
|
|||||||
|
|
||||||
<!-- Card Size -->
|
<!-- Card Size -->
|
||||||
<div>
|
<div>
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label class="mb-1 block text-sm font-medium text-foreground">{$t('board.card_size') ?? 'Card Size'}</label>
|
<label class="mb-1 block text-sm font-medium text-foreground">{$t('board.card_size') ?? 'Card Size'}</label>
|
||||||
<div class="flex gap-1 rounded-lg border border-border bg-muted/50 p-1">
|
<div class="flex gap-1 rounded-lg border border-border bg-muted/50 p-1">
|
||||||
{#each ['compact', 'medium', 'large'] as size (size)}
|
{#each ['compact', 'medium', 'large'] as size (size)}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
const { form, errors, enhance, submitting } = superForm(data.form);
|
const { form, errors, enhance, submitting } = superForm(data.form);
|
||||||
|
|
||||||
const showLocalForm = data.authMode === 'local' || data.authMode === 'both';
|
const showLocalForm = $derived(data.authMode === 'local' || data.authMode === 'both');
|
||||||
const showOAuthButton = data.authMode === 'oauth' || data.authMode === 'both';
|
const showOAuthButton = $derived(data.authMode === 'oauth' || data.authMode === 'both');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
Reference in New Issue
Block a user