feat(confirm): centralize confirmation dialogs and add i18n
Introduce a generic ConfirmDialog and a promise-based requestConfirmation API in App.svelte so callers can await user responses instead of using window.confirm. Provide helper builders (branchDeleteConfirmRequest, discardConfirmRequest) to create dialog content for common cases. Many call sites were switched to use requestConfirmation and now render the in-app ConfirmDialog; the previous specialized confirm components (BranchDeleteConfirmDialog, DiscardConfirmDialog) were removed. Add lightweight i18n support (setLanguage, t()) and new messages/i18n modules, and replace hardcoded English strings in several components (e.g. AiSettingsPage, BlameDialog and many confirmation prompts) with translated keys. Summary of effects: - Replaces native window.confirm with awaitable in-app ConfirmDialog dialogs. - Centralizes confirmation UI and content construction in App.svelte. - Adds i18n plumbing and updates UI text to use t(). - Removes two specialized confirm dialog components and adds src/lib/components/ConfirmDialog.svelte.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { GitBranch, History, LoaderCircle, Search, ShieldCheck, X } from "@lucide/svelte";
|
||||
import type { ReflogEntry } from "../types";
|
||||
import { t } from "../i18n.svelte";
|
||||
|
||||
interface Props {
|
||||
entries: ReflogEntry[];
|
||||
@@ -32,21 +33,21 @@
|
||||
</script>
|
||||
|
||||
<div class="dialog-backdrop app-chrome-backdrop" role="presentation">
|
||||
<div class="dialog reflog-dialog" role="dialog" aria-modal="true" aria-label="Reflog" tabindex="-1">
|
||||
<div class="dialog reflog-dialog" role="dialog" aria-modal="true" aria-label={t("reflog.title")} tabindex="-1">
|
||||
<header class="dialog-header unified-dialog-header">
|
||||
<span class="unified-dialog-icon" aria-hidden="true"><History size={18} /></span>
|
||||
<div class="unified-dialog-text"><span class="eyebrow">Recovery history</span><h2 class="dialog-title">Reflog</h2></div>
|
||||
<button class="dialog-close" type="button" onclick={onClose} disabled={isBusy} title="Close"><X size={18} aria-hidden="true" /></button>
|
||||
<div class="unified-dialog-text"><span class="eyebrow">{t("reflog.eyebrow")}</span><h2 class="dialog-title">{t("reflog.title")}</h2></div>
|
||||
<button class="dialog-close" type="button" onclick={onClose} disabled={isBusy} title={t("common.close")}><X size={18} aria-hidden="true" /></button>
|
||||
</header>
|
||||
<div class="reflog-body">
|
||||
<aside class="reflog-list-pane">
|
||||
<label class="reflog-search"><Search size={15} aria-hidden="true" /><input bind:value={query} placeholder="Search actions, hashes or authors" aria-label="Search reflog" /></label>
|
||||
<label class="reflog-search"><Search size={15} aria-hidden="true" /><input bind:value={query} placeholder={t("reflog.searchPlaceholder")} aria-label={t("reflog.searchLabel")} /></label>
|
||||
{#if isLoading}
|
||||
<div class="blank-state"><LoaderCircle class="spin" size={18} aria-hidden="true" /> Loading reflog…</div>
|
||||
<div class="blank-state"><LoaderCircle class="spin" size={18} aria-hidden="true" /> {t("reflog.loading")}</div>
|
||||
{:else if filteredEntries.length === 0}
|
||||
<div class="blank-state">No reflog entries match this search.</div>
|
||||
<div class="blank-state">{t("reflog.noMatch")}</div>
|
||||
{:else}
|
||||
<div class="reflog-list" role="listbox" aria-label="Reflog entries">
|
||||
<div class="reflog-list" role="listbox" aria-label={t("reflog.listLabel")}>
|
||||
{#each filteredEntries as entry (`${entry.selector}:${entry.hash}`)}
|
||||
<button class:active={selected?.selector === entry.selector} type="button" role="option" aria-selected={selected?.selector === entry.selector} onclick={() => select(entry)}>
|
||||
<span class="reflog-row-top"><code>{entry.selector}</code><span>{new Date(entry.date).toLocaleString()}</span></span>
|
||||
@@ -62,18 +63,18 @@
|
||||
{#if error}<div class="rebase-warning error">{error}</div>{/if}
|
||||
{#if selected}
|
||||
<div class="reflog-detail-head"><History size={20} aria-hidden="true" /><div><span class="eyebrow">{selected.selector}</span><h3>{selected.action}</h3></div></div>
|
||||
<dl><div><dt>Commit</dt><dd><code>{selected.hash}</code></dd></div><div><dt>Author</dt><dd>{selected.author_name}</dd></div><div><dt>Date</dt><dd>{new Date(selected.date).toLocaleString()}</dd></div></dl>
|
||||
<button class="btn-secondary reflog-preview" type="button" onclick={() => onPreview(selected)} disabled={isBusy || selected.hash === currentHash}><History size={15} aria-hidden="true" /> Preview changes to current HEAD</button>
|
||||
<dl><div><dt>{t("common.commit")}</dt><dd><code>{selected.hash}</code></dd></div><div><dt>{t("reflog.author")}</dt><dd>{selected.author_name}</dd></div><div><dt>{t("reflog.date")}</dt><dd>{new Date(selected.date).toLocaleString()}</dd></div></dl>
|
||||
<button class="btn-secondary reflog-preview" type="button" onclick={() => onPreview(selected)} disabled={isBusy || selected.hash === currentHash}><History size={15} aria-hidden="true" /> {t("reflog.preview")}</button>
|
||||
<div class="reflog-recovery-card">
|
||||
<div class="reflog-recovery-title"><ShieldCheck size={18} aria-hidden="true" /><div><strong>Safe recovery</strong><span>Create a new branch here. The current branch is not reset or deleted.</span></div></div>
|
||||
<label><span>Recovery branch</span><div><GitBranch size={15} aria-hidden="true" /><input bind:value={recoveryBranch} disabled={isBusy} spellcheck="false" /></div></label>
|
||||
<div class="reflog-recovery-title"><ShieldCheck size={18} aria-hidden="true" /><div><strong>{t("reflog.safeRecovery")}</strong><span>{t("reflog.safeRecoveryNote")}</span></div></div>
|
||||
<label><span>{t("reflog.recoveryBranch")}</span><div><GitBranch size={15} aria-hidden="true" /><input bind:value={recoveryBranch} disabled={isBusy} spellcheck="false" /></div></label>
|
||||
<button class="btn-primary" type="button" onclick={() => onRestore(selected, recoveryBranch.trim())} disabled={isBusy || !recoveryBranch.trim()}>
|
||||
{#if operation === "Restoring reflog entry"}<LoaderCircle class="spin" size={16} aria-hidden="true" />{:else}<ShieldCheck size={16} aria-hidden="true" />{/if}
|
||||
Create and checkout recovery branch
|
||||
{t("reflog.createBranch")}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="blank-state">Select a reflog entry to inspect or recover it.</div>
|
||||
<div class="blank-state">{t("reflog.selectEntry")}</div>
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user