Feature chore bug 007 #21
@@ -1125,46 +1125,54 @@
|
||||
</section>
|
||||
{:else if section === "calendar"}
|
||||
<section>
|
||||
<h3>Calendar & 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>
|
||||
|
||||
Reference in new issue
Block a user