mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Chore: Patch all Svelte-Warnings & Add Aria-Labels
This commit is contained in:
@@ -39,8 +39,12 @@
|
|||||||
class:row-error={isError}
|
class:row-error={isError}
|
||||||
class:row-selected={isSelected}
|
class:row-selected={isSelected}
|
||||||
class:row-removing={isRemoving}
|
class:row-removing={isRemoving}
|
||||||
|
role="option"
|
||||||
|
aria-selected={isSelected}
|
||||||
|
tabindex="0"
|
||||||
use:rowLongPress
|
use:rowLongPress
|
||||||
onclick={(e) => { e.stopPropagation(); onSelect(item.chapter.id, e); }}
|
onclick={(e) => { e.stopPropagation(); onSelect(item.chapter.id, e); }}
|
||||||
|
onkeydown={(e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); onSelect(item.chapter.id, e as unknown as MouseEvent); } }}
|
||||||
>
|
>
|
||||||
{#if manga?.thumbnailUrl}
|
{#if manga?.thumbnailUrl}
|
||||||
<div class="thumb">
|
<div class="thumb">
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bar-wrap">
|
<div class="bar-wrap">
|
||||||
<div class="status-bar" onclick={handleClickOff} role="presentation">
|
<div class="status-bar" role="none">
|
||||||
<div class="status-dot" class:active={downloadStore.isRunning}></div>
|
<div class="status-dot" class:active={downloadStore.isRunning}></div>
|
||||||
<span class="status-text">
|
<span class="status-text">
|
||||||
{downloadStore.togglingPlay
|
{downloadStore.togglingPlay
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
<button class="sel-action-btn" disabled={downloadStore.batchWorking} onclick={(e) => { e.stopPropagation(); downloadStore.reorderSelectedToEdge("top"); }} title="Move to top">
|
<button class="sel-action-btn" disabled={downloadStore.batchWorking} onclick={(e) => { e.stopPropagation(); downloadStore.reorderSelectedToEdge("top"); }} title="Move to top">
|
||||||
<ArrowLineUp size={12} weight="bold" />
|
<ArrowLineUp size={12} weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
<div class="move-step" onclick={(e) => e.stopPropagation()} role="presentation">
|
<div class="move-step" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="none">
|
||||||
<button class="sel-action-btn" disabled={downloadStore.batchWorking} onclick={(e) => { e.stopPropagation(); downloadStore.reorderSelected("up", moveBy); }} title="Move up">
|
<button class="sel-action-btn" disabled={downloadStore.batchWorking} onclick={(e) => { e.stopPropagation(); downloadStore.reorderSelected("up", moveBy); }} title="Move up">
|
||||||
<CaretUp size={12} weight="bold" />
|
<CaretUp size={12} weight="bold" />
|
||||||
</button>
|
</button>
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content" onclick={handleClickOff}>
|
<div class="content" role="none" onclick={handleClickOff} onkeydown={(e) => e.key === 'Escape' && handleClickOff()}>
|
||||||
<DownloadQueue
|
<DownloadQueue
|
||||||
queue={downloadStore.queue}
|
queue={downloadStore.queue}
|
||||||
loading={downloadStore.loading}
|
loading={downloadStore.loading}
|
||||||
@@ -204,9 +204,6 @@
|
|||||||
.sel-controls { display: flex; align-items: center; gap: var(--sp-2); }
|
.sel-controls { display: flex; align-items: center; gap: var(--sp-2); }
|
||||||
.status-bar { cursor: default; }
|
.status-bar { cursor: default; }
|
||||||
.bar-sep { width: 1px; height: 12px; background: var(--border-dim); flex-shrink: 0; }
|
.bar-sep { width: 1px; height: 12px; background: var(--border-dim); flex-shrink: 0; }
|
||||||
.sel-count { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-secondary); letter-spacing: var(--tracking-wide); white-space: nowrap; }
|
|
||||||
.sel-text-btn { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); background: none; border: none; cursor: pointer; padding: 2px 4px; border-radius: var(--radius-sm); transition: color var(--t-base); white-space: nowrap; }
|
|
||||||
.sel-text-btn:hover { color: var(--text-primary); }
|
|
||||||
.sel-action-btn { display: flex; align-items: center; gap: 4px; font-family: var(--font-ui); font-size: var(--text-xs); padding: 3px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border-dim); background: var(--bg-overlay); color: var(--text-muted); cursor: pointer; transition: color var(--t-base), border-color var(--t-base), background var(--t-base); white-space: nowrap; }
|
.sel-action-btn { display: flex; align-items: center; gap: 4px; font-family: var(--font-ui); font-size: var(--text-xs); padding: 3px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border-dim); background: var(--bg-overlay); color: var(--text-muted); cursor: pointer; transition: color var(--t-base), border-color var(--t-base), background var(--t-base); white-space: nowrap; }
|
||||||
.sel-action-btn:hover:not(:disabled) { color: var(--text-primary); border-color: var(--border-strong); }
|
.sel-action-btn:hover:not(:disabled) { color: var(--text-primary); border-color: var(--border-strong); }
|
||||||
.sel-action-btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
.sel-action-btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
||||||
|
|||||||
@@ -323,8 +323,8 @@
|
|||||||
.root { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
.root { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
||||||
.list { flex: 1; overflow-y: auto; padding: 0 var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: 1px; }
|
.list { flex: 1; overflow-y: auto; padding: 0 var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: 1px; }
|
||||||
.empty { display: flex; align-items: center; justify-content: center; flex: 1; color: var(--text-faint); font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); }
|
.empty { display: flex; align-items: center; justify-content: center; flex: 1; color: var(--text-faint); font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); }
|
||||||
.icon-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); color: var(--text-muted); transition: color var(--t-base), background var(--t-base); }
|
:global(.icon-btn) { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); color: var(--text-muted); transition: color var(--t-base), background var(--t-base); }
|
||||||
.icon-btn:hover:not(:disabled) { color: var(--text-primary); background: var(--bg-raised); }
|
:global(.icon-btn:hover:not(:disabled)) { color: var(--text-primary); background: var(--bg-raised); }
|
||||||
.ext-panel { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-6); flex-shrink: 0; border-bottom: 1px solid var(--border-dim); background: var(--bg-raised); opacity: 1; }
|
.ext-panel { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-6); flex-shrink: 0; border-bottom: 1px solid var(--border-dim); background: var(--bg-raised); opacity: 1; }
|
||||||
.ext-panel-anim { animation: panelSlide 0.18s cubic-bezier(0.16,1,0.3,1) both; }
|
.ext-panel-anim { animation: panelSlide 0.18s cubic-bezier(0.16,1,0.3,1) both; }
|
||||||
.panel-header { display: flex; align-items: center; padding-bottom: var(--sp-1); }
|
.panel-header { display: flex; align-items: center; padding-bottom: var(--sp-1); }
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
let search: string = $state("");
|
let search: string = $state("");
|
||||||
let renderVisible: number = $state(store.settings.renderLimit ?? 48);
|
let renderVisible: number = $state(store.settings.renderLimit ?? 48);
|
||||||
let scrollEl: HTMLDivElement;
|
let scrollEl: HTMLDivElement;
|
||||||
let tabsEl: HTMLDivElement;
|
let tabsEl = $state<HTMLDivElement>(null!);
|
||||||
let containerWidth: number = $state(800);
|
let containerWidth: number = $state(800);
|
||||||
let ctx: { x: number; y: number; manga: Manga } | null = $state(null);
|
let ctx: { x: number; y: number; manga: Manga } | null = $state(null);
|
||||||
let emptyCtx: { x: number; y: number } | null = $state(null);
|
let emptyCtx: { x: number; y: number } | null = $state(null);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="content" onclick={(e) => { if (selectMode && !(e.target as HTMLElement).closest(".card")) onExitSelectMode(); }}>
|
<div class="content" role="presentation" onclick={(e) => { if (selectMode && !(e.target as HTMLElement).closest(".card")) onExitSelectMode(); }}>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{#each Array(12) as _}
|
{#each Array(12) as _}
|
||||||
@@ -184,7 +184,6 @@
|
|||||||
.grid { position: relative; z-index: 1; isolation: isolate; display: grid; grid-template-columns: repeat(var(--cols, auto-fill), minmax(130px, 1fr)); gap: var(--sp-4); }
|
.grid { position: relative; z-index: 1; isolation: isolate; display: grid; grid-template-columns: repeat(var(--cols, auto-fill), minmax(130px, 1fr)); gap: var(--sp-4); }
|
||||||
.card { background: none; border: none; padding: 0; cursor: pointer; text-align: left; }
|
.card { background: none; border: none; padding: 0; cursor: pointer; text-align: left; }
|
||||||
.card.anims:not(.select-mode):hover .cover-wrap { transform: translateY(-3px); border-color: var(--border-strong); box-shadow: 0 6px 20px rgba(0,0,0,0.35); }
|
.card.anims:not(.select-mode):hover .cover-wrap { transform: translateY(-3px); border-color: var(--border-strong); box-shadow: 0 6px 20px rgba(0,0,0,0.35); }
|
||||||
.card.anims:not(.select-mode):hover .cover { filter: brightness(1.1); }
|
|
||||||
.card:not(.select-mode):hover .title { color: var(--text-primary); }
|
.card:not(.select-mode):hover .title { color: var(--text-primary); }
|
||||||
.card.select-mode { cursor: default; }
|
.card.select-mode { cursor: default; }
|
||||||
.card.card-selected .cover-wrap { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-md); }
|
.card.card-selected .cover-wrap { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-md); }
|
||||||
@@ -192,7 +191,6 @@
|
|||||||
.cover-wrap { position: relative; aspect-ratio: 2/3; overflow: hidden; border-radius: var(--radius-md); background: var(--bg-raised); border: 1px solid var(--border-dim); will-change: transform; }
|
.cover-wrap { position: relative; aspect-ratio: 2/3; overflow: hidden; border-radius: var(--radius-md); background: var(--bg-raised); border: 1px solid var(--border-dim); will-change: transform; }
|
||||||
.card.anims .cover-wrap { transition: transform 0.18s cubic-bezier(0.16,1,0.3,1), border-color var(--t-base), box-shadow 0.18s cubic-bezier(0.16,1,0.3,1); }
|
.card.anims .cover-wrap { transition: transform 0.18s cubic-bezier(0.16,1,0.3,1), border-color var(--t-base), box-shadow 0.18s cubic-bezier(0.16,1,0.3,1); }
|
||||||
.cover-wrap.completed { box-shadow: inset 0 -2px 0 0 var(--accent); }
|
.cover-wrap.completed { box-shadow: inset 0 -2px 0 0 var(--accent); }
|
||||||
.card.anims .cover { transition: filter var(--t-base); }
|
|
||||||
.card-info-overlay { position: absolute; bottom: -4px; left: 0; right: 0; z-index: 2; padding: 32px 6px 10px; background: linear-gradient(to top, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.5) 50%, transparent 100%); opacity: 0; pointer-events: none; }
|
.card-info-overlay { position: absolute; bottom: -4px; left: 0; right: 0; z-index: 2; padding: 32px 6px 10px; background: linear-gradient(to top, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.5) 50%, transparent 100%); opacity: 0; pointer-events: none; }
|
||||||
.card-info-overlay.anim { transition: opacity 0.18s ease; }
|
.card-info-overlay.anim { transition: opacity 0.18s ease; }
|
||||||
.card-info-overlay.instant { transition: none; }
|
.card-info-overlay.instant { transition: none; }
|
||||||
|
|||||||
@@ -266,11 +266,6 @@
|
|||||||
.panel-divider { height: 1px; background: var(--border-dim); margin: 4px 2px; }
|
.panel-divider { height: 1px; background: var(--border-dim); margin: 4px 2px; }
|
||||||
.panel-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px 4px; }
|
.panel-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px 4px; }
|
||||||
.panel-heading { font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); color: var(--text-secondary); font-weight: var(--weight-medium, 500); }
|
.panel-heading { font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); color: var(--text-secondary); font-weight: var(--weight-medium, 500); }
|
||||||
.panel-clear-btn { font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); color: var(--text-faint); background: none; border: none; cursor: pointer; padding: 0; transition: color var(--t-base); }
|
|
||||||
.panel-clear-btn:hover { color: var(--color-error); }
|
|
||||||
.panel-item-check { justify-content: flex-start; gap: var(--sp-2); }
|
|
||||||
.panel-check { width: 13px; height: 13px; border-radius: 2px; border: 1px solid var(--border-strong); background: transparent; flex-shrink: 0; transition: background var(--t-base), border-color var(--t-base); display: flex; align-items: center; justify-content: center; color: var(--bg-base); }
|
|
||||||
.panel-check-on { background: var(--accent); border-color: var(--accent); }
|
|
||||||
.dir-toggle { color: var(--text-secondary); justify-content: flex-start; gap: var(--sp-2); border-top: 1px solid var(--border-dim); border-radius: 0 0 var(--radius-sm) var(--radius-sm); margin-top: 2px; padding-top: 9px; }
|
.dir-toggle { color: var(--text-secondary); justify-content: flex-start; gap: var(--sp-2); border-top: 1px solid var(--border-dim); border-radius: 0 0 var(--radius-sm) var(--radius-sm); margin-top: 2px; padding-top: 9px; }
|
||||||
:global(.sort-caret) { flex-shrink: 0; }
|
:global(.sort-caret) { flex-shrink: 0; }
|
||||||
</style>
|
</style>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
let inspectPanStartX = 0;
|
let inspectPanStartX = 0;
|
||||||
let inspectPanStartY = 0;
|
let inspectPanStartY = 0;
|
||||||
|
|
||||||
let stripDragging = false;
|
let stripDragging = $state(false);
|
||||||
let stripDragMoved = false;
|
let stripDragMoved = false;
|
||||||
let stripDragStartY = 0;
|
let stripDragStartY = 0;
|
||||||
let stripScrollStart = 0;
|
let stripScrollStart = 0;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
let containerEl: HTMLDivElement | null = null;
|
let containerEl: HTMLDivElement | null = null;
|
||||||
let pageViewRef: PageView;
|
let pageViewRef: PageView;
|
||||||
let zoomAnchor = { el: null as HTMLElement | null, offset: 0 };
|
let zoomAnchor = { el: null as HTMLElement | null, offset: 0 };
|
||||||
let hideTimer: ReturnType<typeof setTimeout> | null = null;
|
let hideTimer = $state<ReturnType<typeof setTimeout> | null>(null);
|
||||||
let markedRead = new Set<number>();
|
let markedRead = new Set<number>();
|
||||||
let appending = false;
|
let appending = false;
|
||||||
let abortCtrl = { current: null as AbortController | null };
|
let abortCtrl = { current: null as AbortController | null };
|
||||||
|
|||||||
@@ -306,6 +306,7 @@
|
|||||||
{#if readerState.winOpen}
|
{#if readerState.winOpen}
|
||||||
<div
|
<div
|
||||||
class="wc-clip wc-clip-{popoverSide}"
|
class="wc-clip wc-clip-{popoverSide}"
|
||||||
|
role="presentation"
|
||||||
onmouseenter={wcResetTimer}
|
onmouseenter={wcResetTimer}
|
||||||
onmousemove={wcResetTimer}
|
onmousemove={wcResetTimer}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="backdrop" role="presentation" onclick={close} transition:fade={{ duration: 150 }}></div>
|
<div class="backdrop" role="button" tabindex="-1" aria-label="Close settings" onclick={close} onkeydown={(e) => e.key === 'Escape' && close()} transition:fade={{ duration: 150 }}></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="panel"
|
class="panel"
|
||||||
@@ -130,12 +130,13 @@
|
|||||||
<p class="section-label">Page Style</p>
|
<p class="section-label">Page Style</p>
|
||||||
<div class="option-grid">
|
<div class="option-grid">
|
||||||
{#each styleOptions as o}
|
{#each styleOptions as o}
|
||||||
|
{@const Icon = o.icon}
|
||||||
<button
|
<button
|
||||||
class="option-tile"
|
class="option-tile"
|
||||||
class:active={style === o.value}
|
class:active={style === o.value}
|
||||||
onclick={() => onApplySettings({ pageStyle: o.value as typeof PAGE_STYLES[number] })}
|
onclick={() => onApplySettings({ pageStyle: o.value as typeof PAGE_STYLES[number] })}
|
||||||
>
|
>
|
||||||
<div class="tile-icon"><svelte:component this={o.icon} size={18} weight={style === o.value ? "fill" : "light"} /></div>
|
<div class="tile-icon"><Icon size={18} weight={style === o.value ? "fill" : "light"} /></div>
|
||||||
<span class="tile-label">{o.label}</span>
|
<span class="tile-label">{o.label}</span>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -149,6 +150,7 @@
|
|||||||
class:on={effectiveSettings.offsetDoubleSpreads}
|
class:on={effectiveSettings.offsetDoubleSpreads}
|
||||||
onclick={() => onApplySettings({ offsetDoubleSpreads: !effectiveSettings.offsetDoubleSpreads })}
|
onclick={() => onApplySettings({ offsetDoubleSpreads: !effectiveSettings.offsetDoubleSpreads })}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Offset double spreads"
|
||||||
aria-checked={effectiveSettings.offsetDoubleSpreads}
|
aria-checked={effectiveSettings.offsetDoubleSpreads}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -161,6 +163,7 @@
|
|||||||
class:on={effectiveSettings.pageGap ?? true}
|
class:on={effectiveSettings.pageGap ?? true}
|
||||||
onclick={() => onApplySettings({ pageGap: !(effectiveSettings.pageGap ?? true) })}
|
onclick={() => onApplySettings({ pageGap: !(effectiveSettings.pageGap ?? true) })}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Gap between pages"
|
||||||
aria-checked={effectiveSettings.pageGap ?? true}
|
aria-checked={effectiveSettings.pageGap ?? true}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -171,6 +174,7 @@
|
|||||||
class:on={store.settings.autoNextChapter ?? false}
|
class:on={store.settings.autoNextChapter ?? false}
|
||||||
onclick={() => updateSettings({ autoNextChapter: !(store.settings.autoNextChapter ?? false) })}
|
onclick={() => updateSettings({ autoNextChapter: !(store.settings.autoNextChapter ?? false) })}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Auto next chapter"
|
||||||
aria-checked={store.settings.autoNextChapter ?? false}
|
aria-checked={store.settings.autoNextChapter ?? false}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -181,12 +185,13 @@
|
|||||||
<p class="section-label">Fit Mode</p>
|
<p class="section-label">Fit Mode</p>
|
||||||
<div class="option-grid">
|
<div class="option-grid">
|
||||||
{#each fitOptions as o}
|
{#each fitOptions as o}
|
||||||
|
{@const Icon = o.icon}
|
||||||
<button
|
<button
|
||||||
class="option-tile"
|
class="option-tile"
|
||||||
class:active={fit === o.value}
|
class:active={fit === o.value}
|
||||||
onclick={() => onApplySettings({ fitMode: o.value })}
|
onclick={() => onApplySettings({ fitMode: o.value })}
|
||||||
>
|
>
|
||||||
<div class="tile-icon"><svelte:component this={o.icon} size={18} weight={fit === o.value ? "fill" : "light"} /></div>
|
<div class="tile-icon"><Icon size={18} weight={fit === o.value ? "fill" : "light"} /></div>
|
||||||
<span class="tile-label">{o.label}</span>
|
<span class="tile-label">{o.label}</span>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -240,7 +245,7 @@
|
|||||||
<span class="zoom-readout">{zoomPct}%</span>
|
<span class="zoom-readout">{zoomPct}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="zoom-row">
|
<div class="zoom-row">
|
||||||
<button class="zoom-step" onclick={() => setZoom(zoom - 0.1)} disabled={zoom <= ZOOM_MIN}>−</button>
|
<button class="zoom-step" aria-label="Zoom out" onclick={() => setZoom(zoom - 0.1)} disabled={zoom <= ZOOM_MIN}>−</button>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
class="zoom-slider"
|
class="zoom-slider"
|
||||||
@@ -250,7 +255,7 @@
|
|||||||
value={zoomPct}
|
value={zoomPct}
|
||||||
oninput={(e) => setZoom(Number(e.currentTarget.value) / 100)}
|
oninput={(e) => setZoom(Number(e.currentTarget.value) / 100)}
|
||||||
/>
|
/>
|
||||||
<button class="zoom-step" onclick={() => setZoom(zoom + 0.1)} disabled={zoom >= ZOOM_MAX}>+</button>
|
<button class="zoom-step" aria-label="Zoom in" onclick={() => setZoom(zoom + 0.1)} disabled={zoom >= ZOOM_MAX}>+</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -263,6 +268,7 @@
|
|||||||
class:on={effectiveSettings.optimizeContrast}
|
class:on={effectiveSettings.optimizeContrast}
|
||||||
onclick={() => onApplySettings({ optimizeContrast: !effectiveSettings.optimizeContrast })}
|
onclick={() => onApplySettings({ optimizeContrast: !effectiveSettings.optimizeContrast })}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Optimize contrast"
|
||||||
aria-checked={effectiveSettings.optimizeContrast}
|
aria-checked={effectiveSettings.optimizeContrast}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -273,6 +279,7 @@
|
|||||||
class:on={store.settings.pinchZoom ?? false}
|
class:on={store.settings.pinchZoom ?? false}
|
||||||
onclick={() => updateSettings({ pinchZoom: !(store.settings.pinchZoom ?? false) })}
|
onclick={() => updateSettings({ pinchZoom: !(store.settings.pinchZoom ?? false) })}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Pinch to zoom"
|
||||||
aria-checked={store.settings.pinchZoom ?? false}
|
aria-checked={store.settings.pinchZoom ?? false}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -283,6 +290,7 @@
|
|||||||
class:on={store.settings.markReadOnNext ?? true}
|
class:on={store.settings.markReadOnNext ?? true}
|
||||||
onclick={() => updateSettings({ markReadOnNext: !(store.settings.markReadOnNext ?? true) })}
|
onclick={() => updateSettings({ markReadOnNext: !(store.settings.markReadOnNext ?? true) })}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Mark read on chapter advance"
|
||||||
aria-checked={store.settings.markReadOnNext ?? true}
|
aria-checked={store.settings.markReadOnNext ?? true}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -297,6 +305,7 @@
|
|||||||
class:on={perMangaEnabled}
|
class:on={perMangaEnabled}
|
||||||
onclick={onTogglePerManga}
|
onclick={onTogglePerManga}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Per-manga settings"
|
||||||
aria-checked={perMangaEnabled}
|
aria-checked={perMangaEnabled}
|
||||||
><span class="toggle-knob"></span></button>
|
><span class="toggle-knob"></span></button>
|
||||||
</label>
|
</label>
|
||||||
@@ -319,8 +328,8 @@
|
|||||||
bind:value={presetNameInput}
|
bind:value={presetNameInput}
|
||||||
onkeydown={(e) => { if (e.key === "Enter") commitSavePreset(); if (e.key === "Escape") presetSaving = false; }}
|
onkeydown={(e) => { if (e.key === "Enter") commitSavePreset(); if (e.key === "Escape") presetSaving = false; }}
|
||||||
/>
|
/>
|
||||||
<button class="small-btn" disabled={!presetNameInput.trim()} onclick={commitSavePreset}><Check size={12} weight="bold" /></button>
|
<button class="small-btn" aria-label="Confirm" disabled={!presetNameInput.trim()} onclick={commitSavePreset}><Check size={12} weight="bold" /></button>
|
||||||
<button class="small-btn" onclick={() => presetSaving = false}><X size={12} weight="light" /></button>
|
<button class="small-btn" aria-label="Cancel" onclick={() => presetSaving = false}><X size={12} weight="light" /></button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -336,8 +345,8 @@
|
|||||||
bind:value={presetEditName}
|
bind:value={presetEditName}
|
||||||
onkeydown={(e) => { if (e.key === "Enter") commitRenamePreset(); if (e.key === "Escape") presetEditId = null; }}
|
onkeydown={(e) => { if (e.key === "Enter") commitRenamePreset(); if (e.key === "Escape") presetEditId = null; }}
|
||||||
/>
|
/>
|
||||||
<button class="small-btn" disabled={!presetEditName.trim()} onclick={commitRenamePreset}><Check size={12} weight="bold" /></button>
|
<button class="small-btn" aria-label="Confirm" disabled={!presetEditName.trim()} onclick={commitRenamePreset}><Check size={12} weight="bold" /></button>
|
||||||
<button class="small-btn" onclick={() => presetEditId = null}><X size={12} weight="light" /></button>
|
<button class="small-btn" aria-label="Cancel" onclick={() => presetEditId = null}><X size={12} weight="light" /></button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="preset-row">
|
<div class="preset-row">
|
||||||
|
|||||||
@@ -60,11 +60,13 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop"
|
class="backdrop"
|
||||||
role="presentation"
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-label="Close cover picker"
|
||||||
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
||||||
onkeydown={(e) => e.key === "Escape" && onClose()}
|
onkeydown={(e) => e.key === "Escape" && onClose()}
|
||||||
>
|
>
|
||||||
<div class="modal" role="dialog" aria-label="Choose cover image" onkeydown={onKeydown}>
|
<div class="modal" role="dialog" aria-label="Choose cover image" tabindex="-1" onkeydown={onKeydown}>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<span class="title">Cover Image</span>
|
<span class="title">Cover Image</span>
|
||||||
<button class="close-btn" onclick={onClose}><X size={14} weight="light" /></button>
|
<button class="close-btn" onclick={onClose}><X size={14} weight="light" /></button>
|
||||||
@@ -142,12 +144,6 @@
|
|||||||
font-size: var(--text-sm); font-weight: var(--weight-medium);
|
font-size: var(--text-sm); font-weight: var(--weight-medium);
|
||||||
color: var(--text-secondary); flex: 1;
|
color: var(--text-secondary); flex: 1;
|
||||||
}
|
}
|
||||||
.comparing {
|
|
||||||
font-family: var(--font-ui); font-size: 9px;
|
|
||||||
color: var(--text-faint); letter-spacing: var(--tracking-wider);
|
|
||||||
text-transform: uppercase;
|
|
||||||
animation: pulse 1.2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
.close-btn {
|
.close-btn {
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
width: 26px; height: 26px; border-radius: var(--radius-sm);
|
width: 26px; height: 26px; border-radius: var(--radius-sm);
|
||||||
|
|||||||
@@ -493,8 +493,8 @@
|
|||||||
{#if confirmUnbindId !== null}
|
{#if confirmUnbindId !== null}
|
||||||
{@const rec = records.find(r => r.id === confirmUnbindId)}
|
{@const rec = records.find(r => r.id === confirmUnbindId)}
|
||||||
{@const trk = rec ? trackerFor(rec.trackerId) : null}
|
{@const trk = rec ? trackerFor(rec.trackerId) : null}
|
||||||
<div class="confirm-backdrop" role="presentation" onclick={() => confirmUnbindId = null}>
|
<div class="confirm-backdrop" role="button" tabindex="-1" aria-label="Cancel" onclick={() => confirmUnbindId = null} onkeydown={(e) => { if (e.key === 'Escape') confirmUnbindId = null; }}>
|
||||||
<div class="confirm-modal" role="dialog" onclick={(e) => e.stopPropagation()}>
|
<div class="confirm-modal" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
||||||
<p class="confirm-title">Unlink from {trk?.name ?? "tracker"}?</p>
|
<p class="confirm-title">Unlink from {trk?.name ?? "tracker"}?</p>
|
||||||
<p class="confirm-body">Your progress on {trk?.name} is unaffected.</p>
|
<p class="confirm-body">Your progress on {trk?.name} is unaffected.</p>
|
||||||
<div class="confirm-row">
|
<div class="confirm-row">
|
||||||
|
|||||||
@@ -139,8 +139,8 @@
|
|||||||
|
|
||||||
<svelte:window onkeydown={onKey} />
|
<svelte:window onkeydown={onKey} />
|
||||||
|
|
||||||
<div class="backdrop" role="presentation" onclick={onClose} onkeydown={(e) => e.key === "Escape" && onClose()}>
|
<div class="backdrop" role="button" tabindex="-1" aria-label="Close theme editor" onclick={onClose} onkeydown={(e) => e.key === "Escape" && onClose()}>
|
||||||
<div class="shell" role="dialog" aria-label="Theme editor" tabindex="0" style={toCssVars(tokens)} onclick={(e) => e.stopPropagation()}>
|
<div class="shell" role="dialog" aria-label="Theme editor" tabindex="0" style={toCssVars(tokens)} onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
||||||
|
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
mountSystemThemeSync();
|
mountSystemThemeSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
let triggerDark: HTMLButtonElement;
|
let triggerDark = $state<HTMLButtonElement>(null!);
|
||||||
let triggerLight: HTMLButtonElement;
|
let triggerLight = $state<HTMLButtonElement>(null!);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="s-panel">
|
<div class="s-panel">
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
class:on={store.settings.systemThemeSync}
|
class:on={store.settings.systemThemeSync}
|
||||||
onclick={toggleSync}
|
onclick={toggleSync}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label="Match system theme"
|
||||||
aria-checked={store.settings.systemThemeSync}
|
aria-checked={store.settings.systemThemeSync}
|
||||||
><span class="s-toggle-thumb"></span></button>
|
><span class="s-toggle-thumb"></span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
let { selectOpen, closingSelect, toggleSelect, anims }: Props = $props();
|
let { selectOpen, closingSelect, toggleSelect, anims }: Props = $props();
|
||||||
|
|
||||||
let triggerIdleTimeout: HTMLButtonElement;
|
let triggerIdleTimeout = $state<HTMLButtonElement>(null!);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="s-panel">
|
<div class="s-panel">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
let { selectOpen, toggleSelect, anims }: Props = $props();
|
let { selectOpen, toggleSelect, anims }: Props = $props();
|
||||||
|
|
||||||
let triggerSortDir: HTMLButtonElement;
|
let triggerSortDir = $state<HTMLButtonElement>(null!);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="s-panel">
|
<div class="s-panel">
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
let { selectOpen, toggleSelect, anims }: Props = $props();
|
let { selectOpen, toggleSelect, anims }: Props = $props();
|
||||||
|
|
||||||
let triggerPageStyle: HTMLButtonElement;
|
let triggerPageStyle = $state<HTMLButtonElement>(null!);
|
||||||
let triggerReadingDir: HTMLButtonElement;
|
let triggerReadingDir = $state<HTMLButtonElement>(null!);
|
||||||
let triggerFitMode: HTMLButtonElement;
|
let triggerFitMode = $state<HTMLButtonElement>(null!);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="s-panel">
|
<div class="s-panel">
|
||||||
|
|||||||
@@ -200,7 +200,7 @@
|
|||||||
<div class="s-row-info"><span class="s-label">Password</span></div>
|
<div class="s-row-info"><span class="s-label">Password</span></div>
|
||||||
<div class="s-field-wrap">
|
<div class="s-field-wrap">
|
||||||
<input class="s-input" type={showAuthPass ? "text" : "password"} bind:value={authPassword} placeholder="Password" autocomplete="off" spellcheck="false" disabled={secLoading} />
|
<input class="s-input" type={showAuthPass ? "text" : "password"} bind:value={authPassword} placeholder="Password" autocomplete="off" spellcheck="false" disabled={secLoading} />
|
||||||
<button class="s-eye-btn" onclick={() => showAuthPass = !showAuthPass} tabindex="-1">{@html showAuthPass ? EyeClose : EyeOpen}</button>
|
<button class="s-eye-btn" onclick={() => showAuthPass = !showAuthPass} tabindex="-1" aria-label={showAuthPass ? "Hide password" : "Show password"}>{@html showAuthPass ? EyeClose : EyeOpen}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
<div class="s-section-body">
|
<div class="s-section-body">
|
||||||
<label class="s-row">
|
<label class="s-row">
|
||||||
<div class="s-row-info"><span class="s-label">Enable SOCKS proxy</span><span class="s-desc">Route Suwayomi traffic through a SOCKS4/5 proxy</span></div>
|
<div class="s-row-info"><span class="s-label">Enable SOCKS proxy</span><span class="s-desc">Route Suwayomi traffic through a SOCKS4/5 proxy</span></div>
|
||||||
<button role="switch" aria-checked={socksEnabled} class="s-toggle" class:on={socksEnabled}
|
<button role="switch" aria-checked={socksEnabled} aria-label="Enable SOCKS proxy" class="s-toggle" class:on={socksEnabled}
|
||||||
onclick={() => { socksEnabled = !socksEnabled; saveSocksProxy(); }}><span class="s-toggle-thumb"></span></button>
|
onclick={() => { socksEnabled = !socksEnabled; saveSocksProxy(); }}><span class="s-toggle-thumb"></span></button>
|
||||||
</label>
|
</label>
|
||||||
{#if socksEnabled}
|
{#if socksEnabled}
|
||||||
@@ -272,7 +272,7 @@
|
|||||||
<div class="s-row-info"><span class="s-label">Password</span><span class="s-desc">Optional</span></div>
|
<div class="s-row-info"><span class="s-label">Password</span><span class="s-desc">Optional</span></div>
|
||||||
<div class="s-field-wrap">
|
<div class="s-field-wrap">
|
||||||
<input class="s-input" type={showSocksPass ? "text" : "password"} bind:value={socksPassword} placeholder="Password" autocomplete="off" spellcheck="false" />
|
<input class="s-input" type={showSocksPass ? "text" : "password"} bind:value={socksPassword} placeholder="Password" autocomplete="off" spellcheck="false" />
|
||||||
<button class="s-eye-btn" onclick={() => showSocksPass = !showSocksPass} tabindex="-1">{@html showSocksPass ? EyeClose : EyeOpen}</button>
|
<button class="s-eye-btn" onclick={() => showSocksPass = !showSocksPass} tabindex="-1" aria-label={showSocksPass ? "Hide password" : "Show password"}>{@html showSocksPass ? EyeClose : EyeOpen}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="s-row">
|
<div class="s-row">
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
<div class="s-section-body">
|
<div class="s-section-body">
|
||||||
<label class="s-row">
|
<label class="s-row">
|
||||||
<div class="s-row-info"><span class="s-label">Enable FlareSolverr</span><span class="s-desc">Bypass Cloudflare challenges for sources that require it</span></div>
|
<div class="s-row-info"><span class="s-label">Enable FlareSolverr</span><span class="s-desc">Bypass Cloudflare challenges for sources that require it</span></div>
|
||||||
<button role="switch" aria-checked={flareEnabled} class="s-toggle" class:on={flareEnabled}
|
<button role="switch" aria-checked={flareEnabled} aria-label="Enable FlareSolverr" class="s-toggle" class:on={flareEnabled}
|
||||||
onclick={() => { flareEnabled = !flareEnabled; saveFlareSolverr(); }}><span class="s-toggle-thumb"></span></button>
|
onclick={() => { flareEnabled = !flareEnabled; saveFlareSolverr(); }}><span class="s-toggle-thumb"></span></button>
|
||||||
</label>
|
</label>
|
||||||
{#if flareEnabled}
|
{#if flareEnabled}
|
||||||
@@ -320,7 +320,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<label class="s-row">
|
<label class="s-row">
|
||||||
<div class="s-row-info"><span class="s-label">Response fallback</span><span class="s-desc">Use FlareSolverr's response when the direct request fails</span></div>
|
<div class="s-row-info"><span class="s-label">Response fallback</span><span class="s-desc">Use FlareSolverr's response when the direct request fails</span></div>
|
||||||
<button role="switch" aria-checked={flareFallback} class="s-toggle" class:on={flareFallback}
|
<button role="switch" aria-checked={flareFallback} aria-label="Response fallback" class="s-toggle" class:on={flareFallback}
|
||||||
onclick={() => flareFallback = !flareFallback}><span class="s-toggle-thumb"></span></button>
|
onclick={() => flareFallback = !flareFallback}><span class="s-toggle-thumb"></span></button>
|
||||||
</label>
|
</label>
|
||||||
<div class="s-row">
|
<div class="s-row">
|
||||||
|
|||||||
@@ -210,7 +210,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="s-toggle" class:on={store.settings.trackerSyncBack}
|
<button class="s-toggle" class:on={store.settings.trackerSyncBack}
|
||||||
onclick={() => updateSettings({ trackerSyncBack: !store.settings.trackerSyncBack })}
|
onclick={() => updateSettings({ trackerSyncBack: !store.settings.trackerSyncBack })}
|
||||||
role="switch" aria-checked={store.settings.trackerSyncBack}>
|
role="switch" aria-checked={store.settings.trackerSyncBack} aria-label="Enable sync back">
|
||||||
<span class="s-toggle-thumb"></span>
|
<span class="s-toggle-thumb"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
<span class="s-label">Chapter number tolerance</span>
|
<span class="s-label">Chapter number tolerance</span>
|
||||||
<span class="s-desc">Allow source and tracker chapter numbers to differ by up to the set amount. When off, the tracker number is used as-is with no range check.</span>
|
<span class="s-desc">Allow source and tracker chapter numbers to differ by up to the set amount. When off, the tracker number is used as-is with no range check.</span>
|
||||||
</div>
|
</div>
|
||||||
<button role="switch" aria-checked={store.settings.trackerSyncBackThreshold !== null} class="s-toggle" class:on={store.settings.trackerSyncBackThreshold !== null}
|
<button role="switch" aria-checked={store.settings.trackerSyncBackThreshold !== null} aria-label="Chapter number tolerance" class="s-toggle" class:on={store.settings.trackerSyncBackThreshold !== null}
|
||||||
onclick={() => updateSettings({ trackerSyncBackThreshold: store.settings.trackerSyncBackThreshold !== null ? null : 20 })}>
|
onclick={() => updateSettings({ trackerSyncBackThreshold: store.settings.trackerSyncBackThreshold !== null ? null : 20 })}>
|
||||||
<span class="s-toggle-thumb"></span>
|
<span class="s-toggle-thumb"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="s-toggle" class:on={store.settings.trackerRespectScanlatorFilter}
|
<button class="s-toggle" class:on={store.settings.trackerRespectScanlatorFilter}
|
||||||
onclick={() => updateSettings({ trackerRespectScanlatorFilter: !store.settings.trackerRespectScanlatorFilter })}
|
onclick={() => updateSettings({ trackerRespectScanlatorFilter: !store.settings.trackerRespectScanlatorFilter })}
|
||||||
role="switch" aria-checked={store.settings.trackerRespectScanlatorFilter}>
|
role="switch" aria-checked={store.settings.trackerRespectScanlatorFilter} aria-label="Respect scanlator filter">
|
||||||
<span class="s-toggle-thumb"></span>
|
<span class="s-toggle-thumb"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,8 +20,13 @@
|
|||||||
let updatingId = $state<number | null>(null);
|
let updatingId = $state<number | null>(null);
|
||||||
let syncingId = $state<number | null>(null);
|
let syncingId = $state<number | null>(null);
|
||||||
let editingChapter = $state(false);
|
let editingChapter = $state(false);
|
||||||
let chapterDraft = $state(record.lastChapterRead);
|
let chapterDraft = $state(0);
|
||||||
let scoreDraft = $state(record.displayScore ?? "");
|
let scoreDraft = $state("");
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
chapterDraft = record.lastChapterRead;
|
||||||
|
scoreDraft = record.displayScore ?? "";
|
||||||
|
});
|
||||||
let confirmUnbind = $state(false);
|
let confirmUnbind = $state(false);
|
||||||
|
|
||||||
const isBusy = $derived(updatingId === record.id);
|
const isBusy = $derived(updatingId === record.id);
|
||||||
@@ -123,8 +128,11 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop"
|
class="backdrop"
|
||||||
role="presentation"
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-label="Close tracking detail"
|
||||||
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
||||||
|
onkeydown={(e) => { if (e.key === 'Escape') onClose(); }}
|
||||||
>
|
>
|
||||||
<div class="modal" role="dialog" aria-label="Tracking detail">
|
<div class="modal" role="dialog" aria-label="Tracking detail">
|
||||||
|
|
||||||
@@ -323,8 +331,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if confirmUnbind}
|
{#if confirmUnbind}
|
||||||
<div class="confirm-backdrop" role="presentation" onclick={() => confirmUnbind = false}>
|
<div class="confirm-backdrop" role="button" tabindex="-1" aria-label="Cancel" onclick={() => confirmUnbind = false} onkeydown={(e) => { if (e.key === 'Escape') confirmUnbind = false; }}>
|
||||||
<div class="confirm-modal" role="dialog" aria-modal="true" onclick={(e) => e.stopPropagation()}>
|
<div class="confirm-modal" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
||||||
<div class="confirm-icon"><X size={16} weight="bold" /></div>
|
<div class="confirm-icon"><X size={16} weight="bold" /></div>
|
||||||
<p class="confirm-title">Unlink from {record.tracker.name}?</p>
|
<p class="confirm-title">Unlink from {record.tracker.name}?</p>
|
||||||
<p class="confirm-body"><strong>{record.title}</strong> will be removed from your list. Your progress on {record.tracker.name} is unaffected.</p>
|
<p class="confirm-body"><strong>{record.title}</strong> will be removed from your list. Your progress on {record.tracker.name} is unaffected.</p>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
{#if store.toasts.length}
|
{#if store.toasts.length}
|
||||||
<div class="toaster" aria-live="polite">
|
<div class="toaster" aria-live="polite">
|
||||||
{#each store.toasts as t (t.id)}
|
{#each store.toasts as t (t.id)}
|
||||||
<div role="alert" class="toast toast-{t.kind}" data-toast-id={t.id} onclick={() => dismiss(t.id)}>
|
<button class="toast toast-{t.kind}" data-toast-id={t.id} aria-label="{t.title}{t.body ? ': ' + t.body : ''}" onclick={() => dismiss(t.id)}>
|
||||||
<div class="accent-bar"></div>
|
<div class="accent-bar"></div>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<p class="title">{t.title}</p>
|
<p class="title">{t.title}</p>
|
||||||
<p class="sub">{t.body ?? '\u00a0'}</p>
|
<p class="sub">{t.body ?? '\u00a0'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -355,7 +355,9 @@
|
|||||||
{#if store.previewManga}
|
{#if store.previewManga}
|
||||||
<div
|
<div
|
||||||
class="backdrop"
|
class="backdrop"
|
||||||
role="presentation"
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-label="Close preview"
|
||||||
onclick={(e) => { if (e.target === e.currentTarget) close(); }}
|
onclick={(e) => { if (e.target === e.currentTarget) close(); }}
|
||||||
onkeydown={(e) => { if (e.key === "Escape") close(); }}
|
onkeydown={(e) => { if (e.key === "Escape") close(); }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
let focused = $state(-1);
|
let focused = $state(-1);
|
||||||
let el = $state<HTMLDivElement | undefined>(undefined);
|
let el = $state<HTMLDivElement | undefined>(undefined);
|
||||||
let measured = $state(false);
|
let measured = $state(false);
|
||||||
let pos = $state({ left: x, top: y });
|
let pos = $state({ left: 0, top: 0 });
|
||||||
let subOpen = $state(-1);
|
let subOpen = $state(-1);
|
||||||
let subEls = $state<(HTMLDivElement | null)[]>([]);
|
let subEls = $state<(HTMLDivElement | null)[]>([]);
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
{#if hasSub}<span class="sub-arrow">›</span>{/if}
|
{#if hasSub}<span class="sub-arrow">›</span>{/if}
|
||||||
</button>
|
</button>
|
||||||
{#if hasSub && subOpen === i}
|
{#if hasSub && subOpen === i}
|
||||||
<div bind:this={subEls[i]} class="menu submenu" role="menu"
|
<div bind:this={subEls[i]} class="menu submenu" role="menu" tabindex="-1"
|
||||||
onmouseenter={() => { subOpen = i; }}>
|
onmouseenter={() => { subOpen = i; }}>
|
||||||
{#each mi.children as child}
|
{#each mi.children as child}
|
||||||
{#if "separator" in child}
|
{#if "separator" in child}
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
animation: scaleIn 0.08s ease both;
|
animation: scaleIn 0.08s ease both;
|
||||||
transform-origin: top left;
|
transform-origin: top left;
|
||||||
}
|
}
|
||||||
.submenu.sub-flip {
|
:global(.submenu.sub-flip) {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 100%;
|
right: 100%;
|
||||||
transform-origin: top right;
|
transform-origin: top right;
|
||||||
|
|||||||
Reference in New Issue
Block a user