Feature chore bug 007 #21

Merged
iamdoubz merged 68 commits from feature_chore_bug_007 into main 2026-07-12 12:59:04 -05:00
Showing only changes of commit 802e30e9c6 - Show all commits
+61 -50
View File
@@ -1125,46 +1125,54 @@
</section>
{:else if section === "calendar"}
<section>
<h3>Calendar &amp; Outlook .pst</h3>
<h3>{t("settings.calendar.title")}</h3>
<p class="muted">
Import events and attendees from a local Outlook <code>.pst</code> backup — read-only, nothing
is written back to the file. Nothing leaves this device.
{t("settings.calendar.intro_1")} <code>.pst</code>
{t("settings.calendar.intro_2")}
</p>
<div class="grid">
<label class="wide"
>.pst file
>{t("settings.calendar.pst_file")}
<div class="row">
<input class="grow" bind:value={pstPath} placeholder="No file selected" readonly />
<button onclick={pickPstFile}>Browse…</button>
<input
class="grow"
bind:value={pstPath}
placeholder={t("settings.calendar.no_file")}
readonly
/>
<button onclick={pickPstFile}>{t("settings.calendar.browse")}</button>
</div>
</label>
<label
>Password <em>(rarely needed)</em>
<input type="password" bind:value={pstPassword} placeholder="optional" />
>{t("settings.calendar.password")} <em>({t("settings.calendar.password_note")})</em>
<input
type="password"
bind:value={pstPassword}
placeholder={t("settings.calendar.optional")}
/>
</label>
<label
>Import range
>{t("settings.calendar.import_range")}
<select value={importRange} onchange={onImportRangeChange}>
<option value="30">Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="180">Last 6 months</option>
<option value="365">Last 1 year</option>
<option value="all">All time</option>
<option value="30">{t("settings.calendar.range_30")}</option>
<option value="90">{t("settings.calendar.range_90")}</option>
<option value="180">{t("settings.calendar.range_180")}</option>
<option value="365">{t("settings.calendar.range_365")}</option>
<option value="all">{t("settings.calendar.range_all")}</option>
</select>
</label>
</div>
<p class="muted small">
A long-lived mailbox can hold years of recurring/holiday entries — narrowing the range
keeps the imported calendar to what's actually relevant. Applies to both this Import
button and automatic re-import on launch.
</p>
<p class="muted small">{t("settings.calendar.range_hint")}</p>
<div class="actions">
<button class="primary" onclick={doImportPst} disabled={!pstPath || calendar.importing}>
{calendar.importing ? "Importing…" : "Import"}
{calendar.importing ? t("settings.calendar.importing") : t("settings.calendar.import")}
</button>
{#if calendar.importProgress}
<span class="muted"
>{calendar.importProgress.processed}/{calendar.importProgress.total} events</span
>{t("settings.calendar.import_progress", {
processed: calendar.importProgress.processed,
total: calendar.importProgress.total,
})}</span
>
{/if}
</div>
@@ -1174,31 +1182,24 @@
checked={settings.settings.pst_auto_sync}
onchange={onToggleAutoSync}
/>
<span>Re-import this file automatically on launch</span>
<span>{t("settings.calendar.auto_reimport")}</span>
</label>
<p class="muted small">
Runs once at startup, not on a timer — re-import is safe to repeat (existing events are
matched and updated, not duplicated).
</p>
<p class="muted small">{t("settings.calendar.auto_reimport_hint")}</p>
{#if calendar.importError}
<p class="error">
Import failed: {calendar.importError} — the file itself is untouched; check the path and try
again.
{t("settings.calendar.import_failed", { error: calendar.importError })}
</p>
{/if}
<h4>Clean up</h4>
<p class="muted small">
Events already attached to a recorded meeting are always kept, no matter which option
below is picked.
</p>
<h4>{t("settings.calendar.cleanup_title")}</h4>
<p class="muted small">{t("settings.calendar.cleanup_hint")}</p>
<div class="actions">
<select bind:value={cleanupThreshold} disabled={cleanupRunning}>
<option value="30">Older than 30 days</option>
<option value="90">Older than 90 days</option>
<option value="180">Older than 6 months</option>
<option value="365">Older than 1 year</option>
<option value="all">Delete all</option>
<option value="30">{t("settings.calendar.cleanup_30")}</option>
<option value="90">{t("settings.calendar.cleanup_90")}</option>
<option value="180">{t("settings.calendar.cleanup_180")}</option>
<option value="365">{t("settings.calendar.cleanup_365")}</option>
<option value="all">{t("settings.calendar.cleanup_all")}</option>
</select>
<button
class="icon-btn danger"
@@ -1206,41 +1207,51 @@
disabled={cleanupRunning || calendar.events.length === 0}
>
<Trash2 size={13} aria-hidden="true" />
{cleanupRunning ? "Cleaning up…" : "Clean up calendar"}
{cleanupRunning ? t("settings.calendar.cleaning") : t("settings.calendar.cleanup_btn")}
</button>
</div>
{#if cleanupResult}
<p class="muted small">
Deleted {cleanupResult.deleted}{cleanupResult.protected
? `, kept ${cleanupResult.protected} (linked to a meeting)`
: ""}.
{t("settings.calendar.cleanup_result", {
deleted: cleanupResult.deleted,
extra: cleanupResult.protected
? t("settings.calendar.cleanup_kept", { n: cleanupResult.protected })
: "",
})}
</p>
{/if}
{#if cleanupError}
<p class="error">Clean up failed: {cleanupError}</p>
<p class="error">{t("settings.calendar.cleanup_failed", { error: cleanupError })}</p>
{/if}
<h4>Imported events</h4>
<h4>{t("settings.calendar.imported_events")}</h4>
{#if calendar.events.length === 0}
<p class="muted">No events imported yet.</p>
<p class="muted">{t("settings.calendar.no_events")}</p>
{:else}
<div class="grid">
<label class="wide"
>Search title
<input type="text" bind:value={eventTitleFilter} placeholder="Meeting name…" />
>{t("settings.calendar.search_title")}
<input
type="text"
bind:value={eventTitleFilter}
placeholder={t("settings.calendar.search_placeholder")}
/>
</label>
<label
>Date
>{t("settings.calendar.date_label")}
<input type="date" bind:value={eventDateFilter} />
</label>
</div>
<p class="muted">
{filteredEvents.length} of {calendar.events.length} events
{t("settings.calendar.events_count", {
shown: filteredEvents.length,
total: calendar.events.length,
})}
</p>
<ul class="events">
{#each filteredEvents as ev (ev.id)}
<li>
<span class="name">{ev.subject ?? "(untitled)"}</span>
<span class="name">{ev.subject ?? t("settings.calendar.untitled")}</span>
<span class="muted">{formatEventDate(ev.starts_at)}</span>
{#if ev.organizer}<span class="muted">· {ev.organizer}</span>{/if}
</li>