@zaclinley/dsh-mailharbor 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -27,6 +27,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
27
27
  var index_exports = {};
28
28
  __export(index_exports, {
29
29
  PANEL_ID: () => PANEL_ID,
30
+ TASKS_PANEL_ID: () => TASKS_PANEL_ID,
30
31
  VERSION: () => VERSION,
31
32
  apply: () => apply,
32
33
  inject: () => inject
@@ -34,7 +35,7 @@ __export(index_exports, {
34
35
  module.exports = __toCommonJS(index_exports);
35
36
 
36
37
  // src/client/styles.css
37
- var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: 2fr 90px auto;\n align-items: end;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor-settings {\n padding: 20px 24px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 880px;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
38
+ var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > input,\n.mailharbor-field > select,\n.mailharbor-field > textarea,\n.mailharbor-card > textarea {\n width: 100%;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 10px;\n min-width: 0;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: minmax(0, 2fr) minmax(72px, 90px) auto;\n align-items: end;\n min-width: 0;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- selection / extraction ---------- */\n\n.mailharbor-selectbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n margin: 0 12px 8px;\n border: 1px solid var(--m-accent-soft);\n border-radius: 9px;\n background: var(--m-accent-soft);\n}\n\n.mailharbor-selectbar select {\n height: 28px;\n max-width: 200px;\n}\n\n.mailharbor-row.selectable input[type='checkbox'] {\n width: 15px;\n height: 15px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 9px 10px;\n border: 1px solid var(--m-line-soft);\n border-radius: 9px;\n background: var(--m-bg-1);\n cursor: pointer;\n}\n\n.mailharbor-extract-row input {\n margin-top: 2px;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row > span {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 6px 12px;\n margin: 0;\n font-size: 12.5px;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n/* ---------- tasks panel ---------- */\n\n.mailharbor .chip {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n.tasks-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 18px;\n padding: 18px 20px 48px;\n width: 100%;\n max-width: 940px;\n margin: 0 auto;\n}\n\n.tasks-overview {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));\n gap: 12px;\n}\n\n.tasks-progress,\n.tasks-project {\n display: flex;\n flex-direction: column;\n gap: 7px;\n padding: 12px 14px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n}\n\n.tasks-progress-head,\n.tasks-project-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.tasks-project-head .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 500;\n}\n\n.tasks-bar {\n height: 6px;\n border-radius: 999px;\n background: var(--m-bg-3);\n overflow: hidden;\n}\n\n.tasks-bar span {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: var(--m-accent);\n transition: width 200ms ease;\n}\n\n.tasks-bar.small {\n height: 4px;\n}\n\n.tasks-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.tasks-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tasks-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 9px 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n}\n\n.tasks-row input[type='checkbox'] {\n width: 16px;\n height: 16px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.tasks-row .tasks-title {\n flex: 1;\n min-width: 0;\n height: auto;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n text-align: left;\n border: 0;\n background: transparent;\n}\n\n.tasks-row .tasks-title:hover {\n background: transparent;\n}\n\n.tasks-row .tasks-title > span:first-child {\n font-size: 13px;\n color: var(--m-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.tasks-row.done .tasks-title > span:first-child {\n color: var(--m-text-3);\n text-decoration: line-through;\n}\n\n.tasks-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-meta .due.overdue {\n color: var(--m-danger);\n}\n\n.tasks-list-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- markdown ---------- */\n\n.mailharbor-markdown {\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.68;\n overflow-wrap: anywhere;\n}\n\n.mailharbor-markdown > *:first-child {\n margin-top: 0;\n}\n\n.mailharbor-markdown > *:last-child {\n margin-bottom: 0;\n}\n\n.mailharbor-markdown p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.mailharbor-markdown h1,\n.mailharbor-markdown h2,\n.mailharbor-markdown h3,\n.mailharbor-markdown h4,\n.mailharbor-markdown h5,\n.mailharbor-markdown h6 {\n margin: 4px 0 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.mailharbor-markdown ul,\n.mailharbor-markdown ol {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.mailharbor-markdown li::marker {\n color: var(--m-text-3);\n}\n\n.mailharbor-markdown a {\n color: var(--m-accent);\n text-decoration: none;\n}\n\n.mailharbor-markdown a:hover {\n text-decoration: underline;\n}\n\n.mailharbor-markdown code {\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--m-bg-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11.5px;\n}\n\n.mailharbor-markdown pre {\n margin: 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n overflow: auto;\n}\n\n.mailharbor-markdown pre code {\n padding: 0;\n background: transparent;\n}\n\n.mailharbor-markdown blockquote {\n margin: 0;\n padding-left: 10px;\n border-left: 2px solid var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor-markdown hr {\n width: 100%;\n margin: 4px 0;\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor.mailharbor-settings-page {\n height: auto;\n min-height: 100%;\n overflow: visible;\n}\n\n.mailharbor-settings {\n flex: 0 0 auto;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n padding: 18px 20px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n overflow-x: hidden;\n}\n\n.mailharbor-settings > * {\n min-width: 0;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n overflow: hidden;\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
38
39
 
39
40
  // src/client/icon.tsx
40
41
  var import_react = require("react");
@@ -161,6 +162,46 @@ var api = {
161
162
  method: "POST",
162
163
  body: JSON.stringify({ accountId: accountId ?? null })
163
164
  }),
165
+ tasks: (accountId, params = {}) => request(
166
+ `tasks${query({ accountId: accountId ?? null, ...params })}`
167
+ ),
168
+ createTask: (payload) => request("tasks", { method: "POST", body: JSON.stringify(payload) }),
169
+ updateTask: (id, patch) => request(`tasks/${encodeURIComponent(id)}`, {
170
+ method: "PATCH",
171
+ body: JSON.stringify(patch)
172
+ }),
173
+ deleteTask: (id) => request(`tasks/${encodeURIComponent(id)}`, { method: "DELETE" }),
174
+ extractTasks: (payload) => request("tasks/extract", {
175
+ method: "POST",
176
+ body: JSON.stringify(payload)
177
+ }),
178
+ taskCaldav: (accountId) => request(`tasks/caldav${query({ accountId: accountId ?? null })}`),
179
+ saveTaskCaldav: (payload) => request("tasks/caldav", {
180
+ method: "PUT",
181
+ body: JSON.stringify(payload)
182
+ }),
183
+ syncTasks: (accountId) => request("tasks/sync", { method: "POST", body: JSON.stringify({ accountId }) }),
184
+ projects: () => request("projects"),
185
+ projectFor: (accountId, key) => request(`projects/for${query({ accountId: accountId ?? null, key })}`),
186
+ createProject: (payload) => request("projects", {
187
+ method: "POST",
188
+ body: JSON.stringify(payload)
189
+ }),
190
+ addProjectMessage: (id, payload) => request(`projects/${encodeURIComponent(id)}/messages`, {
191
+ method: "POST",
192
+ body: JSON.stringify(payload)
193
+ }),
194
+ removeProjectMessage: (id, accountId, key) => request(
195
+ `projects/${encodeURIComponent(id)}/messages${query({ accountId: accountId ?? null, key })}`,
196
+ { method: "DELETE" }
197
+ ),
198
+ transferProject: (id, payload) => request(`projects/${encodeURIComponent(id)}/transfer`, {
199
+ method: "POST",
200
+ body: JSON.stringify(payload)
201
+ }),
202
+ lookupMessage: (accountId, key) => request(
203
+ `messages/lookup${query({ accountId: accountId ?? null, key })}`
204
+ ),
164
205
  settings: () => request("settings"),
165
206
  saveSettings: (patch) => request("settings", { method: "PATCH", body: JSON.stringify(patch) }),
166
207
  backupStatus: (accountId) => request(`backup/status${query({ accountId: accountId ?? null })}`),
@@ -255,65 +296,218 @@ function MailIcon() {
255
296
  }
256
297
 
257
298
  // src/client/panel.tsx
258
- var import_react8 = require("react");
299
+ var import_react9 = require("react");
259
300
 
260
301
  // src/client/ai-rail.tsx
261
- var import_react3 = require("react");
302
+ var import_react5 = require("react");
262
303
 
263
- // src/client/icons.tsx
304
+ // src/client/components.tsx
264
305
  var import_react2 = require("react");
306
+ var import_react_dom = require("react-dom");
307
+ function Avatar({ email, size = 26 }) {
308
+ const letter = (email.trim()[0] ?? "?").toUpperCase();
309
+ return (0, import_react2.createElement)(
310
+ "span",
311
+ {
312
+ className: "avatar",
313
+ style: {
314
+ width: `${String(size)}px`,
315
+ height: `${String(size)}px`,
316
+ fontSize: `${String(Math.round(size * 0.42))}px`
317
+ },
318
+ "aria-hidden": "true"
319
+ },
320
+ letter
321
+ );
322
+ }
323
+ function Field({
324
+ label,
325
+ children,
326
+ hint
327
+ }) {
328
+ return (0, import_react2.createElement)(
329
+ "div",
330
+ { className: "mailharbor-field" },
331
+ (0, import_react2.createElement)("label", null, label),
332
+ children,
333
+ hint === void 0 ? null : (0, import_react2.createElement)("span", { className: "small muted" }, hint)
334
+ );
335
+ }
336
+ function Toggle({
337
+ checked,
338
+ onChange,
339
+ label
340
+ }) {
341
+ return (0, import_react2.createElement)(
342
+ "label",
343
+ { className: "mailharbor-toggle" },
344
+ (0, import_react2.createElement)("input", {
345
+ type: "checkbox",
346
+ checked,
347
+ onChange: (event) => {
348
+ onChange(event.target.checked);
349
+ }
350
+ }),
351
+ label
352
+ );
353
+ }
354
+ function portalRoot() {
355
+ const existing = document.getElementById("mailharbor-portal");
356
+ if (existing !== null) return existing;
357
+ const element = document.createElement("div");
358
+ element.id = "mailharbor-portal";
359
+ element.className = "mailharbor mailharbor-portal";
360
+ document.body.append(element);
361
+ return element;
362
+ }
363
+ function Modal({
364
+ title,
365
+ children,
366
+ footer,
367
+ onClose,
368
+ wide = false
369
+ }) {
370
+ (0, import_react2.useEffect)(() => {
371
+ const onKey = (event) => {
372
+ if (event.key === "Escape") onClose();
373
+ };
374
+ window.addEventListener("keydown", onKey);
375
+ return () => {
376
+ window.removeEventListener("keydown", onKey);
377
+ };
378
+ }, [onClose]);
379
+ return (0, import_react_dom.createPortal)(
380
+ (0, import_react2.createElement)(
381
+ "div",
382
+ {
383
+ className: "mailharbor-modal-mask",
384
+ onMouseDown: (event) => {
385
+ if (event.target === event.currentTarget) onClose();
386
+ }
387
+ },
388
+ (0, import_react2.createElement)(
389
+ "div",
390
+ {
391
+ className: "mailharbor-modal",
392
+ style: wide ? { width: "min(920px, 100%)" } : void 0,
393
+ role: "dialog",
394
+ "aria-modal": "true",
395
+ "aria-label": title
396
+ },
397
+ (0, import_react2.createElement)(
398
+ "header",
399
+ null,
400
+ title,
401
+ (0, import_react2.createElement)("span", { className: "spacer" }),
402
+ (0, import_react2.createElement)("button", { className: "subtle", onClick: onClose, "aria-label": "\u5173\u95ED" }, "\u2715")
403
+ ),
404
+ (0, import_react2.createElement)("div", { className: "content" }, children),
405
+ footer === void 0 ? null : (0, import_react2.createElement)("footer", null, footer)
406
+ )
407
+ ),
408
+ portalRoot()
409
+ );
410
+ }
411
+ function ErrorBar({ message, onDismiss }) {
412
+ return (0, import_react2.createElement)(
413
+ "div",
414
+ { className: "error", role: "alert" },
415
+ (0, import_react2.createElement)("span", null, message),
416
+ (0, import_react2.createElement)("span", { className: "spacer" }),
417
+ (0, import_react2.createElement)("button", { className: "subtle", onClick: onDismiss }, "\u5173\u95ED")
418
+ );
419
+ }
420
+ function shortDate(iso) {
421
+ const date = new Date(iso);
422
+ if (Number.isNaN(date.getTime())) return iso.slice(0, 16);
423
+ const now = /* @__PURE__ */ new Date();
424
+ const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
425
+ const pad = (value) => String(value).padStart(2, "0");
426
+ if (sameDay) return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
427
+ if (date.getFullYear() === now.getFullYear()) return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
428
+ return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
429
+ }
430
+ function longDate(iso) {
431
+ const date = new Date(iso);
432
+ if (Number.isNaN(date.getTime())) return iso;
433
+ return date.toLocaleString(void 0, {
434
+ year: "numeric",
435
+ month: "2-digit",
436
+ day: "2-digit",
437
+ hour: "2-digit",
438
+ minute: "2-digit"
439
+ });
440
+ }
441
+ function formatBytes(size) {
442
+ if (!Number.isFinite(size) || size <= 0) return "";
443
+ if (size < 1024) return `${String(size)} B`;
444
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
445
+ return `${(size / 1024 / 1024).toFixed(1)} MB`;
446
+ }
447
+
448
+ // src/client/icons.tsx
449
+ var import_react3 = require("react");
265
450
  var PATHS = {
266
- refresh: (0, import_react2.createElement)("path", { d: "M20 11a8 8 0 1 0-2.3 5.7M20 5v6h-6" }),
267
- plus: (0, import_react2.createElement)("path", { d: "M12 5v14M5 12h14" }),
268
- compose: (0, import_react2.createElement)("path", { d: "M4 20h4l10.5-10.5a2.1 2.1 0 0 0-3-3L5 17v3zM13.5 6.5l3 3" }),
269
- archive: (0, import_react2.createElement)(
451
+ refresh: (0, import_react3.createElement)("path", { d: "M20 11a8 8 0 1 0-2.3 5.7M20 5v6h-6" }),
452
+ plus: (0, import_react3.createElement)("path", { d: "M12 5v14M5 12h14" }),
453
+ compose: (0, import_react3.createElement)("path", { d: "M4 20h4l10.5-10.5a2.1 2.1 0 0 0-3-3L5 17v3zM13.5 6.5l3 3" }),
454
+ archive: (0, import_react3.createElement)(
270
455
  "g",
271
456
  null,
272
- (0, import_react2.createElement)("rect", { x: 3, y: 4, width: 18, height: 5, rx: 1.5 }),
273
- (0, import_react2.createElement)("path", { d: "M5 9v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9M10 13h4" })
457
+ (0, import_react3.createElement)("rect", { x: 3, y: 4, width: 18, height: 5, rx: 1.5 }),
458
+ (0, import_react3.createElement)("path", { d: "M5 9v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9M10 13h4" })
274
459
  ),
275
- paperclip: (0, import_react2.createElement)("path", {
460
+ paperclip: (0, import_react3.createElement)("path", {
276
461
  d: "M20 11.5 12 19.5a5 5 0 0 1-7-7l8-8a3.3 3.3 0 0 1 4.7 4.7l-8 8a1.7 1.7 0 0 1-2.4-2.4l7.3-7.3"
277
462
  }),
278
- mail: (0, import_react2.createElement)(
463
+ mail: (0, import_react3.createElement)(
279
464
  "g",
280
465
  null,
281
- (0, import_react2.createElement)("rect", { x: 3, y: 5, width: 18, height: 14, rx: 2.5 }),
282
- (0, import_react2.createElement)("path", { d: "m4 7 7.1 5a1.6 1.6 0 0 0 1.8 0L20 7" })
466
+ (0, import_react3.createElement)("rect", { x: 3, y: 5, width: 18, height: 14, rx: 2.5 }),
467
+ (0, import_react3.createElement)("path", { d: "m4 7 7.1 5a1.6 1.6 0 0 0 1.8 0L20 7" })
283
468
  ),
284
- search: (0, import_react2.createElement)("path", { d: "M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM20 20l-4-4" }),
285
- reply: (0, import_react2.createElement)("path", { d: "M9 7 4 12l5 5M4 12h9a6 6 0 0 1 6 6v1" }),
286
- replyAll: (0, import_react2.createElement)("path", { d: "m9 7-5 5 5 5M14 7l-5 5 5 5M13 12h5a5 5 0 0 1 5 5" }),
287
- forward: (0, import_react2.createElement)("path", { d: "m15 7 5 5-5 5M20 12h-9a6 6 0 0 0-6 6v1" }),
288
- check: (0, import_react2.createElement)("path", { d: "m5 13 4 4L19 7" }),
289
- image: (0, import_react2.createElement)(
469
+ search: (0, import_react3.createElement)("path", { d: "M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM20 20l-4-4" }),
470
+ reply: (0, import_react3.createElement)("path", { d: "M9 7 4 12l5 5M4 12h9a6 6 0 0 1 6 6v1" }),
471
+ replyAll: (0, import_react3.createElement)("path", { d: "m9 7-5 5 5 5M14 7l-5 5 5 5M13 12h5a5 5 0 0 1 5 5" }),
472
+ forward: (0, import_react3.createElement)("path", { d: "m15 7 5 5-5 5M20 12h-9a6 6 0 0 0-6 6v1" }),
473
+ check: (0, import_react3.createElement)("path", { d: "m5 13 4 4L19 7" }),
474
+ image: (0, import_react3.createElement)(
290
475
  "g",
291
476
  null,
292
- (0, import_react2.createElement)("rect", { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
293
- (0, import_react2.createElement)("circle", { cx: 8.5, cy: 9.5, r: 1.5 }),
294
- (0, import_react2.createElement)("path", { d: "m4 18 5-5 4 4 3-3 4 4" })
477
+ (0, import_react3.createElement)("rect", { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
478
+ (0, import_react3.createElement)("circle", { cx: 8.5, cy: 9.5, r: 1.5 }),
479
+ (0, import_react3.createElement)("path", { d: "m4 18 5-5 4 4 3-3 4 4" })
295
480
  ),
296
- close: (0, import_react2.createElement)("path", { d: "M6 6l12 12M18 6 6 18" }),
297
- chevronLeft: (0, import_react2.createElement)("path", { d: "m14 6-6 6 6 6" }),
298
- chevronRight: (0, import_react2.createElement)("path", { d: "m10 6 6 6-6 6" }),
299
- sparkles: (0, import_react2.createElement)(
481
+ close: (0, import_react3.createElement)("path", { d: "M6 6l12 12M18 6 6 18" }),
482
+ chevronLeft: (0, import_react3.createElement)("path", { d: "m14 6-6 6 6 6" }),
483
+ chevronRight: (0, import_react3.createElement)("path", { d: "m10 6 6 6-6 6" }),
484
+ sparkles: (0, import_react3.createElement)(
300
485
  "g",
301
486
  null,
302
- (0, import_react2.createElement)("path", { d: "m12 3 1.8 4.6L18.5 9l-4.7 1.4L12 15l-1.8-4.6L5.5 9l4.7-1.4z" }),
303
- (0, import_react2.createElement)("path", { d: "M18.5 15.5 19.3 18l2.2.8-2.2.7-.8 2.5-.8-2.5-2.2-.7 2.2-.8z" })
487
+ (0, import_react3.createElement)("path", { d: "m12 3 1.8 4.6L18.5 9l-4.7 1.4L12 15l-1.8-4.6L5.5 9l4.7-1.4z" }),
488
+ (0, import_react3.createElement)("path", { d: "M18.5 15.5 19.3 18l2.2.8-2.2.7-.8 2.5-.8-2.5-2.2-.7 2.2-.8z" })
304
489
  ),
305
- trash: (0, import_react2.createElement)(
490
+ trash: (0, import_react3.createElement)(
306
491
  "g",
307
492
  null,
308
- (0, import_react2.createElement)("path", {
493
+ (0, import_react3.createElement)("path", {
309
494
  d: "M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6.5 7l.8 12a1 1 0 0 0 1 .9h7.4a1 1 0 0 0 1-.9l.8-12"
310
495
  }),
311
- (0, import_react2.createElement)("path", { d: "M10 11v6M14 11v6" })
496
+ (0, import_react3.createElement)("path", { d: "M10 11v6M14 11v6" })
312
497
  ),
313
- send: (0, import_react2.createElement)("path", { d: "m4 4 17 8-17 8 3.6-8z M7.6 12H21" })
498
+ send: (0, import_react3.createElement)("path", { d: "m4 4 17 8-17 8 3.6-8z M7.6 12H21" }),
499
+ folder: (0, import_react3.createElement)("path", {
500
+ d: "M3.5 6.5A1.5 1.5 0 0 1 5 5h4l2 2.5h8A1.5 1.5 0 0 1 20.5 9v8.5A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5z"
501
+ }),
502
+ checklist: (0, import_react3.createElement)(
503
+ "g",
504
+ null,
505
+ (0, import_react3.createElement)("path", { d: "M4 6.5 5.5 8 8 5M4 12.5 5.5 14 8 11M4 18.5 5.5 20 8 17" }),
506
+ (0, import_react3.createElement)("path", { d: "M11 6.5h9M11 12.5h9M11 18.5h9" })
507
+ )
314
508
  };
315
509
  function Icon({ name, size = 16 }) {
316
- return (0, import_react2.createElement)(
510
+ return (0, import_react3.createElement)(
317
511
  "svg",
318
512
  {
319
513
  width: size,
@@ -331,6 +525,114 @@ function Icon({ name, size = 16 }) {
331
525
  );
332
526
  }
333
527
 
528
+ // src/client/markdown.tsx
529
+ var import_react4 = require("react");
530
+ var INLINE = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)|\*\*([^*]+)\*\*|__([^_]+)__|`([^`]+)`|~~([^~]+)~~|\*([^*]+)\*|_([^_]+)_/g;
531
+ function inline(text, keyPrefix) {
532
+ const nodes = [];
533
+ let last = 0;
534
+ let index = 0;
535
+ INLINE.lastIndex = 0;
536
+ let match;
537
+ while ((match = INLINE.exec(text)) !== null) {
538
+ if (match.index > last) nodes.push(text.slice(last, match.index));
539
+ const key = `${keyPrefix}-${String(index++)}`;
540
+ if (match[1] !== void 0 && match[2] !== void 0) {
541
+ nodes.push((0, import_react4.createElement)("a", { key, href: match[2], target: "_blank", rel: "noreferrer noopener" }, match[1]));
542
+ } else if (match[3] !== void 0) {
543
+ nodes.push((0, import_react4.createElement)("strong", { key }, match[3]));
544
+ } else if (match[4] !== void 0) {
545
+ nodes.push((0, import_react4.createElement)("strong", { key }, match[4]));
546
+ } else if (match[5] !== void 0) {
547
+ nodes.push((0, import_react4.createElement)("code", { key }, match[5]));
548
+ } else if (match[6] !== void 0) {
549
+ nodes.push((0, import_react4.createElement)("del", { key }, match[6]));
550
+ } else if (match[7] !== void 0) {
551
+ nodes.push((0, import_react4.createElement)("em", { key }, match[7]));
552
+ } else if (match[8] !== void 0) {
553
+ nodes.push((0, import_react4.createElement)("em", { key }, match[8]));
554
+ }
555
+ last = INLINE.lastIndex;
556
+ }
557
+ if (last < text.length) nodes.push(text.slice(last));
558
+ return nodes;
559
+ }
560
+ function Markdown({ text }) {
561
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
562
+ const blocks = [];
563
+ let index = 0;
564
+ let key = 0;
565
+ const isBlockStart = (line) => /^```/.test(line) || /^#{1,6}\s+/.test(line) || /^>\s?/.test(line) || /^([-*+]|\d+\.)\s+/.test(line) || /^(-{3,}|\*{3,}|_{3,})$/.test(line);
566
+ while (index < lines.length) {
567
+ const line = lines[index] ?? "";
568
+ const k = `b${String(key++)}`;
569
+ if (line.trim() === "") {
570
+ index += 1;
571
+ continue;
572
+ }
573
+ if (/^```/.test(line)) {
574
+ const code = [];
575
+ index += 1;
576
+ while (index < lines.length && !/^```/.test(lines[index] ?? "")) {
577
+ code.push(lines[index] ?? "");
578
+ index += 1;
579
+ }
580
+ index += 1;
581
+ blocks.push((0, import_react4.createElement)("pre", { key: k }, (0, import_react4.createElement)("code", null, code.join("\n"))));
582
+ continue;
583
+ }
584
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(line.trim())) {
585
+ blocks.push((0, import_react4.createElement)("hr", { key: k }));
586
+ index += 1;
587
+ continue;
588
+ }
589
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
590
+ if (heading !== null) {
591
+ const level = Math.min(6, (heading[1] ?? "#").length);
592
+ blocks.push((0, import_react4.createElement)(`h${String(level)}`, { key: k }, inline(heading[2] ?? "", k)));
593
+ index += 1;
594
+ continue;
595
+ }
596
+ if (/^>\s?/.test(line)) {
597
+ const quote = [];
598
+ while (index < lines.length && /^>\s?/.test(lines[index] ?? "")) {
599
+ quote.push((lines[index] ?? "").replace(/^>\s?/, ""));
600
+ index += 1;
601
+ }
602
+ blocks.push((0, import_react4.createElement)("blockquote", { key: k }, (0, import_react4.createElement)("p", null, inline(quote.join(" "), k))));
603
+ continue;
604
+ }
605
+ if (/^([-*+]|\d+\.)\s+/.test(line)) {
606
+ const ordered = /^\d+\.\s+/.test(line);
607
+ const items = [];
608
+ while (index < lines.length && /^([-*+]|\d+\.)\s+/.test(lines[index] ?? "")) {
609
+ items.push((lines[index] ?? "").replace(/^([-*+]|\d+\.)\s+/, ""));
610
+ index += 1;
611
+ }
612
+ blocks.push(
613
+ (0, import_react4.createElement)(
614
+ ordered ? "ol" : "ul",
615
+ { key: k },
616
+ items.map(
617
+ (item, itemIndex) => (0, import_react4.createElement)("li", { key: `${k}-${String(itemIndex)}` }, inline(item, `${k}-${String(itemIndex)}`))
618
+ )
619
+ )
620
+ );
621
+ continue;
622
+ }
623
+ const paragraph = [];
624
+ while (index < lines.length) {
625
+ const current = lines[index] ?? "";
626
+ if (current.trim() === "" || paragraph.length > 0 && isBlockStart(current)) break;
627
+ paragraph.push(current);
628
+ index += 1;
629
+ }
630
+ blocks.push((0, import_react4.createElement)("p", { key: k }, inline(paragraph.join(" "), k)));
631
+ }
632
+ if (blocks.length === 0) return null;
633
+ return (0, import_react4.createElement)("div", { className: "mailharbor-markdown" }, blocks);
634
+ }
635
+
334
636
  // src/client/ai-rail.tsx
335
637
  function errorText(error) {
336
638
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
@@ -386,17 +688,23 @@ function AiRail({
386
688
  onSaveDraft,
387
689
  onNotice
388
690
  }) {
389
- const [status, setStatus] = (0, import_react3.useState)(null);
390
- const [thread, setThread] = (0, import_react3.useState)(null);
391
- const [loading, setLoading] = (0, import_react3.useState)(false);
392
- const [generating, setGenerating] = (0, import_react3.useState)(null);
393
- const [error, setError] = (0, import_react3.useState)("");
394
- const [question, setQuestion] = (0, import_react3.useState)("");
395
- const [confirmClear, setConfirmClear] = (0, import_react3.useState)(false);
396
- const list = (0, import_react3.useRef)(null);
397
- const lastNonce = (0, import_react3.useRef)(0);
691
+ const [status, setStatus] = (0, import_react5.useState)(null);
692
+ const [thread, setThread] = (0, import_react5.useState)(null);
693
+ const [loading, setLoading] = (0, import_react5.useState)(false);
694
+ const [generating, setGenerating] = (0, import_react5.useState)(null);
695
+ const [error, setError] = (0, import_react5.useState)("");
696
+ const [question, setQuestion] = (0, import_react5.useState)("");
697
+ const [confirmClear, setConfirmClear] = (0, import_react5.useState)(false);
698
+ const [project, setProject] = (0, import_react5.useState)(null);
699
+ const [projectOpen, setProjectOpen] = (0, import_react5.useState)(false);
700
+ const [projectName, setProjectName] = (0, import_react5.useState)("");
701
+ const [projectRoot, setProjectRoot] = (0, import_react5.useState)("");
702
+ const [projectBusy, setProjectBusy] = (0, import_react5.useState)(false);
703
+ const [projectWarnings, setProjectWarnings] = (0, import_react5.useState)([]);
704
+ const list = (0, import_react5.useRef)(null);
705
+ const lastNonce = (0, import_react5.useRef)(0);
398
706
  const key = detail === null ? "" : threadKey(detail, source);
399
- (0, import_react3.useEffect)(() => {
707
+ (0, import_react5.useEffect)(() => {
400
708
  if (account === null) return;
401
709
  let cancelled = false;
402
710
  api.aiStatus(account.id).then((next) => {
@@ -409,7 +717,7 @@ function AiRail({
409
717
  cancelled = true;
410
718
  };
411
719
  }, [account]);
412
- (0, import_react3.useEffect)(() => {
720
+ (0, import_react5.useEffect)(() => {
413
721
  if (account === null || key === "") {
414
722
  setThread(null);
415
723
  return;
@@ -427,7 +735,7 @@ function AiRail({
427
735
  cancelled = true;
428
736
  };
429
737
  }, [account, key]);
430
- const generate = (0, import_react3.useCallback)(
738
+ const generate = (0, import_react5.useCallback)(
431
739
  async (kind, extra = {}) => {
432
740
  if (detail === null || account === null) return;
433
741
  setGenerating(kind);
@@ -451,15 +759,85 @@ function AiRail({
451
759
  },
452
760
  [detail, account, key, source, mailbox]
453
761
  );
454
- (0, import_react3.useEffect)(() => {
762
+ (0, import_react5.useEffect)(() => {
455
763
  if (request2 === null || request2.nonce === lastNonce.current) return;
456
764
  lastNonce.current = request2.nonce;
457
765
  if (detail !== null) void generate(request2.kind);
458
766
  }, [request2]);
459
- (0, import_react3.useEffect)(() => {
767
+ (0, import_react5.useEffect)(() => {
460
768
  const element = list.current;
461
769
  if (element !== null) element.scrollTop = element.scrollHeight;
462
770
  }, [thread, generating]);
771
+ (0, import_react5.useEffect)(() => {
772
+ if (detail === null || account === null) {
773
+ setProject(null);
774
+ return;
775
+ }
776
+ let cancelled = false;
777
+ api.projectFor(detail.accountId, key).then((result) => {
778
+ if (!cancelled) setProject(result.project);
779
+ }).catch(() => {
780
+ if (!cancelled) setProject(null);
781
+ });
782
+ return () => {
783
+ cancelled = true;
784
+ };
785
+ }, [detail, account, key, thread]);
786
+ const openProject = async () => {
787
+ setProjectOpen(true);
788
+ setProjectWarnings([]);
789
+ try {
790
+ const settings = await api.settings();
791
+ setProjectRoot(settings.projects.root);
792
+ setProjectName(detail?.subject.replace(/^(re|fwd):\s*/i, "").slice(0, 80) ?? "");
793
+ } catch {
794
+ }
795
+ };
796
+ const conversation = () => (thread?.cards ?? []).map(
797
+ (card) => `### ${KIND_LABEL[card.kind] ?? "AI"} \xB7 ${new Date(card.createdAt).toLocaleString()}
798
+
799
+ ${card.output}`
800
+ ).join("\n\n");
801
+ const transfer = async () => {
802
+ if (project === null || detail === null || account === null) return;
803
+ setProjectBusy(true);
804
+ setProjectWarnings([]);
805
+ try {
806
+ await api.transferProject(project.id, {
807
+ accountId: account.id,
808
+ key,
809
+ conversation: conversation() === "" ? "\uFF08\u6682\u65E0\u65B0\u7684 AI \u8BB0\u5F55\uFF09" : conversation()
810
+ });
811
+ onNotice("\u5DF2\u8FFD\u52A0\u5230\u9879\u76EE\u5BF9\u8BDD");
812
+ setProjectOpen(false);
813
+ } catch (caught) {
814
+ setProjectWarnings([errorText(caught)]);
815
+ } finally {
816
+ setProjectBusy(false);
817
+ }
818
+ };
819
+ const create = async () => {
820
+ if (detail === null || account === null) return;
821
+ setProjectBusy(true);
822
+ setProjectWarnings([]);
823
+ try {
824
+ const result = await api.createProject({
825
+ accountId: account.id,
826
+ name: projectName.trim() === "" ? detail.subject : projectName.trim(),
827
+ root: projectRoot,
828
+ conversation: conversation() === "" ? "\uFF08\u8FD8\u6CA1\u6709 AI \u8BB0\u5F55\uFF09" : conversation(),
829
+ message: { key, subject: detail.subject, date: detail.date }
830
+ });
831
+ setProject(result.project);
832
+ setProjectWarnings(result.warnings);
833
+ onNotice(`\u9879\u76EE\u5DF2\u521B\u5EFA\uFF1A${result.project.path}`);
834
+ if (result.warnings.length === 0) setProjectOpen(false);
835
+ } catch (caught) {
836
+ setProjectWarnings([errorText(caught)]);
837
+ } finally {
838
+ setProjectBusy(false);
839
+ }
840
+ };
463
841
  const replyInit = (card) => {
464
842
  const subject = card.meta?.subject ?? (detail === null ? "" : /^re:/i.test(detail.subject) ? detail.subject : `Re: ${detail.subject}`);
465
843
  return {
@@ -502,54 +880,58 @@ function AiRail({
502
880
  };
503
881
  const disabled = status !== null && (!status.available || !status.enabled);
504
882
  const cards = thread?.cards ?? [];
505
- return (0, import_react3.createElement)(
883
+ return (0, import_react5.createElement)(
506
884
  "aside",
507
885
  { className: "mailharbor-ai", "aria-label": "AI \u52A9\u624B" },
508
- (0, import_react3.createElement)(
886
+ (0, import_react5.createElement)(
509
887
  "div",
510
888
  { className: "mailharbor-ai-head" },
511
- (0, import_react3.createElement)(Icon, { name: "sparkles", size: 15 }),
512
- (0, import_react3.createElement)("strong", null, "AI \u52A9\u624B"),
513
- status === null || status.model === "" ? null : (0, import_react3.createElement)("span", { className: "badge" }, status.model),
514
- (0, import_react3.createElement)("span", { className: "spacer" }),
515
- confirmClear ? (0, import_react3.createElement)(
889
+ (0, import_react5.createElement)(Icon, { name: "sparkles", size: 15 }),
890
+ (0, import_react5.createElement)("strong", null, "AI \u52A9\u624B"),
891
+ status === null || status.model === "" ? null : (0, import_react5.createElement)("span", { className: "badge" }, status.model),
892
+ (0, import_react5.createElement)("span", { className: "spacer" }),
893
+ confirmClear ? (0, import_react5.createElement)(
516
894
  "span",
517
895
  { className: "mailharbor-ai-confirm" },
518
- (0, import_react3.createElement)("button", { className: "subtle", onClick: () => void clear() }, "\u786E\u8BA4\u6E05\u9664"),
519
- (0, import_react3.createElement)("button", { className: "subtle", onClick: () => setConfirmClear(false) }, "\u53D6\u6D88")
520
- ) : (0, import_react3.createElement)(
896
+ (0, import_react5.createElement)("button", { className: "subtle", onClick: () => void clear() }, "\u786E\u8BA4\u6E05\u9664"),
897
+ (0, import_react5.createElement)("button", { className: "subtle", onClick: () => setConfirmClear(false) }, "\u53D6\u6D88")
898
+ ) : (0, import_react5.createElement)(
521
899
  "button",
522
900
  {
523
901
  className: "subtle icon",
524
902
  title: cards.length === 0 ? "\u5173\u95ED" : "\u6E05\u9664\u8FD9\u5C01\u90AE\u4EF6\u7684\u8BB0\u5F55",
525
903
  onClick: () => cards.length === 0 ? onClose() : setConfirmClear(true)
526
904
  },
527
- (0, import_react3.createElement)(Icon, { name: cards.length === 0 ? "close" : "trash", size: 14 })
905
+ (0, import_react5.createElement)(Icon, { name: cards.length === 0 ? "close" : "trash", size: 14 })
528
906
  ),
529
- (0, import_react3.createElement)(
907
+ (0, import_react5.createElement)(
530
908
  "button",
531
909
  { className: "subtle icon", title: "\u6536\u8D77", onClick: onClose },
532
- (0, import_react3.createElement)(Icon, { name: "chevronRight", size: 14 })
910
+ (0, import_react5.createElement)(Icon, { name: "chevronRight", size: 14 })
533
911
  )
534
912
  ),
535
- detail === null ? (0, import_react3.createElement)(
913
+ detail === null ? (0, import_react5.createElement)(
536
914
  "div",
537
915
  { className: "mailharbor-ai-empty" },
538
- (0, import_react3.createElement)(Icon, { name: "sparkles", size: 26 }),
539
- (0, import_react3.createElement)("div", { className: "title" }, "\u5148\u9009\u4E00\u5C01\u90AE\u4EF6"),
540
- (0, import_react3.createElement)("div", { className: "small" }, "\u9009\u4E2D\u90AE\u4EF6\u540E\uFF0C\u53EF\u5728\u8FD9\u91CC\u751F\u6210\u6458\u8981\u3001\u56DE\u590D\u548C\u7FFB\u8BD1\uFF0C\u8BB0\u5F55\u4F1A\u4E00\u76F4\u4FDD\u7559\u3002")
541
- ) : (0, import_react3.createElement)(
916
+ (0, import_react5.createElement)(Icon, { name: "sparkles", size: 26 }),
917
+ (0, import_react5.createElement)("div", { className: "title" }, "\u5148\u9009\u4E00\u5C01\u90AE\u4EF6"),
918
+ (0, import_react5.createElement)("div", { className: "small" }, "\u9009\u4E2D\u90AE\u4EF6\u540E\uFF0C\u53EF\u5728\u8FD9\u91CC\u751F\u6210\u6458\u8981\u3001\u56DE\u590D\u548C\u7FFB\u8BD1\uFF0C\u8BB0\u5F55\u4F1A\u4E00\u76F4\u4FDD\u7559\u3002")
919
+ ) : (0, import_react5.createElement)(
542
920
  "div",
543
921
  { className: "mailharbor-ai-context" },
544
- (0, import_react3.createElement)("span", { className: "small" }, "\u8DDF\u968F\u5F53\u524D\u90AE\u4EF6"),
545
- (0, import_react3.createElement)("strong", null, detail.subject),
546
- (0, import_react3.createElement)("span", { className: "small muted" }, "AI \u53EA\u770B\u5230\u8FD9\u5C01\u90AE\u4EF6")
922
+ (0, import_react5.createElement)("span", { className: "small" }, "\u8DDF\u968F\u5F53\u524D\u90AE\u4EF6"),
923
+ (0, import_react5.createElement)("strong", null, detail.subject),
924
+ (0, import_react5.createElement)(
925
+ "span",
926
+ { className: "small muted" },
927
+ project === null ? "AI \u53EA\u770B\u5230\u8FD9\u5C01\u90AE\u4EF6" : `\u9879\u76EE\uFF1A${project.name}`
928
+ )
547
929
  ),
548
- disabled && detail !== null ? (0, import_react3.createElement)("div", { className: "mailharbor-ai-warn" }, status?.error ?? "AI \u5DF2\u5173\u95ED\uFF0C\u8BF7\u5230\u8BBE\u7F6E\u91CC\u5F00\u542F") : null,
549
- detail === null ? null : (0, import_react3.createElement)(
930
+ disabled && detail !== null ? (0, import_react5.createElement)("div", { className: "mailharbor-ai-warn" }, status?.error ?? "AI \u5DF2\u5173\u95ED\uFF0C\u8BF7\u5230\u8BBE\u7F6E\u91CC\u5F00\u542F") : null,
931
+ detail === null ? null : (0, import_react5.createElement)(
550
932
  "div",
551
933
  { className: "mailharbor-ai-quick" },
552
- (0, import_react3.createElement)(
934
+ (0, import_react5.createElement)(
553
935
  "button",
554
936
  {
555
937
  className: "ghost",
@@ -558,7 +940,7 @@ function AiRail({
558
940
  },
559
941
  generating === "summary" ? "\u603B\u7ED3\u4E2D\u2026" : "\u6458\u8981"
560
942
  ),
561
- (0, import_react3.createElement)(
943
+ (0, import_react5.createElement)(
562
944
  "button",
563
945
  {
564
946
  className: "ghost",
@@ -567,7 +949,7 @@ function AiRail({
567
949
  },
568
950
  generating === "reply" ? "\u8D77\u8349\u4E2D\u2026" : "\u56DE\u590D"
569
951
  ),
570
- (0, import_react3.createElement)(
952
+ (0, import_react5.createElement)(
571
953
  "button",
572
954
  {
573
955
  className: "ghost",
@@ -575,58 +957,64 @@ function AiRail({
575
957
  onClick: () => void generate("translate", { target: "en" })
576
958
  },
577
959
  generating === "translate" ? "\u7FFB\u8BD1\u4E2D\u2026" : "\u7FFB\u8BD1"
960
+ ),
961
+ (0, import_react5.createElement)(
962
+ "button",
963
+ { className: "ghost", onClick: () => void openProject() },
964
+ (0, import_react5.createElement)(Icon, { name: "folder", size: 13 }),
965
+ "\u9879\u76EE"
578
966
  )
579
967
  ),
580
- (0, import_react3.createElement)(
968
+ (0, import_react5.createElement)(
581
969
  "div",
582
970
  { className: "mailharbor-ai-list", ref: list },
583
- loading ? (0, import_react3.createElement)("div", { className: "mailharbor-ai-empty small" }, "\u6B63\u5728\u8BFB\u53D6\u8BB0\u5F55\u2026") : null,
584
- !loading && detail !== null && cards.length === 0 && generating === null ? (0, import_react3.createElement)(
971
+ loading ? (0, import_react5.createElement)("div", { className: "mailharbor-ai-empty small" }, "\u6B63\u5728\u8BFB\u53D6\u8BB0\u5F55\u2026") : null,
972
+ !loading && detail !== null && cards.length === 0 && generating === null ? (0, import_react5.createElement)(
585
973
  "div",
586
974
  { className: "mailharbor-ai-empty" },
587
- (0, import_react3.createElement)("div", { className: "title" }, "\u8FD9\u5C01\u90AE\u4EF6\u8FD8\u6CA1\u6709 AI \u8BB0\u5F55"),
588
- (0, import_react3.createElement)(
975
+ (0, import_react5.createElement)("div", { className: "title" }, "\u8FD9\u5C01\u90AE\u4EF6\u8FD8\u6CA1\u6709 AI \u8BB0\u5F55"),
976
+ (0, import_react5.createElement)(
589
977
  "div",
590
978
  { className: "small" },
591
979
  "\u70B9\u4E0A\u9762\u7684\u300C\u6458\u8981\u300D\u5F00\u59CB\uFF1B\u751F\u6210\u7684\u5185\u5BB9\u4F1A\u4FDD\u5B58\u5728\u8FD9\u91CC\uFF0C\u5173\u6389\u9762\u677F\u4E5F\u4E0D\u4F1A\u4E22\u3002"
592
980
  )
593
981
  ) : null,
594
982
  cards.map(
595
- (card) => (0, import_react3.createElement)(
983
+ (card) => (0, import_react5.createElement)(
596
984
  "article",
597
985
  { className: "mailharbor-ai-card", key: card.id },
598
- (0, import_react3.createElement)(
986
+ (0, import_react5.createElement)(
599
987
  "div",
600
988
  { className: "mailharbor-ai-card-head" },
601
- (0, import_react3.createElement)("span", null, KIND_LABEL[card.kind] ?? "AI"),
602
- (0, import_react3.createElement)("span", { className: "muted small" }, new Date(card.createdAt).toLocaleTimeString()),
603
- (0, import_react3.createElement)("span", { className: "spacer" }),
604
- (0, import_react3.createElement)(
989
+ (0, import_react5.createElement)("span", null, KIND_LABEL[card.kind] ?? "AI"),
990
+ (0, import_react5.createElement)("span", { className: "muted small" }, new Date(card.createdAt).toLocaleTimeString()),
991
+ (0, import_react5.createElement)("span", { className: "spacer" }),
992
+ (0, import_react5.createElement)(
605
993
  "button",
606
994
  {
607
995
  className: "subtle icon",
608
996
  title: "\u5220\u9664",
609
997
  onClick: () => void remove(card)
610
998
  },
611
- (0, import_react3.createElement)(Icon, { name: "close", size: 12 })
999
+ (0, import_react5.createElement)(Icon, { name: "close", size: 12 })
612
1000
  )
613
1001
  ),
614
- card.kind === "summary" ? (0, import_react3.createElement)(
1002
+ card.kind === "summary" ? (0, import_react5.createElement)(
615
1003
  "div",
616
1004
  { className: "mailharbor-ai-summary" },
617
1005
  summaryBlocks(card.output).map(
618
- (block, index) => (0, import_react3.createElement)(
1006
+ (block, index) => (0, import_react5.createElement)(
619
1007
  "section",
620
1008
  { key: `${block.title}:${String(index)}` },
621
- block.title === "" ? null : (0, import_react3.createElement)("h4", null, block.title),
622
- (0, import_react3.createElement)("p", null, block.body)
1009
+ block.title === "" ? null : (0, import_react5.createElement)("h4", null, block.title),
1010
+ (0, import_react5.createElement)(Markdown, { text: block.body })
623
1011
  )
624
1012
  )
625
- ) : (0, import_react3.createElement)("div", { className: "mailharbor-ai-text" }, card.output),
626
- (0, import_react3.createElement)(
1013
+ ) : (0, import_react5.createElement)("div", { className: "mailharbor-ai-text" }, (0, import_react5.createElement)(Markdown, { text: card.output })),
1014
+ (0, import_react5.createElement)(
627
1015
  "div",
628
1016
  { className: "mailharbor-ai-card-actions" },
629
- card.kind === "summary" ? (0, import_react3.createElement)(
1017
+ card.kind === "summary" ? (0, import_react5.createElement)(
630
1018
  "button",
631
1019
  {
632
1020
  className: "ghost",
@@ -635,13 +1023,13 @@ function AiRail({
635
1023
  },
636
1024
  "\u7528\u8FD9\u4E9B\u8981\u70B9\u56DE\u590D"
637
1025
  ) : null,
638
- card.kind === "reply" || card.kind === "translate" ? (0, import_react3.createElement)(
1026
+ card.kind === "reply" || card.kind === "translate" ? (0, import_react5.createElement)(
639
1027
  "button",
640
1028
  { className: "ghost", onClick: () => onInsert(replyInit(card)) },
641
1029
  card.kind === "translate" ? "\u63D2\u5165\u56DE\u590D" : "\u63D2\u5165\u6B63\u6587"
642
1030
  ) : null,
643
- card.kind === "reply" ? (0, import_react3.createElement)("button", { className: "ghost", onClick: () => onSaveDraft(replyInit(card)) }, "\u5B58\u8349\u7A3F") : null,
644
- card.kind === "answer" ? (0, import_react3.createElement)(
1031
+ card.kind === "reply" ? (0, import_react5.createElement)("button", { className: "ghost", onClick: () => onSaveDraft(replyInit(card)) }, "\u5B58\u8349\u7A3F") : null,
1032
+ card.kind === "answer" ? (0, import_react5.createElement)(
645
1033
  "button",
646
1034
  {
647
1035
  className: "ghost",
@@ -649,196 +1037,125 @@ function AiRail({
649
1037
  },
650
1038
  "\u7EE7\u7EED\u8FFD\u95EE"
651
1039
  ) : null,
652
- (0, import_react3.createElement)("span", { className: "spacer" }),
653
- (0, import_react3.createElement)("button", { className: "subtle", onClick: () => copy(card) }, "\u590D\u5236")
1040
+ (0, import_react5.createElement)("span", { className: "spacer" }),
1041
+ (0, import_react5.createElement)("button", { className: "subtle", onClick: () => copy(card) }, "\u590D\u5236")
654
1042
  )
655
1043
  )
656
1044
  ),
657
- generating !== null ? (0, import_react3.createElement)(
1045
+ generating !== null ? (0, import_react5.createElement)(
658
1046
  "article",
659
1047
  { className: "mailharbor-ai-card generating" },
660
- (0, import_react3.createElement)("div", { className: "mailharbor-ai-card-head" }, (0, import_react3.createElement)("span", null, KIND_LABEL[generating])),
661
- (0, import_react3.createElement)("div", { className: "mailharbor-ai-text muted" }, "\u6B63\u5728\u751F\u6210\u2026")
1048
+ (0, import_react5.createElement)("div", { className: "mailharbor-ai-card-head" }, (0, import_react5.createElement)("span", null, KIND_LABEL[generating])),
1049
+ (0, import_react5.createElement)("div", { className: "mailharbor-ai-text muted" }, "\u6B63\u5728\u751F\u6210\u2026")
662
1050
  ) : null,
663
- error === "" ? null : (0, import_react3.createElement)("div", { className: "mailharbor-ai-error" }, error)
1051
+ error === "" ? null : (0, import_react5.createElement)("div", { className: "mailharbor-ai-error" }, error)
664
1052
  ),
665
- detail === null ? null : (0, import_react3.createElement)(
666
- "form",
1053
+ projectOpen ? (0, import_react5.createElement)(
1054
+ Modal,
667
1055
  {
668
- className: "mailharbor-ai-ask",
669
- onSubmit: (event) => {
670
- event.preventDefault();
671
- void ask();
672
- }
1056
+ title: project === null ? "\u8F6C\u5230\u9879\u76EE" : `\u9879\u76EE\uFF1A${project.name}`,
1057
+ onClose: () => setProjectOpen(false),
1058
+ footer: (0, import_react5.createElement)(
1059
+ "div",
1060
+ { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
1061
+ projectWarnings.length === 0 ? (0, import_react5.createElement)(
1062
+ "span",
1063
+ { className: "small muted" },
1064
+ project === null ? "\u628A\u5F53\u524D AI \u8BB0\u5F55\u5E26\u8FDB\u4E00\u4E2A\u65B0\u5DE5\u4F5C\u7A7A\u95F4" : "\u628A\u5F53\u524D AI \u8BB0\u5F55\u8FFD\u52A0\u5230\u9879\u76EE\u5BF9\u8BDD"
1065
+ ) : (0, import_react5.createElement)(
1066
+ "span",
1067
+ { className: "small", style: { color: "var(--m-warn)" } },
1068
+ projectWarnings.join("\uFF1B")
1069
+ ),
1070
+ (0, import_react5.createElement)("span", { className: "spacer" }),
1071
+ (0, import_react5.createElement)("button", { className: "ghost", onClick: () => setProjectOpen(false) }, "\u5173\u95ED"),
1072
+ project === null ? (0, import_react5.createElement)(
1073
+ "button",
1074
+ { className: "primary", disabled: projectBusy, onClick: () => void create() },
1075
+ projectBusy ? "\u521B\u5EFA\u4E2D\u2026" : "\u521B\u5EFA\u9879\u76EE"
1076
+ ) : (0, import_react5.createElement)(
1077
+ "button",
1078
+ { className: "primary", disabled: projectBusy, onClick: () => void transfer() },
1079
+ projectBusy ? "\u8FFD\u52A0\u4E2D\u2026" : "\u8FFD\u52A0\u5F53\u524D\u8BB0\u5F55"
1080
+ )
1081
+ )
673
1082
  },
674
- (0, import_react3.createElement)("input", {
675
- value: question,
676
- placeholder: "\u95EE\u8FD9\u5C01\u90AE\u4EF6\uFF0C\u4F8B\u5982\uFF1A\u8C01\u9700\u8981\u5728\u4EC0\u4E48\u65F6\u5019\u505A\u4EC0\u4E48\uFF1F",
677
- "aria-label": "\u95EE AI",
678
- disabled: disabled || generating !== null,
679
- onChange: (event) => setQuestion(event.target.value)
680
- }),
681
- (0, import_react3.createElement)(
682
- "button",
683
- {
684
- className: "primary icon",
685
- type: "submit",
686
- disabled: disabled || generating !== null || question.trim() === ""
687
- },
688
- (0, import_react3.createElement)(Icon, { name: "send", size: 14 })
1083
+ project === null ? (0, import_react5.createElement)(
1084
+ "div",
1085
+ { style: { display: "flex", flexDirection: "column", gap: "10px" } },
1086
+ (0, import_react5.createElement)(
1087
+ "div",
1088
+ { className: "mailharbor-field" },
1089
+ (0, import_react5.createElement)("label", null, "\u9879\u76EE\u540D\u79F0"),
1090
+ (0, import_react5.createElement)("input", {
1091
+ value: projectName,
1092
+ onChange: (event) => setProjectName(event.target.value)
1093
+ })
1094
+ ),
1095
+ (0, import_react5.createElement)(
1096
+ "div",
1097
+ { className: "mailharbor-field" },
1098
+ (0, import_react5.createElement)("label", null, "\u9879\u76EE\u76EE\u5F55\uFF08\u9ED8\u8BA4\u53EF\u5728\u8BBE\u7F6E\u91CC\u6539\uFF09"),
1099
+ (0, import_react5.createElement)("input", {
1100
+ value: projectRoot,
1101
+ onChange: (event) => setProjectRoot(event.target.value)
1102
+ })
1103
+ ),
1104
+ (0, import_react5.createElement)(
1105
+ "p",
1106
+ { className: "small muted" },
1107
+ `\u4F1A\u521B\u5EFA\u300C${projectName.trim() === "" ? "\u9879\u76EE" : projectName.trim()}\u300D\u6587\u4EF6\u5939\uFF0C\u5199\u5165 BRIEF.md \u548C\u5F53\u524D ${String(thread?.cards.length ?? 0)} \u6761 AI \u8BB0\u5F55\uFF0C\u5E76\u5728\u4FA7\u680F\u6CE8\u518C\u5DE5\u4F5C\u7A7A\u95F4\u3001\u5F00\u4E00\u4E2A\u4F1A\u8BDD\u3002`
1108
+ )
1109
+ ) : (0, import_react5.createElement)(
1110
+ "div",
1111
+ { style: { display: "flex", flexDirection: "column", gap: "8px" } },
1112
+ (0, import_react5.createElement)(
1113
+ "dl",
1114
+ { className: "mailharbor-kv" },
1115
+ (0, import_react5.createElement)("dt", null, "\u76EE\u5F55"),
1116
+ (0, import_react5.createElement)("dd", null, project.path),
1117
+ (0, import_react5.createElement)("dt", null, "AI \u8BB0\u5F55"),
1118
+ (0, import_react5.createElement)("dd", null, `${String(thread?.cards.length ?? 0)} \u6761`)
1119
+ ),
1120
+ project.sessionId === "" ? (0, import_react5.createElement)("p", { className: "small muted" }, "\u4F1A\u8BDD\u672A\u521B\u5EFA\uFF1A\u5F53\u524D\u7EC4\u5408\u6CA1\u6709 sessions \u670D\u52A1\u3002") : null,
1121
+ (0, import_react5.createElement)(
1122
+ "p",
1123
+ { className: "small muted" },
1124
+ "\u300C\u8FFD\u52A0\u5F53\u524D\u8BB0\u5F55\u300D\u4F1A\u628A\u5DE5\u4F5C\u53F0\u91CC\u7684\u5361\u7247\u5199\u5165\u9879\u76EE\u7684 CONVERSATION.md\u3002"
1125
+ )
689
1126
  )
690
- )
691
- );
692
- }
693
-
694
- // src/client/account-form.tsx
695
- var import_react5 = require("react");
696
-
697
- // src/client/components.tsx
698
- var import_react4 = require("react");
699
- var import_react_dom = require("react-dom");
700
- function Avatar({ email, size = 26 }) {
701
- const letter = (email.trim()[0] ?? "?").toUpperCase();
702
- return (0, import_react4.createElement)(
703
- "span",
704
- {
705
- className: "avatar",
706
- style: {
707
- width: `${String(size)}px`,
708
- height: `${String(size)}px`,
709
- fontSize: `${String(Math.round(size * 0.42))}px`
710
- },
711
- "aria-hidden": "true"
712
- },
713
- letter
714
- );
715
- }
716
- function Field({
717
- label,
718
- children,
719
- hint
720
- }) {
721
- return (0, import_react4.createElement)(
722
- "div",
723
- { className: "mailharbor-field" },
724
- (0, import_react4.createElement)("label", null, label),
725
- children,
726
- hint === void 0 ? null : (0, import_react4.createElement)("span", { className: "small muted" }, hint)
727
- );
728
- }
729
- function Toggle({
730
- checked,
731
- onChange,
732
- label
733
- }) {
734
- return (0, import_react4.createElement)(
735
- "label",
736
- { className: "mailharbor-toggle" },
737
- (0, import_react4.createElement)("input", {
738
- type: "checkbox",
739
- checked,
740
- onChange: (event) => {
741
- onChange(event.target.checked);
742
- }
743
- }),
744
- label
745
- );
746
- }
747
- function portalRoot() {
748
- const existing = document.getElementById("mailharbor-portal");
749
- if (existing !== null) return existing;
750
- const element = document.createElement("div");
751
- element.id = "mailharbor-portal";
752
- element.className = "mailharbor mailharbor-portal";
753
- document.body.append(element);
754
- return element;
755
- }
756
- function Modal({
757
- title,
758
- children,
759
- footer,
760
- onClose,
761
- wide = false
762
- }) {
763
- (0, import_react4.useEffect)(() => {
764
- const onKey = (event) => {
765
- if (event.key === "Escape") onClose();
766
- };
767
- window.addEventListener("keydown", onKey);
768
- return () => {
769
- window.removeEventListener("keydown", onKey);
770
- };
771
- }, [onClose]);
772
- return (0, import_react_dom.createPortal)(
773
- (0, import_react4.createElement)(
774
- "div",
1127
+ ) : null,
1128
+ detail === null ? null : (0, import_react5.createElement)(
1129
+ "form",
775
1130
  {
776
- className: "mailharbor-modal-mask",
777
- onMouseDown: (event) => {
778
- if (event.target === event.currentTarget) onClose();
1131
+ className: "mailharbor-ai-ask",
1132
+ onSubmit: (event) => {
1133
+ event.preventDefault();
1134
+ void ask();
779
1135
  }
780
1136
  },
781
- (0, import_react4.createElement)(
782
- "div",
1137
+ (0, import_react5.createElement)("input", {
1138
+ value: question,
1139
+ placeholder: "\u95EE\u8FD9\u5C01\u90AE\u4EF6\uFF0C\u4F8B\u5982\uFF1A\u8C01\u9700\u8981\u5728\u4EC0\u4E48\u65F6\u5019\u505A\u4EC0\u4E48\uFF1F",
1140
+ "aria-label": "\u95EE AI",
1141
+ disabled: disabled || generating !== null,
1142
+ onChange: (event) => setQuestion(event.target.value)
1143
+ }),
1144
+ (0, import_react5.createElement)(
1145
+ "button",
783
1146
  {
784
- className: "mailharbor-modal",
785
- style: wide ? { width: "min(920px, 100%)" } : void 0,
786
- role: "dialog",
787
- "aria-modal": "true",
788
- "aria-label": title
1147
+ className: "primary icon",
1148
+ type: "submit",
1149
+ disabled: disabled || generating !== null || question.trim() === ""
789
1150
  },
790
- (0, import_react4.createElement)(
791
- "header",
792
- null,
793
- title,
794
- (0, import_react4.createElement)("span", { className: "spacer" }),
795
- (0, import_react4.createElement)("button", { className: "subtle", onClick: onClose, "aria-label": "\u5173\u95ED" }, "\u2715")
796
- ),
797
- (0, import_react4.createElement)("div", { className: "content" }, children),
798
- footer === void 0 ? null : (0, import_react4.createElement)("footer", null, footer)
1151
+ (0, import_react5.createElement)(Icon, { name: "send", size: 14 })
799
1152
  )
800
- ),
801
- portalRoot()
802
- );
803
- }
804
- function ErrorBar({ message, onDismiss }) {
805
- return (0, import_react4.createElement)(
806
- "div",
807
- { className: "error", role: "alert" },
808
- (0, import_react4.createElement)("span", null, message),
809
- (0, import_react4.createElement)("span", { className: "spacer" }),
810
- (0, import_react4.createElement)("button", { className: "subtle", onClick: onDismiss }, "\u5173\u95ED")
1153
+ )
811
1154
  );
812
1155
  }
813
- function shortDate(iso) {
814
- const date = new Date(iso);
815
- if (Number.isNaN(date.getTime())) return iso.slice(0, 16);
816
- const now = /* @__PURE__ */ new Date();
817
- const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
818
- const pad = (value) => String(value).padStart(2, "0");
819
- if (sameDay) return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
820
- if (date.getFullYear() === now.getFullYear()) return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
821
- return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
822
- }
823
- function longDate(iso) {
824
- const date = new Date(iso);
825
- if (Number.isNaN(date.getTime())) return iso;
826
- return date.toLocaleString(void 0, {
827
- year: "numeric",
828
- month: "2-digit",
829
- day: "2-digit",
830
- hour: "2-digit",
831
- minute: "2-digit"
832
- });
833
- }
834
- function formatBytes(size) {
835
- if (!Number.isFinite(size) || size <= 0) return "";
836
- if (size < 1024) return `${String(size)} B`;
837
- if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
838
- return `${(size / 1024 / 1024).toFixed(1)} MB`;
839
- }
840
1156
 
841
1157
  // src/client/account-form.tsx
1158
+ var import_react6 = require("react");
842
1159
  function domainOf(email) {
843
1160
  return email.split("@")[1] ?? "example.com";
844
1161
  }
@@ -849,20 +1166,20 @@ function AddAccountForm({
849
1166
  onDone,
850
1167
  onCancel
851
1168
  }) {
852
- const [email, setEmail] = (0, import_react5.useState)("");
853
- const [password, setPassword] = (0, import_react5.useState)("");
854
- const [label, setLabel] = (0, import_react5.useState)("");
855
- const [remember, setRemember] = (0, import_react5.useState)(true);
856
- const [advanced, setAdvanced] = (0, import_react5.useState)(false);
857
- const [imapHost, setImapHost] = (0, import_react5.useState)("");
858
- const [imapPort, setImapPort] = (0, import_react5.useState)("993");
859
- const [imapSecure, setImapSecure] = (0, import_react5.useState)(true);
860
- const [smtpHost, setSmtpHost] = (0, import_react5.useState)("");
861
- const [smtpPort, setSmtpPort] = (0, import_react5.useState)("465");
862
- const [smtpSecure, setSmtpSecure] = (0, import_react5.useState)(true);
863
- const [fromName, setFromName] = (0, import_react5.useState)("");
864
- const [busy, setBusy] = (0, import_react5.useState)(false);
865
- const [error, setError] = (0, import_react5.useState)("");
1169
+ const [email, setEmail] = (0, import_react6.useState)("");
1170
+ const [password, setPassword] = (0, import_react6.useState)("");
1171
+ const [label, setLabel] = (0, import_react6.useState)("");
1172
+ const [remember, setRemember] = (0, import_react6.useState)(true);
1173
+ const [advanced, setAdvanced] = (0, import_react6.useState)(false);
1174
+ const [imapHost, setImapHost] = (0, import_react6.useState)("");
1175
+ const [imapPort, setImapPort] = (0, import_react6.useState)("993");
1176
+ const [imapSecure, setImapSecure] = (0, import_react6.useState)(true);
1177
+ const [smtpHost, setSmtpHost] = (0, import_react6.useState)("");
1178
+ const [smtpPort, setSmtpPort] = (0, import_react6.useState)("465");
1179
+ const [smtpSecure, setSmtpSecure] = (0, import_react6.useState)(true);
1180
+ const [fromName, setFromName] = (0, import_react6.useState)("");
1181
+ const [busy, setBusy] = (0, import_react6.useState)(false);
1182
+ const [error, setError] = (0, import_react6.useState)("");
866
1183
  const submit = async () => {
867
1184
  setBusy(true);
868
1185
  setError("");
@@ -894,17 +1211,17 @@ function AddAccountForm({
894
1211
  setBusy(false);
895
1212
  }
896
1213
  };
897
- return (0, import_react5.createElement)(
1214
+ return (0, import_react6.createElement)(
898
1215
  "div",
899
1216
  { className: "mailharbor-card" },
900
- (0, import_react5.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
901
- (0, import_react5.createElement)(
1217
+ (0, import_react6.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
1218
+ (0, import_react6.createElement)(
902
1219
  "div",
903
1220
  { className: "mailharbor-grid" },
904
- (0, import_react5.createElement)(
1221
+ (0, import_react6.createElement)(
905
1222
  Field,
906
1223
  { label: "\u90AE\u7BB1\u5730\u5740" },
907
- (0, import_react5.createElement)("input", {
1224
+ (0, import_react6.createElement)("input", {
908
1225
  type: "email",
909
1226
  value: email,
910
1227
  placeholder: "you@example.com",
@@ -912,95 +1229,95 @@ function AddAccountForm({
912
1229
  autoFocus: true
913
1230
  })
914
1231
  ),
915
- (0, import_react5.createElement)(
1232
+ (0, import_react6.createElement)(
916
1233
  Field,
917
1234
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
918
- (0, import_react5.createElement)("input", {
1235
+ (0, import_react6.createElement)("input", {
919
1236
  type: "password",
920
1237
  value: password,
921
1238
  onChange: (event) => setPassword(event.target.value)
922
1239
  })
923
1240
  )
924
1241
  ),
925
- (0, import_react5.createElement)(
1242
+ (0, import_react6.createElement)(
926
1243
  Field,
927
1244
  { label: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
928
- (0, import_react5.createElement)("input", {
1245
+ (0, import_react6.createElement)("input", {
929
1246
  value: label,
930
1247
  placeholder: "\u5DE5\u4F5C\u90AE\u7BB1",
931
1248
  onChange: (event) => setLabel(event.target.value)
932
1249
  })
933
1250
  ),
934
- (0, import_react5.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
935
- (0, import_react5.createElement)(
1251
+ (0, import_react6.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
1252
+ (0, import_react6.createElement)(
936
1253
  "button",
937
1254
  { className: "ghost", onClick: () => setAdvanced(!advanced) },
938
1255
  advanced ? "\u9690\u85CF\u9AD8\u7EA7\u8BBE\u7F6E" : "\u9AD8\u7EA7\u8BBE\u7F6E\uFF08\u670D\u52A1\u5668\u4E0E\u7AEF\u53E3\uFF09"
939
1256
  ),
940
- advanced ? (0, import_react5.createElement)(
1257
+ advanced ? (0, import_react6.createElement)(
941
1258
  "div",
942
1259
  { style: { display: "flex", flexDirection: "column", gap: "10px" } },
943
- (0, import_react5.createElement)(
1260
+ (0, import_react6.createElement)(
944
1261
  "div",
945
1262
  { className: "mailharbor-grid three" },
946
- (0, import_react5.createElement)(
1263
+ (0, import_react6.createElement)(
947
1264
  Field,
948
1265
  { label: "IMAP \u4E3B\u673A" },
949
- (0, import_react5.createElement)("input", {
1266
+ (0, import_react6.createElement)("input", {
950
1267
  value: imapHost,
951
1268
  placeholder: `imap.${domainOf(email)}`,
952
1269
  onChange: (event) => setImapHost(event.target.value)
953
1270
  })
954
1271
  ),
955
- (0, import_react5.createElement)(
1272
+ (0, import_react6.createElement)(
956
1273
  Field,
957
1274
  { label: "\u7AEF\u53E3" },
958
- (0, import_react5.createElement)("input", {
1275
+ (0, import_react6.createElement)("input", {
959
1276
  value: imapPort,
960
1277
  inputMode: "numeric",
961
1278
  onChange: (event) => setImapPort(event.target.value)
962
1279
  })
963
1280
  ),
964
- (0, import_react5.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
1281
+ (0, import_react6.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
965
1282
  ),
966
- (0, import_react5.createElement)(
1283
+ (0, import_react6.createElement)(
967
1284
  "div",
968
1285
  { className: "mailharbor-grid three" },
969
- (0, import_react5.createElement)(
1286
+ (0, import_react6.createElement)(
970
1287
  Field,
971
1288
  { label: "SMTP \u4E3B\u673A" },
972
- (0, import_react5.createElement)("input", {
1289
+ (0, import_react6.createElement)("input", {
973
1290
  value: smtpHost,
974
1291
  placeholder: `smtp.${domainOf(email)}`,
975
1292
  onChange: (event) => setSmtpHost(event.target.value)
976
1293
  })
977
1294
  ),
978
- (0, import_react5.createElement)(
1295
+ (0, import_react6.createElement)(
979
1296
  Field,
980
1297
  { label: "\u7AEF\u53E3" },
981
- (0, import_react5.createElement)("input", {
1298
+ (0, import_react6.createElement)("input", {
982
1299
  value: smtpPort,
983
1300
  inputMode: "numeric",
984
1301
  onChange: (event) => setSmtpPort(event.target.value)
985
1302
  })
986
1303
  ),
987
- (0, import_react5.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
1304
+ (0, import_react6.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
988
1305
  ),
989
- (0, import_react5.createElement)(
1306
+ (0, import_react6.createElement)(
990
1307
  Field,
991
1308
  { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
992
- (0, import_react5.createElement)("input", {
1309
+ (0, import_react6.createElement)("input", {
993
1310
  value: fromName,
994
1311
  onChange: (event) => setFromName(event.target.value)
995
1312
  })
996
1313
  )
997
1314
  ) : null,
998
- error === "" ? null : (0, import_react5.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
999
- (0, import_react5.createElement)(
1315
+ error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1316
+ (0, import_react6.createElement)(
1000
1317
  "div",
1001
1318
  { style: { display: "flex", gap: "8px", justifyContent: "flex-end" } },
1002
- onCancel === void 0 ? null : (0, import_react5.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
1003
- (0, import_react5.createElement)(
1319
+ onCancel === void 0 ? null : (0, import_react6.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
1320
+ (0, import_react6.createElement)(
1004
1321
  "button",
1005
1322
  {
1006
1323
  className: "primary",
@@ -1013,10 +1330,10 @@ function AddAccountForm({
1013
1330
  );
1014
1331
  }
1015
1332
  function LoginForm({ account, onDone }) {
1016
- const [password, setPassword] = (0, import_react5.useState)("");
1017
- const [remember, setRemember] = (0, import_react5.useState)(true);
1018
- const [busy, setBusy] = (0, import_react5.useState)(false);
1019
- const [error, setError] = (0, import_react5.useState)("");
1333
+ const [password, setPassword] = (0, import_react6.useState)("");
1334
+ const [remember, setRemember] = (0, import_react6.useState)(true);
1335
+ const [busy, setBusy] = (0, import_react6.useState)(false);
1336
+ const [error, setError] = (0, import_react6.useState)("");
1020
1337
  const submit = async () => {
1021
1338
  setBusy(true);
1022
1339
  setError("");
@@ -1029,19 +1346,19 @@ function LoginForm({ account, onDone }) {
1029
1346
  setBusy(false);
1030
1347
  }
1031
1348
  };
1032
- return (0, import_react5.createElement)(
1349
+ return (0, import_react6.createElement)(
1033
1350
  "div",
1034
1351
  { className: "mailharbor-card" },
1035
- (0, import_react5.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
1036
- (0, import_react5.createElement)(
1352
+ (0, import_react6.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
1353
+ (0, import_react6.createElement)(
1037
1354
  "p",
1038
1355
  { className: "small muted" },
1039
1356
  `${account.imap.host}:${String(account.imap.port)} \xB7 ${account.smtp.host}:${String(account.smtp.port)}`
1040
1357
  ),
1041
- (0, import_react5.createElement)(
1358
+ (0, import_react6.createElement)(
1042
1359
  Field,
1043
1360
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
1044
- (0, import_react5.createElement)("input", {
1361
+ (0, import_react6.createElement)("input", {
1045
1362
  type: "password",
1046
1363
  value: password,
1047
1364
  onChange: (event) => setPassword(event.target.value),
@@ -1051,9 +1368,9 @@ function LoginForm({ account, onDone }) {
1051
1368
  autoFocus: true
1052
1369
  })
1053
1370
  ),
1054
- (0, import_react5.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
1055
- error === "" ? null : (0, import_react5.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1056
- (0, import_react5.createElement)(
1371
+ (0, import_react6.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
1372
+ error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1373
+ (0, import_react6.createElement)(
1057
1374
  "button",
1058
1375
  { className: "primary", onClick: () => void submit(), disabled: busy || password === "" },
1059
1376
  busy ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
@@ -1064,10 +1381,10 @@ function AddAccountModal({
1064
1381
  onClose,
1065
1382
  onDone
1066
1383
  }) {
1067
- return (0, import_react5.createElement)(
1384
+ return (0, import_react6.createElement)(
1068
1385
  Modal,
1069
1386
  { title: "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7", onClose },
1070
- (0, import_react5.createElement)(AddAccountForm, {
1387
+ (0, import_react6.createElement)(AddAccountForm, {
1071
1388
  onDone: (state) => {
1072
1389
  onDone(state);
1073
1390
  onClose();
@@ -1078,7 +1395,7 @@ function AddAccountModal({
1078
1395
  }
1079
1396
 
1080
1397
  // src/client/composer.tsx
1081
- var import_react6 = require("react");
1398
+ var import_react7 = require("react");
1082
1399
  function errorText3(error) {
1083
1400
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1084
1401
  }
@@ -1099,29 +1416,29 @@ function Composer({
1099
1416
  onClose,
1100
1417
  onSent
1101
1418
  }) {
1102
- const [to, setTo] = (0, import_react6.useState)(initial.to);
1103
- const [cc, setCc] = (0, import_react6.useState)(initial.cc);
1104
- const [bcc, setBcc] = (0, import_react6.useState)(initial.bcc);
1105
- const [subject, setSubject] = (0, import_react6.useState)(initial.subject);
1106
- const [showCc, setShowCc] = (0, import_react6.useState)(initial.cc !== "" || initial.bcc !== "");
1107
- const [attachments, setAttachments] = (0, import_react6.useState)(initial.attachments ?? []);
1108
- const [busy, setBusy] = (0, import_react6.useState)(false);
1109
- const [error, setError] = (0, import_react6.useState)("");
1110
- const [confirming, setConfirming] = (0, import_react6.useState)(false);
1111
- const [draftId, setDraftId] = (0, import_react6.useState)(initial.draftId);
1112
- const [savedAt, setSavedAt] = (0, import_react6.useState)(null);
1113
- const [dirty, setDirty] = (0, import_react6.useState)(0);
1114
- const [translating, setTranslating] = (0, import_react6.useState)(null);
1115
- const [pendingLang, setPendingLang] = (0, import_react6.useState)(null);
1116
- const [versions, setVersions] = (0, import_react6.useState)([{ html: initial.html, label: "\u539F\u59CB" }]);
1117
- const [versionIndex, setVersionIndex] = (0, import_react6.useState)(0);
1118
- const [aiBusy, setAiBusy] = (0, import_react6.useState)(null);
1119
- const [aiDraftOpen, setAiDraftOpen] = (0, import_react6.useState)(false);
1120
- const [aiInstruction, setAiInstruction] = (0, import_react6.useState)("");
1121
- const [hasBody, setHasBody] = (0, import_react6.useState)(initial.html.trim() !== "");
1122
- const editor = (0, import_react6.useRef)(null);
1123
- const closing = (0, import_react6.useRef)(false);
1124
- (0, import_react6.useEffect)(() => {
1419
+ const [to, setTo] = (0, import_react7.useState)(initial.to);
1420
+ const [cc, setCc] = (0, import_react7.useState)(initial.cc);
1421
+ const [bcc, setBcc] = (0, import_react7.useState)(initial.bcc);
1422
+ const [subject, setSubject] = (0, import_react7.useState)(initial.subject);
1423
+ const [showCc, setShowCc] = (0, import_react7.useState)(initial.cc !== "" || initial.bcc !== "");
1424
+ const [attachments, setAttachments] = (0, import_react7.useState)(initial.attachments ?? []);
1425
+ const [busy, setBusy] = (0, import_react7.useState)(false);
1426
+ const [error, setError] = (0, import_react7.useState)("");
1427
+ const [confirming, setConfirming] = (0, import_react7.useState)(false);
1428
+ const [draftId, setDraftId] = (0, import_react7.useState)(initial.draftId);
1429
+ const [savedAt, setSavedAt] = (0, import_react7.useState)(null);
1430
+ const [dirty, setDirty] = (0, import_react7.useState)(0);
1431
+ const [translating, setTranslating] = (0, import_react7.useState)(null);
1432
+ const [pendingLang, setPendingLang] = (0, import_react7.useState)(null);
1433
+ const [versions, setVersions] = (0, import_react7.useState)([{ html: initial.html, label: "\u539F\u59CB" }]);
1434
+ const [versionIndex, setVersionIndex] = (0, import_react7.useState)(0);
1435
+ const [aiBusy, setAiBusy] = (0, import_react7.useState)(null);
1436
+ const [aiDraftOpen, setAiDraftOpen] = (0, import_react7.useState)(false);
1437
+ const [aiInstruction, setAiInstruction] = (0, import_react7.useState)("");
1438
+ const [hasBody, setHasBody] = (0, import_react7.useState)(initial.html.trim() !== "");
1439
+ const editor = (0, import_react7.useRef)(null);
1440
+ const closing = (0, import_react7.useRef)(false);
1441
+ (0, import_react7.useEffect)(() => {
1125
1442
  if (editor.current !== null) editor.current.innerHTML = initial.html;
1126
1443
  setHasBody((editor.current?.innerHTML ?? "").trim() !== "");
1127
1444
  }, []);
@@ -1153,7 +1470,7 @@ function Composer({
1153
1470
  return false;
1154
1471
  }
1155
1472
  };
1156
- (0, import_react6.useEffect)(() => {
1473
+ (0, import_react7.useEffect)(() => {
1157
1474
  if (dirty === 0) return;
1158
1475
  const timer = window.setTimeout(() => {
1159
1476
  void saveNow();
@@ -1287,30 +1604,30 @@ function Composer({
1287
1604
  setConfirming(false);
1288
1605
  }
1289
1606
  };
1290
- return (0, import_react6.createElement)(
1607
+ return (0, import_react7.createElement)(
1291
1608
  Modal,
1292
1609
  {
1293
1610
  title: initial.mode === "reply" ? "\u56DE\u590D" : initial.mode === "replyAll" ? "\u5168\u90E8\u56DE\u590D" : initial.mode === "forward" ? "\u8F6C\u53D1" : "\u5199\u90AE\u4EF6",
1294
1611
  onClose: close,
1295
1612
  wide: true,
1296
- footer: (0, import_react6.createElement)(
1613
+ footer: (0, import_react7.createElement)(
1297
1614
  "div",
1298
1615
  { className: "mailharbor-compose-foot" },
1299
- (0, import_react6.createElement)("span", { className: "small muted" }, `\u53D1\u4EF6\u4EBA ${account.email}`),
1300
- savedAt === null ? null : (0, import_react6.createElement)("span", { className: "small muted" }, `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${savedAt.toLocaleTimeString()}`),
1301
- (0, import_react6.createElement)("span", { className: "spacer" }),
1302
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => void saveDraft(), disabled: busy }, "\u5B58\u8349\u7A3F"),
1303
- confirming ? (0, import_react6.createElement)(
1616
+ (0, import_react7.createElement)("span", { className: "small muted" }, `\u53D1\u4EF6\u4EBA ${account.email}`),
1617
+ savedAt === null ? null : (0, import_react7.createElement)("span", { className: "small muted" }, `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${savedAt.toLocaleTimeString()}`),
1618
+ (0, import_react7.createElement)("span", { className: "spacer" }),
1619
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => void saveDraft(), disabled: busy }, "\u5B58\u8349\u7A3F"),
1620
+ confirming ? (0, import_react7.createElement)(
1304
1621
  "span",
1305
1622
  { style: { display: "inline-flex", gap: "6px", alignItems: "center" } },
1306
- (0, import_react6.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
1307
- (0, import_react6.createElement)(
1623
+ (0, import_react7.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
1624
+ (0, import_react7.createElement)(
1308
1625
  "button",
1309
1626
  { className: "primary", onClick: () => void send(), disabled: busy },
1310
1627
  busy ? "\u53D1\u9001\u4E2D\u2026" : "\u786E\u5B9A"
1311
1628
  ),
1312
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
1313
- ) : (0, import_react6.createElement)(
1629
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
1630
+ ) : (0, import_react7.createElement)(
1314
1631
  "button",
1315
1632
  {
1316
1633
  className: "primary",
@@ -1321,14 +1638,14 @@ function Composer({
1321
1638
  )
1322
1639
  )
1323
1640
  },
1324
- error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1325
- (0, import_react6.createElement)(
1641
+ error === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1642
+ (0, import_react7.createElement)(
1326
1643
  "div",
1327
1644
  { className: "mailharbor-grid" },
1328
- (0, import_react6.createElement)(
1645
+ (0, import_react7.createElement)(
1329
1646
  Field,
1330
1647
  { label: "\u6536\u4EF6\u4EBA" },
1331
- (0, import_react6.createElement)("input", {
1648
+ (0, import_react7.createElement)("input", {
1332
1649
  value: to,
1333
1650
  onChange: (event) => {
1334
1651
  setTo(event.target.value);
@@ -1337,10 +1654,10 @@ function Composer({
1337
1654
  placeholder: "a@example.com, b@example.com"
1338
1655
  })
1339
1656
  ),
1340
- (0, import_react6.createElement)(
1657
+ (0, import_react7.createElement)(
1341
1658
  Field,
1342
1659
  { label: "\u4E3B\u9898" },
1343
- (0, import_react6.createElement)("input", {
1660
+ (0, import_react7.createElement)("input", {
1344
1661
  value: subject,
1345
1662
  onChange: (event) => {
1346
1663
  setSubject(event.target.value);
@@ -1350,13 +1667,13 @@ function Composer({
1350
1667
  })
1351
1668
  )
1352
1669
  ),
1353
- showCc ? (0, import_react6.createElement)(
1670
+ showCc ? (0, import_react7.createElement)(
1354
1671
  "div",
1355
1672
  { className: "mailharbor-grid" },
1356
- (0, import_react6.createElement)(
1673
+ (0, import_react7.createElement)(
1357
1674
  Field,
1358
1675
  { label: "\u6284\u9001" },
1359
- (0, import_react6.createElement)("input", {
1676
+ (0, import_react7.createElement)("input", {
1360
1677
  value: cc,
1361
1678
  onChange: (event) => {
1362
1679
  setCc(event.target.value);
@@ -1364,10 +1681,10 @@ function Composer({
1364
1681
  }
1365
1682
  })
1366
1683
  ),
1367
- (0, import_react6.createElement)(
1684
+ (0, import_react7.createElement)(
1368
1685
  Field,
1369
1686
  { label: "\u5BC6\u9001" },
1370
- (0, import_react6.createElement)("input", {
1687
+ (0, import_react7.createElement)("input", {
1371
1688
  value: bcc,
1372
1689
  onChange: (event) => {
1373
1690
  setBcc(event.target.value);
@@ -1375,21 +1692,21 @@ function Composer({
1375
1692
  }
1376
1693
  })
1377
1694
  )
1378
- ) : (0, import_react6.createElement)(
1695
+ ) : (0, import_react7.createElement)(
1379
1696
  "button",
1380
1697
  { className: "subtle small", style: { alignSelf: "flex-start" }, onClick: () => setShowCc(true) },
1381
1698
  "\u6DFB\u52A0\u6284\u9001 / \u5BC6\u9001"
1382
1699
  ),
1383
- (0, import_react6.createElement)(
1700
+ (0, import_react7.createElement)(
1384
1701
  "div",
1385
1702
  { className: "mailharbor-toolbar" },
1386
- (0, import_react6.createElement)("button", { onClick: () => exec("bold"), title: "\u52A0\u7C97" }, "B"),
1387
- (0, import_react6.createElement)("button", { onClick: () => exec("italic"), title: "\u659C\u4F53" }, "I"),
1388
- (0, import_react6.createElement)("button", { onClick: () => exec("underline"), title: "\u4E0B\u5212\u7EBF" }, "U"),
1389
- (0, import_react6.createElement)("button", { onClick: () => exec("insertUnorderedList"), title: "\u65E0\u5E8F\u5217\u8868" }, "\u2022 \u5217\u8868"),
1390
- (0, import_react6.createElement)("button", { onClick: () => exec("insertOrderedList"), title: "\u6709\u5E8F\u5217\u8868" }, "1. \u5217\u8868"),
1391
- (0, import_react6.createElement)("button", { onClick: () => exec("formatBlock", "blockquote"), title: "\u5F15\u7528" }, "\u5F15\u7528"),
1392
- (0, import_react6.createElement)(
1703
+ (0, import_react7.createElement)("button", { onClick: () => exec("bold"), title: "\u52A0\u7C97" }, "B"),
1704
+ (0, import_react7.createElement)("button", { onClick: () => exec("italic"), title: "\u659C\u4F53" }, "I"),
1705
+ (0, import_react7.createElement)("button", { onClick: () => exec("underline"), title: "\u4E0B\u5212\u7EBF" }, "U"),
1706
+ (0, import_react7.createElement)("button", { onClick: () => exec("insertUnorderedList"), title: "\u65E0\u5E8F\u5217\u8868" }, "\u2022 \u5217\u8868"),
1707
+ (0, import_react7.createElement)("button", { onClick: () => exec("insertOrderedList"), title: "\u6709\u5E8F\u5217\u8868" }, "1. \u5217\u8868"),
1708
+ (0, import_react7.createElement)("button", { onClick: () => exec("formatBlock", "blockquote"), title: "\u5F15\u7528" }, "\u5F15\u7528"),
1709
+ (0, import_react7.createElement)(
1393
1710
  "button",
1394
1711
  {
1395
1712
  onClick: () => {
@@ -1400,9 +1717,9 @@ function Composer({
1400
1717
  },
1401
1718
  "\u94FE\u63A5"
1402
1719
  ),
1403
- (0, import_react6.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
1404
- (0, import_react6.createElement)("span", { className: "spacer" }),
1405
- (0, import_react6.createElement)(
1720
+ (0, import_react7.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
1721
+ (0, import_react7.createElement)("span", { className: "spacer" }),
1722
+ (0, import_react7.createElement)(
1406
1723
  "button",
1407
1724
  {
1408
1725
  className: "ai",
@@ -1410,34 +1727,34 @@ function Composer({
1410
1727
  disabled: aiBusy !== null,
1411
1728
  title: "\u8BA9 AI \u8D77\u8349\u6B63\u6587"
1412
1729
  },
1413
- (0, import_react6.createElement)(Icon, { name: "sparkles", size: 13 }),
1730
+ (0, import_react7.createElement)(Icon, { name: "sparkles", size: 13 }),
1414
1731
  "\u8D77\u8349"
1415
1732
  ),
1416
- (0, import_react6.createElement)(
1733
+ (0, import_react7.createElement)(
1417
1734
  "button",
1418
1735
  { className: "ai", onClick: () => void runAi("polish"), disabled: aiBusy !== null || !hasBody },
1419
1736
  aiBusy === "polish" ? "\u6DA6\u8272\u4E2D\u2026" : "\u6DA6\u8272"
1420
1737
  ),
1421
- (0, import_react6.createElement)(
1738
+ (0, import_react7.createElement)(
1422
1739
  "button",
1423
1740
  { className: "ai", onClick: () => void runAi("shorten"), disabled: aiBusy !== null || !hasBody },
1424
1741
  aiBusy === "shorten" ? "\u7F29\u77ED\u4E2D\u2026" : "\u7F29\u77ED"
1425
1742
  ),
1426
- (0, import_react6.createElement)(
1743
+ (0, import_react7.createElement)(
1427
1744
  "button",
1428
1745
  { onClick: () => setPendingLang("zh"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u4E2D\u6587" },
1429
1746
  translating === "zh" ? "\u2026" : "\u4E2D"
1430
1747
  ),
1431
- (0, import_react6.createElement)(
1748
+ (0, import_react7.createElement)(
1432
1749
  "button",
1433
1750
  { onClick: () => setPendingLang("en"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u82F1\u6587" },
1434
1751
  translating === "en" ? "\u2026" : "EN"
1435
1752
  ),
1436
- (0, import_react6.createElement)(
1753
+ (0, import_react7.createElement)(
1437
1754
  "label",
1438
1755
  { className: "mailharbor-toggle small" },
1439
1756
  "\u9644\u4EF6",
1440
- (0, import_react6.createElement)("input", {
1757
+ (0, import_react7.createElement)("input", {
1441
1758
  type: "file",
1442
1759
  multiple: true,
1443
1760
  style: { display: "none" },
@@ -1448,10 +1765,10 @@ function Composer({
1448
1765
  })
1449
1766
  )
1450
1767
  ),
1451
- aiDraftOpen ? (0, import_react6.createElement)(
1768
+ aiDraftOpen ? (0, import_react7.createElement)(
1452
1769
  "div",
1453
1770
  { className: "mailharbor-confirm" },
1454
- (0, import_react6.createElement)("input", {
1771
+ (0, import_react7.createElement)("input", {
1455
1772
  value: aiInstruction,
1456
1773
  placeholder: "\u60F3\u8BA9 AI \u5199\u4EC0\u4E48\uFF1F\u4F8B\u5982\uFF1A\u786E\u8BA4\u6536\u5230\uFF0C\u5468\u4E94\u524D\u7ED9\u7B54\u590D",
1457
1774
  onChange: (event) => setAiInstruction(event.target.value),
@@ -1459,30 +1776,30 @@ function Composer({
1459
1776
  if (event.key === "Enter") void runAi("draft");
1460
1777
  }
1461
1778
  }),
1462
- (0, import_react6.createElement)(
1779
+ (0, import_react7.createElement)(
1463
1780
  "button",
1464
1781
  { className: "primary", onClick: () => void runAi("draft"), disabled: aiBusy !== null },
1465
1782
  aiBusy === "draft" ? "\u751F\u6210\u4E2D\u2026" : "\u751F\u6210"
1466
1783
  ),
1467
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setAiDraftOpen(false) }, "\u53D6\u6D88")
1784
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => setAiDraftOpen(false) }, "\u53D6\u6D88")
1468
1785
  ) : null,
1469
- pendingLang === null ? null : (0, import_react6.createElement)(
1786
+ pendingLang === null ? null : (0, import_react7.createElement)(
1470
1787
  "div",
1471
1788
  { className: "mailharbor-confirm" },
1472
- (0, import_react6.createElement)("span", null, pendingLang === "en" ? "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u82F1\u6587\uFF1F" : "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u4E2D\u6587\uFF1F"),
1473
- (0, import_react6.createElement)("span", { className: "spacer" }),
1474
- (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setPendingLang(null) }, "\u53D6\u6D88"),
1475
- (0, import_react6.createElement)("button", { className: "primary", onClick: () => void translate(pendingLang) }, "\u7FFB\u8BD1")
1789
+ (0, import_react7.createElement)("span", null, pendingLang === "en" ? "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u82F1\u6587\uFF1F" : "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u4E2D\u6587\uFF1F"),
1790
+ (0, import_react7.createElement)("span", { className: "spacer" }),
1791
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => setPendingLang(null) }, "\u53D6\u6D88"),
1792
+ (0, import_react7.createElement)("button", { className: "primary", onClick: () => void translate(pendingLang) }, "\u7FFB\u8BD1")
1476
1793
  ),
1477
- attachments.length === 0 ? null : (0, import_react6.createElement)(
1794
+ attachments.length === 0 ? null : (0, import_react7.createElement)(
1478
1795
  "div",
1479
1796
  { className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
1480
1797
  attachments.map(
1481
- (file, index) => (0, import_react6.createElement)(
1798
+ (file, index) => (0, import_react7.createElement)(
1482
1799
  "span",
1483
1800
  { className: "mailharbor-attachment", key: `${file.filename}:${String(index)}` },
1484
- (0, import_react6.createElement)("span", { className: "name" }, file.filename),
1485
- (0, import_react6.createElement)(
1801
+ (0, import_react7.createElement)("span", { className: "name" }, file.filename),
1802
+ (0, import_react7.createElement)(
1486
1803
  "button",
1487
1804
  {
1488
1805
  className: "subtle",
@@ -1496,7 +1813,7 @@ function Composer({
1496
1813
  )
1497
1814
  )
1498
1815
  ),
1499
- (0, import_react6.createElement)("div", {
1816
+ (0, import_react7.createElement)("div", {
1500
1817
  className: "mailharbor-editor",
1501
1818
  ref: editor,
1502
1819
  contentEditable: true,
@@ -1505,12 +1822,12 @@ function Composer({
1505
1822
  spellCheck: true,
1506
1823
  onInput: markDirty
1507
1824
  }),
1508
- (0, import_react6.createElement)(
1825
+ (0, import_react7.createElement)(
1509
1826
  "div",
1510
1827
  { className: "mailharbor-versions" },
1511
- (0, import_react6.createElement)("span", { className: "small muted" }, "\u7248\u672C"),
1828
+ (0, import_react7.createElement)("span", { className: "small muted" }, "\u7248\u672C"),
1512
1829
  versions.map(
1513
- (version, index) => (0, import_react6.createElement)(
1830
+ (version, index) => (0, import_react7.createElement)(
1514
1831
  "button",
1515
1832
  {
1516
1833
  key: `${version.label}:${String(index)}`,
@@ -1521,19 +1838,19 @@ function Composer({
1521
1838
  String(index + 1)
1522
1839
  )
1523
1840
  ),
1524
- versions.length > 1 ? (0, import_react6.createElement)(
1841
+ versions.length > 1 ? (0, import_react7.createElement)(
1525
1842
  "button",
1526
1843
  { className: "subtle small", onClick: () => restoreVersion(Math.max(0, versionIndex - 1)) },
1527
1844
  "\u6062\u590D\u4E0A\u4E00\u7248"
1528
1845
  ) : null,
1529
- (0, import_react6.createElement)("span", { className: "spacer" }),
1530
- (0, import_react6.createElement)("span", { className: "small muted" }, versions[versionIndex]?.label ?? "")
1846
+ (0, import_react7.createElement)("span", { className: "spacer" }),
1847
+ (0, import_react7.createElement)("span", { className: "small muted" }, versions[versionIndex]?.label ?? "")
1531
1848
  )
1532
1849
  );
1533
1850
  }
1534
1851
 
1535
1852
  // src/client/reader.tsx
1536
- var import_react7 = require("react");
1853
+ var import_react8 = require("react");
1537
1854
  var IFRAME_STYLE = `
1538
1855
  html, body { margin: 0; padding: 0; }
1539
1856
  body {
@@ -1581,13 +1898,14 @@ function Reader({
1581
1898
  onChanged,
1582
1899
  onReply,
1583
1900
  onAiAction,
1901
+ onExtractTasks,
1584
1902
  onClose
1585
1903
  }) {
1586
- const [plain, setPlain] = (0, import_react7.useState)(false);
1587
- const [loadImages, setLoadImages] = (0, import_react7.useState)(false);
1588
- const [busy, setBusy] = (0, import_react7.useState)(false);
1589
- const [error, setError] = (0, import_react7.useState)("");
1590
- const remoteImages = (0, import_react7.useMemo)(() => hasRemoteImages(detail), [detail]);
1904
+ const [plain, setPlain] = (0, import_react8.useState)(false);
1905
+ const [loadImages, setLoadImages] = (0, import_react8.useState)(false);
1906
+ const [busy, setBusy] = (0, import_react8.useState)(false);
1907
+ const [error, setError] = (0, import_react8.useState)("");
1908
+ const remoteImages = (0, import_react8.useMemo)(() => hasRemoteImages(detail), [detail]);
1591
1909
  const html = detail.html !== null && detail.html !== "";
1592
1910
  const toggleRead = async () => {
1593
1911
  setBusy(true);
@@ -1607,22 +1925,22 @@ function Reader({
1607
1925
  setBusy(false);
1608
1926
  }
1609
1927
  };
1610
- return (0, import_react7.createElement)(
1928
+ return (0, import_react8.createElement)(
1611
1929
  "div",
1612
1930
  { className: "mailharbor-reader" },
1613
- (0, import_react7.createElement)(
1931
+ (0, import_react8.createElement)(
1614
1932
  "div",
1615
1933
  { className: "mailharbor-reader-head" },
1616
- (0, import_react7.createElement)(
1934
+ (0, import_react8.createElement)(
1617
1935
  "div",
1618
1936
  { className: "mailharbor-reader-title" },
1619
- onClose === void 0 ? null : (0, import_react7.createElement)(
1937
+ onClose === void 0 ? null : (0, import_react8.createElement)(
1620
1938
  "button",
1621
1939
  { className: "ghost icon", onClick: onClose, "aria-label": "\u8FD4\u56DE\u5217\u8868", title: "\u8FD4\u56DE\u5217\u8868" },
1622
- (0, import_react7.createElement)(Icon, { name: "chevronLeft" })
1940
+ (0, import_react8.createElement)(Icon, { name: "chevronLeft" })
1623
1941
  ),
1624
- (0, import_react7.createElement)("h2", { style: { flex: 1 } }, detail.subject),
1625
- (0, import_react7.createElement)(
1942
+ (0, import_react8.createElement)("h2", { style: { flex: 1 } }, detail.subject),
1943
+ (0, import_react8.createElement)(
1626
1944
  "button",
1627
1945
  {
1628
1946
  className: "ghost icon",
@@ -1631,88 +1949,94 @@ function Reader({
1631
1949
  title: detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB",
1632
1950
  "aria-label": detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB"
1633
1951
  },
1634
- (0, import_react7.createElement)(Icon, { name: "check" })
1952
+ (0, import_react8.createElement)(Icon, { name: "check" })
1635
1953
  )
1636
1954
  ),
1637
- (0, import_react7.createElement)(
1955
+ (0, import_react8.createElement)(
1638
1956
  "div",
1639
1957
  { className: "mailharbor-reader-meta" },
1640
- (0, import_react7.createElement)(
1958
+ (0, import_react8.createElement)(
1641
1959
  "span",
1642
1960
  { className: "who" },
1643
- (0, import_react7.createElement)(Avatar, { email: detail.from.address, size: 22 }),
1644
- (0, import_react7.createElement)("strong", null, detail.from.name !== "" ? detail.from.name : detail.from.address),
1645
- detail.from.name !== "" ? (0, import_react7.createElement)("span", null, `<${detail.from.address}>`) : null
1961
+ (0, import_react8.createElement)(Avatar, { email: detail.from.address, size: 22 }),
1962
+ (0, import_react8.createElement)("strong", null, detail.from.name !== "" ? detail.from.name : detail.from.address),
1963
+ detail.from.name !== "" ? (0, import_react8.createElement)("span", null, `<${detail.from.address}>`) : null
1646
1964
  ),
1647
- (0, import_react7.createElement)("span", null, longDate(detail.date)),
1648
- detail.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null
1965
+ (0, import_react8.createElement)("span", null, longDate(detail.date)),
1966
+ detail.backedUp === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null
1649
1967
  ),
1650
- (0, import_react7.createElement)(
1968
+ (0, import_react8.createElement)(
1651
1969
  "div",
1652
1970
  { className: "mailharbor-reader-meta" },
1653
- (0, import_react7.createElement)("span", null, `\u6536\u4EF6\u4EBA: ${addressLine(detail.to) || "\u2014"}`),
1654
- detail.cc.length === 0 ? null : (0, import_react7.createElement)("span", null, `\u6284\u9001: ${addressLine(detail.cc)}`)
1971
+ (0, import_react8.createElement)("span", null, `\u6536\u4EF6\u4EBA: ${addressLine(detail.to) || "\u2014"}`),
1972
+ detail.cc.length === 0 ? null : (0, import_react8.createElement)("span", null, `\u6284\u9001: ${addressLine(detail.cc)}`)
1655
1973
  ),
1656
- (0, import_react7.createElement)(
1974
+ (0, import_react8.createElement)(
1657
1975
  "div",
1658
1976
  { className: "mailharbor-reader-actions" },
1659
- (0, import_react7.createElement)(
1977
+ (0, import_react8.createElement)(
1660
1978
  "button",
1661
1979
  { className: "ghost", onClick: () => onReply("reply") },
1662
- (0, import_react7.createElement)(Icon, { name: "reply" }),
1980
+ (0, import_react8.createElement)(Icon, { name: "reply" }),
1663
1981
  "\u56DE\u590D"
1664
1982
  ),
1665
- (0, import_react7.createElement)(
1983
+ (0, import_react8.createElement)(
1666
1984
  "button",
1667
1985
  { className: "ghost", onClick: () => onReply("replyAll") },
1668
- (0, import_react7.createElement)(Icon, { name: "replyAll" }),
1986
+ (0, import_react8.createElement)(Icon, { name: "replyAll" }),
1669
1987
  "\u5168\u90E8\u56DE\u590D"
1670
1988
  ),
1671
- (0, import_react7.createElement)(
1989
+ (0, import_react8.createElement)(
1672
1990
  "button",
1673
1991
  { className: "ghost", onClick: () => onReply("forward") },
1674
- (0, import_react7.createElement)(Icon, { name: "forward" }),
1992
+ (0, import_react8.createElement)(Icon, { name: "forward" }),
1675
1993
  "\u8F6C\u53D1"
1676
1994
  ),
1677
- (0, import_react7.createElement)("span", { className: "mailharbor-divider" }),
1678
- (0, import_react7.createElement)(
1995
+ (0, import_react8.createElement)("span", { className: "mailharbor-divider" }),
1996
+ (0, import_react8.createElement)(
1679
1997
  "button",
1680
1998
  { className: "ghost", onClick: () => onAiAction("summary") },
1681
- (0, import_react7.createElement)(Icon, { name: "sparkles", size: 14 }),
1999
+ (0, import_react8.createElement)(Icon, { name: "sparkles", size: 14 }),
1682
2000
  "AI \u6458\u8981"
1683
2001
  ),
1684
- (0, import_react7.createElement)("button", { className: "ghost", onClick: () => onAiAction("reply") }, "AI \u56DE\u590D"),
1685
- (0, import_react7.createElement)("button", { className: "ghost", onClick: () => onAiAction("translate") }, "AI \u7FFB\u8BD1"),
1686
- (0, import_react7.createElement)("span", { className: "spacer" }),
1687
- html ? (0, import_react7.createElement)(
2002
+ (0, import_react8.createElement)("button", { className: "ghost", onClick: () => onAiAction("reply") }, "AI \u56DE\u590D"),
2003
+ (0, import_react8.createElement)("button", { className: "ghost", onClick: () => onAiAction("translate") }, "AI \u7FFB\u8BD1"),
2004
+ (0, import_react8.createElement)(
2005
+ "button",
2006
+ { className: "ghost", onClick: onExtractTasks },
2007
+ (0, import_react8.createElement)(Icon, { name: "checklist", size: 14 }),
2008
+ "\u63D0\u53D6\u4EFB\u52A1"
2009
+ ),
2010
+ (0, import_react8.createElement)("span", { className: "spacer" }),
2011
+ html ? (0, import_react8.createElement)(
1688
2012
  "button",
1689
2013
  { className: "subtle", onClick: () => setPlain(!plain) },
1690
2014
  plain ? "\u663E\u793A HTML" : "\u7EAF\u6587\u672C"
1691
2015
  ) : null,
1692
- remoteImages && !loadImages ? (0, import_react7.createElement)(
2016
+ remoteImages && !loadImages ? (0, import_react8.createElement)(
1693
2017
  "button",
1694
2018
  { className: "subtle", onClick: () => setLoadImages(true) },
1695
- (0, import_react7.createElement)(Icon, { name: "image", size: 14 }),
2019
+ (0, import_react8.createElement)(Icon, { name: "image", size: 14 }),
1696
2020
  "\u663E\u793A\u56FE\u7247"
1697
2021
  ) : null
1698
2022
  ),
1699
- error === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error)
2023
+ error === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error)
1700
2024
  ),
1701
- (0, import_react7.createElement)(
2025
+ (0, import_react8.createElement)(
1702
2026
  "div",
1703
2027
  { className: "mailharbor-reader-body" },
1704
- html && !plain ? (0, import_react7.createElement)("iframe", {
2028
+ html && !plain ? (0, import_react8.createElement)("iframe", {
1705
2029
  title: "\u90AE\u4EF6\u6B63\u6587",
1706
2030
  sandbox: "",
1707
2031
  srcDoc: srcDoc(detail, loadImages),
1708
2032
  referrerPolicy: "no-referrer"
1709
- }) : (0, import_react7.createElement)("pre", null, detail.text !== "" ? detail.text : "\uFF08\u65E0\u6B63\u6587\uFF09")
2033
+ }) : (0, import_react8.createElement)("pre", null, detail.text !== "" ? detail.text : "\uFF08\u65E0\u6B63\u6587\uFF09")
1710
2034
  ),
1711
- detail.attachments.length === 0 ? null : (0, import_react7.createElement)(
2035
+ detail.attachments.length === 0 ? null : (0, import_react8.createElement)(
1712
2036
  "div",
1713
2037
  { className: "mailharbor-attachments" },
1714
2038
  detail.attachments.map(
1715
- (file) => (0, import_react7.createElement)(
2039
+ (file) => (0, import_react8.createElement)(
1716
2040
  "a",
1717
2041
  {
1718
2042
  className: "mailharbor-attachment",
@@ -1732,15 +2056,27 @@ function Reader({
1732
2056
  download: file.filename,
1733
2057
  title: file.filename
1734
2058
  },
1735
- (0, import_react7.createElement)(Icon, { name: "paperclip", size: 14 }),
1736
- (0, import_react7.createElement)("span", { className: "name" }, file.filename),
1737
- (0, import_react7.createElement)("span", { className: "muted small" }, formatBytes(file.size))
2059
+ (0, import_react8.createElement)(Icon, { name: "paperclip", size: 14 }),
2060
+ (0, import_react8.createElement)("span", { className: "name" }, file.filename),
2061
+ (0, import_react8.createElement)("span", { className: "muted small" }, formatBytes(file.size))
1738
2062
  )
1739
2063
  )
1740
2064
  )
1741
2065
  );
1742
2066
  }
1743
2067
 
2068
+ // src/client/nav.ts
2069
+ var pending = null;
2070
+ function requestOpenMail(request2) {
2071
+ pending = request2;
2072
+ window.dispatchEvent(new CustomEvent("mailharbor:open-mail", { detail: request2 }));
2073
+ }
2074
+ function consumeOpenMail() {
2075
+ const value = pending;
2076
+ pending = null;
2077
+ return value;
2078
+ }
2079
+
1744
2080
  // src/client/panel.tsx
1745
2081
  var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
1746
2082
  var DRAFTS = "__drafts";
@@ -1755,43 +2091,162 @@ function quoteForReply(detail) {
1755
2091
  return `<p><br></p><p>------------------ \u539F\u59CB\u90AE\u4EF6 ------------------</p><blockquote style="margin:0 0 0 12px;padding-left:12px;border-left:2px solid #d7dbe3;color:#565c67">${body}</blockquote>`;
1756
2092
  }
1757
2093
  function MailPanel() {
1758
- const [state, setState] = (0, import_react8.useState)(null);
1759
- const [error, setError] = (0, import_react8.useState)("");
1760
- const [accountScope, setAccountScope] = (0, import_react8.useState)(null);
1761
- const [source, setSource] = (0, import_react8.useState)("local");
1762
- const [mailbox, setMailbox] = (0, import_react8.useState)("all");
1763
- const [folders, setFolders] = (0, import_react8.useState)([]);
1764
- const [query2, setQuery] = (0, import_react8.useState)("");
1765
- const [search, setSearch] = (0, import_react8.useState)("");
1766
- const [page, setPage] = (0, import_react8.useState)(EMPTY_PAGE);
1767
- const [drafts, setDrafts] = (0, import_react8.useState)([]);
1768
- const [limit, setLimit] = (0, import_react8.useState)(40);
1769
- const [loading, setLoading] = (0, import_react8.useState)(false);
1770
- const [selected, setSelected] = (0, import_react8.useState)(null);
1771
- const [compose, setCompose] = (0, import_react8.useState)(null);
1772
- const [showAdd, setShowAdd] = (0, import_react8.useState)(false);
1773
- const [showBackup, setShowBackup] = (0, import_react8.useState)(false);
1774
- const [backup, setBackup] = (0, import_react8.useState)(null);
1775
- const [toast, setToast] = (0, import_react8.useState)("");
1776
- const [narrow, setNarrow] = (0, import_react8.useState)(() => matchesNarrow());
1777
- const [railOpen, setRailOpen] = (0, import_react8.useState)(false);
1778
- const [aiRequest, setAiRequest] = (0, import_react8.useState)(null);
1779
- const [tick, setTick] = (0, import_react8.useState)(0);
2094
+ const [state, setState] = (0, import_react9.useState)(null);
2095
+ const [error, setError] = (0, import_react9.useState)("");
2096
+ const [accountScope, setAccountScope] = (0, import_react9.useState)(null);
2097
+ const [source, setSource] = (0, import_react9.useState)("local");
2098
+ const [mailbox, setMailbox] = (0, import_react9.useState)("all");
2099
+ const [folders, setFolders] = (0, import_react9.useState)([]);
2100
+ const [query2, setQuery] = (0, import_react9.useState)("");
2101
+ const [search, setSearch] = (0, import_react9.useState)("");
2102
+ const [page, setPage] = (0, import_react9.useState)(EMPTY_PAGE);
2103
+ const [drafts, setDrafts] = (0, import_react9.useState)([]);
2104
+ const [limit, setLimit] = (0, import_react9.useState)(40);
2105
+ const [loading, setLoading] = (0, import_react9.useState)(false);
2106
+ const [selected, setSelected] = (0, import_react9.useState)(null);
2107
+ const [compose, setCompose] = (0, import_react9.useState)(null);
2108
+ const [showAdd, setShowAdd] = (0, import_react9.useState)(false);
2109
+ const [showBackup, setShowBackup] = (0, import_react9.useState)(false);
2110
+ const [backup, setBackup] = (0, import_react9.useState)(null);
2111
+ const [toast, setToast] = (0, import_react9.useState)("");
2112
+ const [narrow, setNarrow] = (0, import_react9.useState)(() => matchesNarrow());
2113
+ const [railOpen, setRailOpen] = (0, import_react9.useState)(false);
2114
+ const [selectMode, setSelectMode] = (0, import_react9.useState)(false);
2115
+ const [selectedKeys, setSelectedKeys] = (0, import_react9.useState)([]);
2116
+ const [projectOptions, setProjectOptions] = (0, import_react9.useState)([]);
2117
+ const [targetProject, setTargetProject] = (0, import_react9.useState)("");
2118
+ const [extraction, setExtraction] = (0, import_react9.useState)(null);
2119
+ const [extractBusy, setExtractBusy] = (0, import_react9.useState)(false);
2120
+ const [aiRequest, setAiRequest] = (0, import_react9.useState)(null);
2121
+ const [tick, setTick] = (0, import_react9.useState)(0);
1780
2122
  const accounts = state?.accounts ?? [];
1781
2123
  const isAll = accountScope === "all";
1782
2124
  const account = accounts.find((item) => item.id === accountScope) ?? null;
1783
2125
  const composeAccount = accounts.find((item) => item.id === compose?.accountId) ?? account ?? accounts[0] ?? null;
1784
2126
  const showingDrafts = mailbox === DRAFTS && source === "local";
1785
- const refresh = (0, import_react8.useCallback)(() => {
2127
+ const keyOf = (item) => item.messageId !== void 0 && item.messageId !== "" ? `mid:${item.messageId}` : `local:${item.id}`;
2128
+ const openFromRequest = (0, import_react9.useCallback)((detail) => {
2129
+ setAccountScope(detail.accountId);
2130
+ setSource("local");
2131
+ setMailbox(detail.mailbox === "" ? "all" : detail.mailbox);
2132
+ api.activateAccount(detail.accountId).then(() => {
2133
+ setRailOpen(false);
2134
+ return api.message({
2135
+ id: detail.id,
2136
+ accountId: detail.accountId,
2137
+ source: "local",
2138
+ mailbox: detail.mailbox === "" ? "all" : detail.mailbox
2139
+ });
2140
+ }).then((message) => setSelected(message)).catch((caught) => setError(errorText5(caught)));
2141
+ }, []);
2142
+ (0, import_react9.useEffect)(() => {
2143
+ const queued = consumeOpenMail();
2144
+ if (queued !== null) openFromRequest(queued);
2145
+ const onOpen = (event) => {
2146
+ const detail = event.detail;
2147
+ if (detail !== void 0) openFromRequest(detail);
2148
+ };
2149
+ window.addEventListener("mailharbor:open-mail", onOpen);
2150
+ return () => {
2151
+ window.removeEventListener("mailharbor:open-mail", onOpen);
2152
+ };
2153
+ }, [openFromRequest]);
2154
+ const enterSelect = async () => {
2155
+ setSelectMode(true);
2156
+ try {
2157
+ const list = await api.projects();
2158
+ setProjectOptions(list.items);
2159
+ setTargetProject(list.items[0]?.id ?? "");
2160
+ } catch (caught) {
2161
+ setError(errorText5(caught));
2162
+ }
2163
+ };
2164
+ const toggleSelect = (item) => {
2165
+ const key = `${item.accountId}:${item.id}`;
2166
+ setSelectedKeys(
2167
+ (current) => current.includes(key) ? current.filter((entry) => entry !== key) : [...current, key]
2168
+ );
2169
+ };
2170
+ const addSelectedToProject = async () => {
2171
+ if (targetProject === "") {
2172
+ setError("\u5148\u521B\u5EFA\u4E00\u4E2A\u9879\u76EE");
2173
+ return;
2174
+ }
2175
+ const chosen = items.filter((item) => selectedKeys.includes(`${item.accountId}:${item.id}`));
2176
+ if (chosen.length === 0) return;
2177
+ try {
2178
+ for (const item of chosen) {
2179
+ await api.addProjectMessage(targetProject, {
2180
+ accountId: item.accountId,
2181
+ key: keyOf(item),
2182
+ subject: item.subject,
2183
+ date: item.date
2184
+ });
2185
+ }
2186
+ notice(`\u5DF2\u52A0\u5165\u9879\u76EE\uFF1A${String(chosen.length)} \u5C01`);
2187
+ setSelectMode(false);
2188
+ setSelectedKeys([]);
2189
+ } catch (caught) {
2190
+ setError(errorText5(caught));
2191
+ }
2192
+ };
2193
+ const extractTasks = async () => {
2194
+ if (selected === null) return;
2195
+ setExtractBusy(true);
2196
+ try {
2197
+ const result = await api.extractTasks({
2198
+ accountId: selected.accountId,
2199
+ source,
2200
+ id: selected.id,
2201
+ mailbox
2202
+ });
2203
+ if (result.suggestions.length === 0) {
2204
+ notice("\u8FD9\u5C01\u90AE\u4EF6\u6CA1\u6709\u63D0\u53D6\u5230\u660E\u786E\u4EFB\u52A1");
2205
+ return;
2206
+ }
2207
+ setExtraction({ items: result.suggestions, checked: result.suggestions.map(() => true) });
2208
+ } catch (caught) {
2209
+ setError(errorText5(caught));
2210
+ } finally {
2211
+ setExtractBusy(false);
2212
+ }
2213
+ };
2214
+ const saveExtraction = async () => {
2215
+ if (extraction === null || selected === null) return;
2216
+ setExtractBusy(true);
2217
+ try {
2218
+ const key = keyOf(selected);
2219
+ for (const [index, suggestion] of extraction.items.entries()) {
2220
+ if (!extraction.checked[index]) continue;
2221
+ await api.createTask({
2222
+ accountId: selected.accountId,
2223
+ title: suggestion.title,
2224
+ notes: suggestion.notes,
2225
+ due: suggestion.due,
2226
+ priority: suggestion.priority,
2227
+ owner: suggestion.owner,
2228
+ messageKey: key,
2229
+ messageSubject: selected.subject
2230
+ });
2231
+ }
2232
+ setExtraction(null);
2233
+ notice("\u4EFB\u52A1\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u5728\u300C\u4EFB\u52A1\u300D\u9762\u677F\u67E5\u770B");
2234
+ } catch (caught) {
2235
+ setError(errorText5(caught));
2236
+ } finally {
2237
+ setExtractBusy(false);
2238
+ }
2239
+ };
2240
+ const refresh = (0, import_react9.useCallback)(() => {
1786
2241
  setTick((value) => value + 1);
1787
2242
  }, []);
1788
- const notice = (0, import_react8.useCallback)((message) => {
2243
+ const notice = (0, import_react9.useCallback)((message) => {
1789
2244
  setToast(message);
1790
2245
  window.setTimeout(() => {
1791
2246
  setToast("");
1792
2247
  }, 3200);
1793
2248
  }, []);
1794
- (0, import_react8.useEffect)(() => {
2249
+ (0, import_react9.useEffect)(() => {
1795
2250
  let cancelled = false;
1796
2251
  api.state().then((next) => {
1797
2252
  if (cancelled) return;
@@ -1805,7 +2260,7 @@ function MailPanel() {
1805
2260
  cancelled = true;
1806
2261
  };
1807
2262
  }, [tick]);
1808
- (0, import_react8.useEffect)(() => {
2263
+ (0, import_react9.useEffect)(() => {
1809
2264
  if (accountScope === null) return;
1810
2265
  let cancelled = false;
1811
2266
  api.folders({ accountId: accountScope, source }).then((result) => {
@@ -1829,16 +2284,16 @@ function MailPanel() {
1829
2284
  cancelled = true;
1830
2285
  };
1831
2286
  }, [accountScope, source, tick]);
1832
- (0, import_react8.useEffect)(() => {
2287
+ (0, import_react9.useEffect)(() => {
1833
2288
  setMailbox(source === "local" ? "all" : "INBOX");
1834
2289
  setSelected(null);
1835
2290
  setLimit(state?.settings.render.listLimit ?? 40);
1836
2291
  }, [source]);
1837
- (0, import_react8.useEffect)(() => {
2292
+ (0, import_react9.useEffect)(() => {
1838
2293
  if (accountScope === "all" && source === "server") setSource("local");
1839
2294
  setSelected(null);
1840
2295
  }, [accountScope, source]);
1841
- (0, import_react8.useEffect)(() => {
2296
+ (0, import_react9.useEffect)(() => {
1842
2297
  if (accountScope === null) return;
1843
2298
  let cancelled = false;
1844
2299
  setLoading(true);
@@ -1878,7 +2333,7 @@ function MailPanel() {
1878
2333
  cancelled = true;
1879
2334
  };
1880
2335
  }, [accountScope, source, mailbox, search, limit, tick]);
1881
- (0, import_react8.useEffect)(() => {
2336
+ (0, import_react9.useEffect)(() => {
1882
2337
  if (typeof window.matchMedia !== "function") return;
1883
2338
  const media = window.matchMedia("(max-width: 900px)");
1884
2339
  const onChange = () => setNarrow(media.matches);
@@ -1887,7 +2342,7 @@ function MailPanel() {
1887
2342
  media.removeEventListener("change", onChange);
1888
2343
  };
1889
2344
  }, []);
1890
- (0, import_react8.useEffect)(() => {
2345
+ (0, import_react9.useEffect)(() => {
1891
2346
  if (!showBackup || accountScope === null || isAll) return;
1892
2347
  let cancelled = false;
1893
2348
  const poll = async () => {
@@ -2055,7 +2510,7 @@ function MailPanel() {
2055
2510
  setError(errorText5(caught));
2056
2511
  }
2057
2512
  };
2058
- const folderOptions = (0, import_react8.useMemo)(
2513
+ const folderOptions = (0, import_react9.useMemo)(
2059
2514
  () => folders.map((folder) => ({
2060
2515
  value: folder.name,
2061
2516
  label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
@@ -2063,27 +2518,27 @@ function MailPanel() {
2063
2518
  [folders]
2064
2519
  );
2065
2520
  if (state === null) {
2066
- return (0, import_react8.createElement)(
2521
+ return (0, import_react9.createElement)(
2067
2522
  "div",
2068
2523
  { className: "mailharbor" },
2069
- (0, import_react8.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
2524
+ (0, import_react9.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
2070
2525
  );
2071
2526
  }
2072
2527
  if (accounts.length === 0) {
2073
- return (0, import_react8.createElement)(
2528
+ return (0, import_react9.createElement)(
2074
2529
  "div",
2075
2530
  { className: "mailharbor" },
2076
- (0, import_react8.createElement)(
2531
+ (0, import_react9.createElement)(
2077
2532
  "div",
2078
2533
  { className: "mailharbor-settings" },
2079
- (0, import_react8.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
2080
- (0, import_react8.createElement)(
2534
+ (0, import_react9.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
2535
+ (0, import_react9.createElement)(
2081
2536
  "p",
2082
2537
  { className: "muted small" },
2083
2538
  "\u628A\u4EFB\u610F IMAP/SMTP \u90AE\u7BB1\u63A5\u8FDB DeepSeek Harness\uFF1A\u672C\u5730\u5907\u4EFD\u3001\u5B9E\u65F6\u8BFB\u4FE1\u3001\u5199\u90AE\u4EF6\u4E0E Agent \u5DE5\u5177\u3002"
2084
2539
  ),
2085
- error === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
2086
- (0, import_react8.createElement)(AddAccountForm, {
2540
+ error === "" ? null : (0, import_react9.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
2541
+ (0, import_react9.createElement)(AddAccountForm, {
2087
2542
  onDone: (next) => {
2088
2543
  setState(next);
2089
2544
  setAccountScope(next.activeAccountId);
@@ -2097,38 +2552,38 @@ function MailPanel() {
2097
2552
  const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
2098
2553
  const needsLogin = source === "server" && !isAll && state.loggedIn === false;
2099
2554
  const items = page.items;
2100
- return (0, import_react8.createElement)(
2555
+ return (0, import_react9.createElement)(
2101
2556
  "div",
2102
2557
  { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
2103
- (0, import_react8.createElement)(
2558
+ (0, import_react9.createElement)(
2104
2559
  "div",
2105
2560
  { className: "mailharbor-topbar" },
2106
- (0, import_react8.createElement)(
2561
+ (0, import_react9.createElement)(
2107
2562
  "div",
2108
2563
  { className: "mailharbor-account" },
2109
- (0, import_react8.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
2110
- accounts.length > 1 ? (0, import_react8.createElement)(
2564
+ (0, import_react9.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
2565
+ accounts.length > 1 ? (0, import_react9.createElement)(
2111
2566
  "select",
2112
2567
  {
2113
2568
  value: accountScope ?? "",
2114
2569
  onChange: (event) => void switchAccount(event.target.value),
2115
2570
  "aria-label": "\u8D26\u53F7"
2116
2571
  },
2117
- (0, import_react8.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
2118
- accounts.map((item) => (0, import_react8.createElement)("option", { key: item.id, value: item.id }, item.label))
2119
- ) : (0, import_react8.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
2120
- isAll ? (0, import_react8.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
2121
- !isAll && state.loggedIn === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
2572
+ (0, import_react9.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
2573
+ accounts.map((item) => (0, import_react9.createElement)("option", { key: item.id, value: item.id }, item.label))
2574
+ ) : (0, import_react9.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
2575
+ isAll ? (0, import_react9.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
2576
+ !isAll && state.loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
2122
2577
  ),
2123
- (0, import_react8.createElement)(
2578
+ (0, import_react9.createElement)(
2124
2579
  "div",
2125
2580
  { className: "mailharbor-segment", role: "tablist" },
2126
- (0, import_react8.createElement)(
2581
+ (0, import_react9.createElement)(
2127
2582
  "button",
2128
2583
  { className: source === "local" ? "active" : "", onClick: () => setSource("local"), role: "tab" },
2129
2584
  "\u672C\u5730\u5907\u4EFD"
2130
2585
  ),
2131
- (0, import_react8.createElement)(
2586
+ (0, import_react9.createElement)(
2132
2587
  "button",
2133
2588
  {
2134
2589
  className: source === "server" ? "active" : "",
@@ -2145,8 +2600,8 @@ function MailPanel() {
2145
2600
  "\u670D\u52A1\u5668\u5B9E\u65F6"
2146
2601
  )
2147
2602
  ),
2148
- (0, import_react8.createElement)("span", { className: "spacer" }),
2149
- (0, import_react8.createElement)(
2603
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2604
+ (0, import_react9.createElement)(
2150
2605
  "button",
2151
2606
  {
2152
2607
  className: "ghost icon",
@@ -2154,56 +2609,56 @@ function MailPanel() {
2154
2609
  "aria-label": "\u6DFB\u52A0\u8D26\u53F7",
2155
2610
  onClick: () => setShowAdd(true)
2156
2611
  },
2157
- (0, import_react8.createElement)(Icon, { name: "plus" })
2612
+ (0, import_react9.createElement)(Icon, { name: "plus" })
2158
2613
  ),
2159
- (0, import_react8.createElement)(
2614
+ (0, import_react9.createElement)(
2160
2615
  "button",
2161
2616
  { className: "ghost icon", title: "\u5237\u65B0\u5217\u8868", "aria-label": "\u5237\u65B0\u5217\u8868", onClick: refresh },
2162
- (0, import_react8.createElement)(Icon, { name: "refresh" })
2617
+ (0, import_react9.createElement)(Icon, { name: "refresh" })
2163
2618
  ),
2164
- (0, import_react8.createElement)(
2619
+ (0, import_react9.createElement)(
2165
2620
  "button",
2166
2621
  {
2167
2622
  className: "ghost",
2168
2623
  onClick: () => void startBackup(),
2169
2624
  title: isAll ? "\u5907\u4EFD\u5F53\u524D\u6D3B\u52A8\u8D26\u53F7" : "\u5907\u4EFD\u8FD9\u4E2A\u8D26\u53F7"
2170
2625
  },
2171
- (0, import_react8.createElement)(Icon, { name: "archive" }),
2626
+ (0, import_react9.createElement)(Icon, { name: "archive" }),
2172
2627
  "\u5907\u4EFD"
2173
2628
  ),
2174
- (0, import_react8.createElement)(
2629
+ (0, import_react9.createElement)(
2175
2630
  "button",
2176
2631
  {
2177
2632
  className: railOpen ? "ghost active" : "ghost",
2178
2633
  onClick: () => setRailOpen(!railOpen),
2179
2634
  title: "AI \u5DE5\u4F5C\u53F0"
2180
2635
  },
2181
- (0, import_react8.createElement)(Icon, { name: "sparkles" }),
2636
+ (0, import_react9.createElement)(Icon, { name: "sparkles" }),
2182
2637
  "AI"
2183
2638
  ),
2184
- (0, import_react8.createElement)(
2639
+ (0, import_react9.createElement)(
2185
2640
  "button",
2186
2641
  { className: "primary", onClick: () => startCompose("new") },
2187
- (0, import_react8.createElement)(Icon, { name: "compose" }),
2642
+ (0, import_react9.createElement)(Icon, { name: "compose" }),
2188
2643
  "\u5199\u90AE\u4EF6"
2189
2644
  )
2190
2645
  ),
2191
- toast === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
2192
- error === "" ? null : (0, import_react8.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
2193
- needsLogin ? (0, import_react8.createElement)(
2646
+ toast === "" ? null : (0, import_react9.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
2647
+ error === "" ? null : (0, import_react9.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
2648
+ needsLogin ? (0, import_react9.createElement)(
2194
2649
  "div",
2195
2650
  { className: "mailharbor-settings" },
2196
- (0, import_react8.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
2197
- ) : (0, import_react8.createElement)(
2651
+ (0, import_react9.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
2652
+ ) : (0, import_react9.createElement)(
2198
2653
  "div",
2199
2654
  { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
2200
- (0, import_react8.createElement)(
2655
+ (0, import_react9.createElement)(
2201
2656
  "div",
2202
2657
  { className: "mailharbor-list" },
2203
- (0, import_react8.createElement)(
2658
+ (0, import_react9.createElement)(
2204
2659
  "div",
2205
2660
  { className: "mailharbor-filters" },
2206
- (0, import_react8.createElement)(
2661
+ (0, import_react9.createElement)(
2207
2662
  "select",
2208
2663
  {
2209
2664
  value: mailbox,
@@ -2214,10 +2669,10 @@ function MailPanel() {
2214
2669
  "aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
2215
2670
  },
2216
2671
  folderOptions.map(
2217
- (option) => (0, import_react8.createElement)("option", { key: option.value, value: option.value }, option.label)
2672
+ (option) => (0, import_react9.createElement)("option", { key: option.value, value: option.value }, option.label)
2218
2673
  )
2219
2674
  ),
2220
- (0, import_react8.createElement)("input", {
2675
+ (0, import_react9.createElement)("input", {
2221
2676
  type: "search",
2222
2677
  placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
2223
2678
  "aria-label": "\u641C\u7D22\u90AE\u4EF6",
@@ -2231,7 +2686,24 @@ function MailPanel() {
2231
2686
  }
2232
2687
  }
2233
2688
  }),
2234
- query2 !== search || search !== "" ? (0, import_react8.createElement)(
2689
+ source === "local" ? (0, import_react9.createElement)(
2690
+ "button",
2691
+ {
2692
+ className: selectMode ? "ghost active" : "ghost",
2693
+ title: "\u9009\u62E9\u591A\u5C01\u90AE\u4EF6\u52A0\u5165\u9879\u76EE",
2694
+ onClick: () => {
2695
+ if (selectMode) {
2696
+ setSelectMode(false);
2697
+ setSelectedKeys([]);
2698
+ } else {
2699
+ void enterSelect();
2700
+ }
2701
+ }
2702
+ },
2703
+ Icon({ name: "checklist", size: 14 }),
2704
+ "\u9009\u62E9"
2705
+ ) : null,
2706
+ query2 !== search || search !== "" ? (0, import_react9.createElement)(
2235
2707
  "button",
2236
2708
  {
2237
2709
  className: "subtle",
@@ -2241,22 +2713,60 @@ function MailPanel() {
2241
2713
  setSearch("");
2242
2714
  }
2243
2715
  },
2244
- (0, import_react8.createElement)(Icon, { name: "close", size: 14 })
2716
+ (0, import_react9.createElement)(Icon, { name: "close", size: 14 })
2245
2717
  ) : null
2246
2718
  ),
2247
- (0, import_react8.createElement)(
2719
+ selectMode ? (0, import_react9.createElement)(
2720
+ "div",
2721
+ { className: "mailharbor-selectbar" },
2722
+ (0, import_react9.createElement)("span", { className: "small" }, `\u5DF2\u9009 ${String(selectedKeys.length)} \u5C01`),
2723
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2724
+ (0, import_react9.createElement)(
2725
+ "select",
2726
+ {
2727
+ value: targetProject,
2728
+ onChange: (event) => setTargetProject(event.target.value),
2729
+ "aria-label": "\u76EE\u6807\u9879\u76EE"
2730
+ },
2731
+ (0, import_react9.createElement)("option", { value: "" }, projectOptions.length === 0 ? "\u8FD8\u6CA1\u6709\u9879\u76EE" : "\u9009\u62E9\u9879\u76EE"),
2732
+ projectOptions.map(
2733
+ (project) => (0, import_react9.createElement)("option", { key: project.id, value: project.id }, project.name)
2734
+ )
2735
+ ),
2736
+ (0, import_react9.createElement)(
2737
+ "button",
2738
+ {
2739
+ className: "primary",
2740
+ disabled: selectedKeys.length === 0 || targetProject === "",
2741
+ onClick: () => void addSelectedToProject()
2742
+ },
2743
+ "\u52A0\u5165\u9879\u76EE"
2744
+ ),
2745
+ (0, import_react9.createElement)(
2746
+ "button",
2747
+ {
2748
+ className: "ghost",
2749
+ onClick: () => {
2750
+ setSelectMode(false);
2751
+ setSelectedKeys([]);
2752
+ }
2753
+ },
2754
+ "\u53D6\u6D88"
2755
+ )
2756
+ ) : null,
2757
+ (0, import_react9.createElement)(
2248
2758
  "div",
2249
2759
  { className: "mailharbor-rows" },
2250
- loading && items.length === 0 && drafts.length === 0 ? (0, import_react8.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react8.createElement)(
2760
+ loading && items.length === 0 && drafts.length === 0 ? (0, import_react9.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react9.createElement)(
2251
2761
  "div",
2252
2762
  { className: "mailharbor-empty" },
2253
- (0, import_react8.createElement)(Icon, { name: "compose", size: 28 }),
2254
- (0, import_react8.createElement)("div", { className: "title" }, "\u6CA1\u6709\u8349\u7A3F"),
2255
- (0, import_react8.createElement)("div", { className: "small" }, "\u5199\u4FE1\u65F6\u70B9\u300C\u5B58\u8349\u7A3F\u300D\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002")
2763
+ (0, import_react9.createElement)(Icon, { name: "compose", size: 28 }),
2764
+ (0, import_react9.createElement)("div", { className: "title" }, "\u6CA1\u6709\u8349\u7A3F"),
2765
+ (0, import_react9.createElement)("div", { className: "small" }, "\u5199\u4FE1\u65F6\u70B9\u300C\u5B58\u8349\u7A3F\u300D\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002")
2256
2766
  ) : drafts.filter(
2257
2767
  (draft) => search === "" ? true : `${draft.subject} ${draft.to} ${draft.cc}`.toLowerCase().includes(search.toLowerCase())
2258
2768
  ).map(
2259
- (draft) => (0, import_react8.createElement)(
2769
+ (draft) => (0, import_react9.createElement)(
2260
2770
  "div",
2261
2771
  {
2262
2772
  key: draft.id,
@@ -2271,21 +2781,21 @@ function MailPanel() {
2271
2781
  }
2272
2782
  }
2273
2783
  },
2274
- (0, import_react8.createElement)(
2784
+ (0, import_react9.createElement)(
2275
2785
  "span",
2276
2786
  { className: "line-1" },
2277
- (0, import_react8.createElement)(
2787
+ (0, import_react9.createElement)(
2278
2788
  "span",
2279
2789
  { className: "from" },
2280
2790
  `\u8349\u7A3F${draft.to === "" ? "" : ` \xB7 \u6536\u4EF6\u4EBA ${draft.to}`}`
2281
2791
  ),
2282
- isAll ? (0, import_react8.createElement)(
2792
+ isAll ? (0, import_react9.createElement)(
2283
2793
  "span",
2284
2794
  { className: "badge" },
2285
2795
  accounts.find((item) => item.id === draft.accountId)?.label ?? ""
2286
2796
  ) : null,
2287
- (0, import_react8.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
2288
- (0, import_react8.createElement)(
2797
+ (0, import_react9.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
2798
+ (0, import_react9.createElement)(
2289
2799
  "button",
2290
2800
  {
2291
2801
  className: "subtle",
@@ -2295,121 +2805,132 @@ function MailPanel() {
2295
2805
  void removeDraft(draft.id, draft.accountId);
2296
2806
  }
2297
2807
  },
2298
- (0, import_react8.createElement)(Icon, { name: "close", size: 13 })
2808
+ (0, import_react9.createElement)(Icon, { name: "close", size: 13 })
2299
2809
  )
2300
2810
  ),
2301
- (0, import_react8.createElement)(
2811
+ (0, import_react9.createElement)(
2302
2812
  "span",
2303
2813
  { className: "line-2" },
2304
- (0, import_react8.createElement)(
2814
+ (0, import_react9.createElement)(
2305
2815
  "span",
2306
2816
  { className: "subject" },
2307
2817
  draft.subject === "" ? "(\u65E0\u4E3B\u9898)" : draft.subject
2308
2818
  )
2309
2819
  ),
2310
- draft.html === "" ? null : (0, import_react8.createElement)(
2820
+ draft.html === "" ? null : (0, import_react9.createElement)(
2311
2821
  "span",
2312
2822
  { className: "snippet" },
2313
2823
  draft.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120)
2314
2824
  )
2315
2825
  )
2316
- ) : items.length === 0 ? (0, import_react8.createElement)(
2826
+ ) : items.length === 0 ? (0, import_react9.createElement)(
2317
2827
  "div",
2318
2828
  { className: "mailharbor-empty" },
2319
- (0, import_react8.createElement)(Icon, { name: "mail", size: 28 }),
2320
- (0, import_react8.createElement)(
2829
+ (0, import_react9.createElement)(Icon, { name: "mail", size: 28 }),
2830
+ (0, import_react9.createElement)(
2321
2831
  "div",
2322
2832
  { className: "title" },
2323
2833
  source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6"
2324
2834
  ),
2325
- (0, import_react8.createElement)(
2835
+ (0, import_react9.createElement)(
2326
2836
  "div",
2327
2837
  { className: "small" },
2328
2838
  source === "local" ? "\u70B9\u9876\u90E8\u300C\u5907\u4EFD\u300D\u628A\u670D\u52A1\u5668\u90AE\u4EF6\u540C\u6B65\u4E0B\u6765\u3002" : "\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\u6216\u7A0D\u540E\u5237\u65B0\u3002"
2329
2839
  )
2330
2840
  ) : items.map(
2331
- (item) => (0, import_react8.createElement)(
2841
+ (item) => (0, import_react9.createElement)(
2332
2842
  "div",
2333
2843
  {
2334
2844
  key: `${item.accountId}:${item.id}`,
2335
- className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}`,
2845
+ className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}${selectMode ? " selectable" : ""}`,
2336
2846
  role: "button",
2337
2847
  tabIndex: 0,
2338
- onClick: () => void openMessage(item),
2848
+ onClick: () => {
2849
+ if (selectMode) toggleSelect(item);
2850
+ else void openMessage(item);
2851
+ },
2339
2852
  onKeyDown: (event) => {
2340
2853
  if (event.key === "Enter" || event.key === " ") {
2341
2854
  event.preventDefault();
2342
- void openMessage(item);
2855
+ if (selectMode) toggleSelect(item);
2856
+ else void openMessage(item);
2343
2857
  }
2344
2858
  }
2345
2859
  },
2346
- (0, import_react8.createElement)(
2860
+ (0, import_react9.createElement)(
2347
2861
  "span",
2348
2862
  { className: "line-1" },
2349
- item.unread ? (0, import_react8.createElement)("span", { className: "dot" }) : null,
2350
- (0, import_react8.createElement)(
2863
+ selectMode ? (0, import_react9.createElement)("input", {
2864
+ type: "checkbox",
2865
+ checked: selectedKeys.includes(`${item.accountId}:${item.id}`),
2866
+ "aria-label": "\u9009\u62E9\u90AE\u4EF6",
2867
+ onClick: (event) => event.stopPropagation(),
2868
+ onChange: () => toggleSelect(item)
2869
+ }) : item.unread ? (0, import_react9.createElement)("span", { className: "dot" }) : null,
2870
+ (0, import_react9.createElement)(
2351
2871
  "span",
2352
2872
  { className: "from" },
2353
2873
  item.from.name !== "" ? item.from.name : item.from.address
2354
2874
  ),
2355
- item.hasAttachments ? (0, import_react8.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
2356
- isAll ? (0, import_react8.createElement)(
2875
+ item.hasAttachments ? (0, import_react9.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
2876
+ isAll ? (0, import_react9.createElement)(
2357
2877
  "span",
2358
2878
  { className: "badge" },
2359
2879
  accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
2360
2880
  ) : null,
2361
- item.backedUp === false ? (0, import_react8.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
2362
- (0, import_react8.createElement)("span", { className: "date" }, shortDate(item.date))
2881
+ item.backedUp === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
2882
+ (0, import_react9.createElement)("span", { className: "date" }, shortDate(item.date))
2363
2883
  ),
2364
- (0, import_react8.createElement)(
2884
+ (0, import_react9.createElement)(
2365
2885
  "span",
2366
2886
  { className: "line-2" },
2367
- (0, import_react8.createElement)("span", { className: "subject" }, item.subject)
2887
+ (0, import_react9.createElement)("span", { className: "subject" }, item.subject)
2368
2888
  ),
2369
- item.snippet === "" ? null : (0, import_react8.createElement)("span", { className: "snippet" }, item.snippet)
2889
+ item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, item.snippet)
2370
2890
  )
2371
2891
  )
2372
2892
  ),
2373
- !showingDrafts && items.length > 0 ? (0, import_react8.createElement)(
2893
+ !showingDrafts && items.length > 0 ? (0, import_react9.createElement)(
2374
2894
  "div",
2375
2895
  { className: "mailharbor-list-foot" },
2376
- (0, import_react8.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
2377
- (0, import_react8.createElement)("span", { className: "spacer" }),
2378
- page.total > items.length ? (0, import_react8.createElement)(
2896
+ (0, import_react9.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
2897
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2898
+ page.total > items.length ? (0, import_react9.createElement)(
2379
2899
  "button",
2380
2900
  { className: "subtle", onClick: () => setLimit((current) => current + 40) },
2381
2901
  "\u52A0\u8F7D\u66F4\u591A"
2382
2902
  ) : null
2383
- ) : showingDrafts && drafts.length > 0 ? (0, import_react8.createElement)(
2903
+ ) : showingDrafts && drafts.length > 0 ? (0, import_react9.createElement)(
2384
2904
  "div",
2385
2905
  { className: "mailharbor-list-foot" },
2386
- (0, import_react8.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
2906
+ (0, import_react9.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
2387
2907
  ) : null
2388
2908
  ),
2389
- (0, import_react8.createElement)(
2909
+ (0, import_react9.createElement)(
2390
2910
  "div",
2391
2911
  { className: "mailharbor-reader" },
2392
- selected === null ? (0, import_react8.createElement)(
2912
+ selected === null ? (0, import_react9.createElement)(
2393
2913
  "div",
2394
2914
  { className: "mailharbor-empty" },
2395
- (0, import_react8.createElement)(Icon, { name: "mail", size: 34 }),
2396
- (0, import_react8.createElement)("div", { className: "title" }, "\u9009\u62E9\u4E00\u5C01\u90AE\u4EF6\u5F00\u59CB\u9605\u8BFB"),
2397
- (0, import_react8.createElement)(
2915
+ (0, import_react9.createElement)(Icon, { name: "mail", size: 34 }),
2916
+ (0, import_react9.createElement)("div", { className: "title" }, "\u9009\u62E9\u4E00\u5C01\u90AE\u4EF6\u5F00\u59CB\u9605\u8BFB"),
2917
+ (0, import_react9.createElement)(
2398
2918
  "div",
2399
2919
  { className: "small" },
2400
2920
  loggedIn ? "\u5DF2\u767B\u5F55\uFF0C\u53EF\u5207\u6362\u300C\u670D\u52A1\u5668\u5B9E\u65F6\u300D\u67E5\u770B\u6700\u65B0\u90AE\u4EF6\u3002" : "\u6D4F\u89C8\u672C\u5730\u5907\u4EFD\u4E0D\u9700\u8981\u767B\u5F55\u3002"
2401
2921
  )
2402
- ) : (0, import_react8.createElement)(Reader, {
2922
+ ) : (0, import_react9.createElement)(Reader, {
2403
2923
  detail: selected,
2404
2924
  source,
2405
2925
  mailbox,
2406
2926
  onChanged: (patch) => setSelected({ ...selected, ...patch }),
2407
2927
  onReply: (mode) => startCompose(mode),
2408
2928
  onAiAction: requestAi,
2929
+ onExtractTasks: () => void extractTasks(),
2409
2930
  onClose: narrow ? () => setSelected(null) : void 0
2410
2931
  })
2411
2932
  ),
2412
- railOpen ? (0, import_react8.createElement)(AiRail, {
2933
+ railOpen ? (0, import_react9.createElement)(AiRail, {
2413
2934
  account: accounts.find((item) => item.id === (selected?.accountId ?? accountScope)) ?? currentAccount,
2414
2935
  detail: selected,
2415
2936
  source,
@@ -2421,45 +2942,101 @@ function MailPanel() {
2421
2942
  onNotice: notice
2422
2943
  }) : null
2423
2944
  ),
2424
- showBackup && backup !== null ? (0, import_react8.createElement)(
2945
+ showBackup && backup !== null ? (0, import_react9.createElement)(
2425
2946
  Modal,
2426
2947
  {
2427
2948
  title: "\u5907\u4EFD",
2428
2949
  onClose: () => setShowBackup(false),
2429
- footer: (0, import_react8.createElement)(
2950
+ footer: (0, import_react9.createElement)(
2430
2951
  "div",
2431
2952
  { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
2432
- (0, import_react8.createElement)("span", { className: "small muted" }, backup.running ? "\u5907\u4EFD\u8FDB\u884C\u4E2D\u2026" : backup.message),
2433
- (0, import_react8.createElement)("span", { className: "spacer" }),
2434
- backup.running ? (0, import_react8.createElement)("button", { className: "ghost danger", onClick: () => void stopBackup() }, "\u505C\u6B62\u5907\u4EFD") : (0, import_react8.createElement)("button", { className: "primary", onClick: () => void startBackup() }, "\u5F00\u59CB\u5907\u4EFD"),
2435
- (0, import_react8.createElement)("button", { className: "ghost", onClick: () => setShowBackup(false) }, "\u5173\u95ED")
2953
+ (0, import_react9.createElement)("span", { className: "small muted" }, backup.running ? "\u5907\u4EFD\u8FDB\u884C\u4E2D\u2026" : backup.message),
2954
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2955
+ backup.running ? (0, import_react9.createElement)("button", { className: "ghost danger", onClick: () => void stopBackup() }, "\u505C\u6B62\u5907\u4EFD") : (0, import_react9.createElement)("button", { className: "primary", onClick: () => void startBackup() }, "\u5F00\u59CB\u5907\u4EFD"),
2956
+ (0, import_react9.createElement)("button", { className: "ghost", onClick: () => setShowBackup(false) }, "\u5173\u95ED")
2436
2957
  )
2437
2958
  },
2438
- (0, import_react8.createElement)(
2959
+ (0, import_react9.createElement)(
2439
2960
  "div",
2440
2961
  { className: "mailharbor-kv" },
2441
- (0, import_react8.createElement)("dt", null, "\u5F00\u59CB\u65F6\u95F4"),
2442
- (0, import_react8.createElement)("dd", null, backup.startedAt ?? "\u2014"),
2443
- (0, import_react8.createElement)("dt", null, "\u4E0A\u6B21\u6210\u529F"),
2444
- (0, import_react8.createElement)("dd", null, backup.lastSuccessAt ?? "\u2014"),
2445
- (0, import_react8.createElement)("dt", null, "\u65B0\u589E"),
2446
- (0, import_react8.createElement)("dd", null, String(backup.added)),
2447
- (0, import_react8.createElement)("dt", null, "\u603B\u6570"),
2448
- (0, import_react8.createElement)("dd", null, String(backup.total))
2962
+ (0, import_react9.createElement)("dt", null, "\u5F00\u59CB\u65F6\u95F4"),
2963
+ (0, import_react9.createElement)("dd", null, backup.startedAt ?? "\u2014"),
2964
+ (0, import_react9.createElement)("dt", null, "\u4E0A\u6B21\u6210\u529F"),
2965
+ (0, import_react9.createElement)("dd", null, backup.lastSuccessAt ?? "\u2014"),
2966
+ (0, import_react9.createElement)("dt", null, "\u65B0\u589E"),
2967
+ (0, import_react9.createElement)("dd", null, String(backup.added)),
2968
+ (0, import_react9.createElement)("dt", null, "\u603B\u6570"),
2969
+ (0, import_react9.createElement)("dd", null, String(backup.total))
2449
2970
  ),
2450
- (0, import_react8.createElement)(
2971
+ (0, import_react9.createElement)(
2451
2972
  "div",
2452
2973
  { className: "mailharbor-log" },
2453
2974
  backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
2454
2975
  )
2455
2976
  ) : null,
2456
- compose !== null && composeAccount !== null ? (0, import_react8.createElement)(Composer, {
2977
+ extraction !== null ? (0, import_react9.createElement)(
2978
+ Modal,
2979
+ {
2980
+ title: "\u63D0\u53D6\u4EFB\u52A1",
2981
+ onClose: () => setExtraction(null),
2982
+ footer: (0, import_react9.createElement)(
2983
+ "div",
2984
+ { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
2985
+ (0, import_react9.createElement)("span", { className: "small muted" }, "\u52FE\u9009\u8981\u4FDD\u5B58\u7684\u4EFB\u52A1\uFF1B\u4FDD\u5B58\u540E\u53EF\u5230\u300C\u4EFB\u52A1\u300D\u9762\u677F\u8DDF\u8FDB\u3002"),
2986
+ (0, import_react9.createElement)("span", { className: "spacer" }),
2987
+ (0, import_react9.createElement)("button", { className: "ghost", onClick: () => setExtraction(null) }, "\u53D6\u6D88"),
2988
+ (0, import_react9.createElement)(
2989
+ "button",
2990
+ {
2991
+ className: "primary",
2992
+ disabled: extractBusy || extraction.checked.every((value) => !value),
2993
+ onClick: () => void saveExtraction()
2994
+ },
2995
+ extractBusy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u4EFB\u52A1"
2996
+ )
2997
+ )
2998
+ },
2999
+ (0, import_react9.createElement)(
3000
+ "div",
3001
+ { style: { display: "flex", flexDirection: "column", gap: "8px" } },
3002
+ extraction.items.map(
3003
+ (item, index) => (0, import_react9.createElement)(
3004
+ "label",
3005
+ { key: `${item.title}:${String(index)}`, className: "mailharbor-extract-row" },
3006
+ (0, import_react9.createElement)("input", {
3007
+ type: "checkbox",
3008
+ checked: extraction.checked[index] ?? false,
3009
+ onChange: () => {
3010
+ const checked = [...extraction.checked];
3011
+ checked[index] = !checked[index];
3012
+ setExtraction({ ...extraction, checked });
3013
+ }
3014
+ }),
3015
+ (0, import_react9.createElement)(
3016
+ "span",
3017
+ null,
3018
+ (0, import_react9.createElement)("strong", null, item.title),
3019
+ (0, import_react9.createElement)(
3020
+ "span",
3021
+ { className: "small muted" },
3022
+ [
3023
+ item.due === "" ? "" : `\u622A\u6B62 ${item.due}`,
3024
+ item.owner === "" ? "" : `\u8D1F\u8D23\u4EBA ${item.owner}`,
3025
+ item.notes
3026
+ ].filter((part) => part !== "").join(" \xB7 ")
3027
+ )
3028
+ )
3029
+ )
3030
+ )
3031
+ )
3032
+ ) : null,
3033
+ compose !== null && composeAccount !== null ? (0, import_react9.createElement)(Composer, {
2457
3034
  account: composeAccount,
2458
3035
  initial: compose,
2459
3036
  onClose: () => setCompose(null),
2460
3037
  onSent: (message) => notice(message)
2461
3038
  }) : null,
2462
- showAdd ? (0, import_react8.createElement)(AddAccountModal, {
3039
+ showAdd ? (0, import_react9.createElement)(AddAccountModal, {
2463
3040
  onClose: () => setShowAdd(false),
2464
3041
  onDone: (next) => {
2465
3042
  setState(next);
@@ -2471,32 +3048,33 @@ function MailPanel() {
2471
3048
  }
2472
3049
 
2473
3050
  // src/client/settings.tsx
2474
- var import_react9 = require("react");
3051
+ var import_react10 = require("react");
2475
3052
  function errorText6(error) {
2476
3053
  return error instanceof ApiError || error instanceof Error ? error.message : String(error);
2477
3054
  }
2478
3055
  function MailSettings() {
2479
- const [state, setState] = (0, import_react9.useState)(null);
2480
- const [settings, setSettings] = (0, import_react9.useState)(null);
2481
- const [error, setError] = (0, import_react9.useState)("");
2482
- const [toast, setToast] = (0, import_react9.useState)("");
2483
- const [signature, setSignature] = (0, import_react9.useState)("");
2484
- const [markOnOpen, setMarkOnOpen] = (0, import_react9.useState)(true);
2485
- const [pollSeconds, setPollSeconds] = (0, import_react9.useState)(60);
2486
- const [listLimit, setListLimit] = (0, import_react9.useState)(40);
2487
- const [aiEnabled, setAiEnabled] = (0, import_react9.useState)(true);
2488
- const [aiProvider, setAiProvider] = (0, import_react9.useState)("deepseek-account");
2489
- const [aiModel, setAiModel] = (0, import_react9.useState)("deepseek-flash");
2490
- const [aiModels, setAiModels] = (0, import_react9.useState)({ providers: [] });
2491
- const [aiStatus, setAiStatus] = (0, import_react9.useState)(null);
2492
- const [styleText, setStyleText] = (0, import_react9.useState)("");
2493
- const [styleMeta, setStyleMeta] = (0, import_react9.useState)({ updatedAt: "", sampleCount: 0 });
2494
- const [styleBusy, setStyleBusy] = (0, import_react9.useState)(null);
2495
- const [editing, setEditing] = (0, import_react9.useState)(null);
2496
- const [checkResults, setCheckResults] = (0, import_react9.useState)({});
2497
- const [adding, setAdding] = (0, import_react9.useState)(false);
2498
- const [tick, setTick] = (0, import_react9.useState)(0);
2499
- (0, import_react9.useEffect)(() => {
3056
+ const [state, setState] = (0, import_react10.useState)(null);
3057
+ const [settings, setSettings] = (0, import_react10.useState)(null);
3058
+ const [error, setError] = (0, import_react10.useState)("");
3059
+ const [toast, setToast] = (0, import_react10.useState)("");
3060
+ const [signature, setSignature] = (0, import_react10.useState)("");
3061
+ const [markOnOpen, setMarkOnOpen] = (0, import_react10.useState)(true);
3062
+ const [pollSeconds, setPollSeconds] = (0, import_react10.useState)(60);
3063
+ const [listLimit, setListLimit] = (0, import_react10.useState)(40);
3064
+ const [projectRoot, setProjectRoot] = (0, import_react10.useState)("");
3065
+ const [aiEnabled, setAiEnabled] = (0, import_react10.useState)(true);
3066
+ const [aiProvider, setAiProvider] = (0, import_react10.useState)("deepseek-account");
3067
+ const [aiModel, setAiModel] = (0, import_react10.useState)("deepseek-flash");
3068
+ const [aiModels, setAiModels] = (0, import_react10.useState)({ providers: [] });
3069
+ const [aiStatus, setAiStatus] = (0, import_react10.useState)(null);
3070
+ const [styleText, setStyleText] = (0, import_react10.useState)("");
3071
+ const [styleMeta, setStyleMeta] = (0, import_react10.useState)({ updatedAt: "", sampleCount: 0 });
3072
+ const [styleBusy, setStyleBusy] = (0, import_react10.useState)(null);
3073
+ const [editing, setEditing] = (0, import_react10.useState)(null);
3074
+ const [checkResults, setCheckResults] = (0, import_react10.useState)({});
3075
+ const [adding, setAdding] = (0, import_react10.useState)(false);
3076
+ const [tick, setTick] = (0, import_react10.useState)(0);
3077
+ (0, import_react10.useEffect)(() => {
2500
3078
  let cancelled = false;
2501
3079
  Promise.all([api.state(), api.settings()]).then(([appState, view]) => {
2502
3080
  if (cancelled) return;
@@ -2506,6 +3084,7 @@ function MailSettings() {
2506
3084
  setMarkOnOpen(view.read.markOnOpen);
2507
3085
  setPollSeconds(view.read.pollSeconds);
2508
3086
  setListLimit(view.render.listLimit);
3087
+ setProjectRoot(view.projects.root);
2509
3088
  setAiEnabled(view.ai.enabled);
2510
3089
  setAiProvider(view.ai.provider);
2511
3090
  setAiModel(view.ai.model);
@@ -2516,7 +3095,7 @@ function MailSettings() {
2516
3095
  cancelled = true;
2517
3096
  };
2518
3097
  }, [tick]);
2519
- (0, import_react9.useEffect)(() => {
3098
+ (0, import_react10.useEffect)(() => {
2520
3099
  let cancelled = false;
2521
3100
  const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
2522
3101
  if (accountId === null) return;
@@ -2559,24 +3138,24 @@ function MailSettings() {
2559
3138
  };
2560
3139
  const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
2561
3140
  if (state === null || settings === null) {
2562
- return (0, import_react9.createElement)("div", { className: "mailharbor mailharbor-settings" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" : error);
3141
+ return (0, import_react10.createElement)("div", { className: "mailharbor mailharbor-settings" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" : error);
2563
3142
  }
2564
- return (0, import_react9.createElement)(
3143
+ return (0, import_react10.createElement)(
2565
3144
  "div",
2566
- { className: "mailharbor" },
2567
- (0, import_react9.createElement)(
3145
+ { className: "mailharbor mailharbor-settings-page" },
3146
+ (0, import_react10.createElement)(
2568
3147
  "div",
2569
3148
  { className: "mailharbor-settings" },
2570
- (0, import_react9.createElement)("h2", null, "MailHarbor"),
2571
- (0, import_react9.createElement)("p", { className: "small muted" }, "\u591A\u8D26\u53F7 IMAP/SMTP \u90AE\u7BB1\u3001\u672C\u5730\u5907\u4EFD\u3001AI \u4E0E\u4EFB\u52A1\u3002\u8BBE\u7F6E\u4F1A\u7ACB\u5373\u751F\u6548\u3002"),
2572
- toast === "" ? null : (0, import_react9.createElement)("div", { className: "small", style: { color: "var(--m-ok)" } }, toast),
2573
- error === "" ? null : (0, import_react9.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
2574
- (0, import_react9.createElement)(
3149
+ (0, import_react10.createElement)("h2", null, "MailHarbor"),
3150
+ (0, import_react10.createElement)("p", { className: "small muted" }, "\u591A\u8D26\u53F7 IMAP/SMTP \u90AE\u7BB1\u3001\u672C\u5730\u5907\u4EFD\u3001AI \u4E0E\u4EFB\u52A1\u3002\u8BBE\u7F6E\u4F1A\u7ACB\u5373\u751F\u6548\u3002"),
3151
+ toast === "" ? null : (0, import_react10.createElement)("div", { className: "small", style: { color: "var(--m-ok)" } }, toast),
3152
+ error === "" ? null : (0, import_react10.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
3153
+ (0, import_react10.createElement)(
2575
3154
  "div",
2576
3155
  { className: "mailharbor-card" },
2577
- (0, import_react9.createElement)("h3", null, "\u8D26\u53F7"),
2578
- settings.accounts.length === 0 ? (0, import_react9.createElement)(AddAccountForm, { onDone: () => reload() }) : settings.accounts.map(
2579
- (item) => (0, import_react9.createElement)(AccountRow, {
3156
+ (0, import_react10.createElement)("h3", null, "\u8D26\u53F7"),
3157
+ settings.accounts.length === 0 ? (0, import_react10.createElement)(AddAccountForm, { onDone: () => reload() }) : settings.accounts.map(
3158
+ (item) => (0, import_react10.createElement)(AccountRow, {
2580
3159
  key: item.id,
2581
3160
  account: item,
2582
3161
  active: item.id === settings.activeAccountId,
@@ -2621,32 +3200,32 @@ function MailSettings() {
2621
3200
  }
2622
3201
  })
2623
3202
  ),
2624
- adding ? (0, import_react9.createElement)(AddAccountForm, {
3203
+ adding ? (0, import_react10.createElement)(AddAccountForm, {
2625
3204
  onDone: () => {
2626
3205
  setAdding(false);
2627
3206
  reload();
2628
3207
  },
2629
3208
  onCancel: () => setAdding(false)
2630
- }) : (0, import_react9.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
3209
+ }) : (0, import_react10.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
2631
3210
  ),
2632
- (0, import_react9.createElement)(
3211
+ (0, import_react10.createElement)(
2633
3212
  "div",
2634
3213
  { className: "mailharbor-card" },
2635
- (0, import_react9.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
2636
- (0, import_react9.createElement)(
3214
+ (0, import_react10.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
3215
+ (0, import_react10.createElement)(
2637
3216
  Field,
2638
3217
  { label: "\u7B7E\u540D\uFF08\u7EAF\u6587\u672C\uFF0C\u53D1\u9001\u65F6\u8FFD\u52A0\u5230\u6B63\u6587\u672B\u5C3E\uFF09" },
2639
- (0, import_react9.createElement)("textarea", {
3218
+ (0, import_react10.createElement)("textarea", {
2640
3219
  rows: 4,
2641
3220
  value: signature,
2642
3221
  onChange: (event) => setSignature(event.target.value)
2643
3222
  })
2644
3223
  ),
2645
- (0, import_react9.createElement)(
3224
+ (0, import_react10.createElement)(
2646
3225
  "div",
2647
3226
  { style: { display: "flex", gap: "10px", alignItems: "center", flexWrap: "wrap" } },
2648
- (0, import_react9.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
2649
- (0, import_react9.createElement)(Toggle, {
3227
+ (0, import_react10.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
3228
+ (0, import_react10.createElement)(Toggle, {
2650
3229
  checked: markOnOpen,
2651
3230
  onChange: (value) => {
2652
3231
  setMarkOnOpen(value);
@@ -2655,13 +3234,13 @@ function MailSettings() {
2655
3234
  label: "\u6253\u5F00\u90AE\u4EF6\u65F6\u6807\u4E3A\u5DF2\u8BFB"
2656
3235
  })
2657
3236
  ),
2658
- (0, import_react9.createElement)(
3237
+ (0, import_react10.createElement)(
2659
3238
  "div",
2660
3239
  { className: "mailharbor-grid" },
2661
- (0, import_react9.createElement)(
3240
+ (0, import_react10.createElement)(
2662
3241
  Field,
2663
3242
  { label: "\u5217\u8868\u6BCF\u9875\u6570\u91CF" },
2664
- (0, import_react9.createElement)("input", {
3243
+ (0, import_react10.createElement)("input", {
2665
3244
  type: "number",
2666
3245
  min: 10,
2667
3246
  max: 200,
@@ -2670,10 +3249,10 @@ function MailSettings() {
2670
3249
  onBlur: () => void saveGeneral({ render: { listLimit } })
2671
3250
  })
2672
3251
  ),
2673
- (0, import_react9.createElement)(
3252
+ (0, import_react10.createElement)(
2674
3253
  Field,
2675
3254
  { label: "\u672A\u8BFB\u89D2\u6807\u8F6E\u8BE2\uFF08\u79D2\uFF09" },
2676
- (0, import_react9.createElement)("input", {
3255
+ (0, import_react10.createElement)("input", {
2677
3256
  type: "number",
2678
3257
  min: 15,
2679
3258
  max: 3600,
@@ -2684,11 +3263,38 @@ function MailSettings() {
2684
3263
  )
2685
3264
  )
2686
3265
  ),
2687
- (0, import_react9.createElement)(
3266
+ (0, import_react10.createElement)(
3267
+ "div",
3268
+ { className: "mailharbor-card" },
3269
+ (0, import_react10.createElement)("h3", null, "\u9879\u76EE"),
3270
+ (0, import_react10.createElement)("p", { className: "small muted" }, "\u4ECE\u90AE\u4EF6\u521B\u5EFA\u9879\u76EE\u65F6\u7684\u9ED8\u8BA4\u76EE\u5F55\uFF1B\u5355\u4E2A\u9879\u76EE\u53EF\u4EE5\u5728\u521B\u5EFA\u65F6\u6539\u3002"),
3271
+ (0, import_react10.createElement)(
3272
+ "div",
3273
+ { className: "mailharbor-grid", style: { gridTemplateColumns: "minmax(0, 1fr) auto" } },
3274
+ (0, import_react10.createElement)(
3275
+ Field,
3276
+ { label: "\u9ED8\u8BA4\u9879\u76EE\u76EE\u5F55" },
3277
+ (0, import_react10.createElement)("input", {
3278
+ value: projectRoot,
3279
+ onChange: (event) => setProjectRoot(event.target.value)
3280
+ })
3281
+ ),
3282
+ (0, import_react10.createElement)(
3283
+ "button",
3284
+ {
3285
+ className: "ghost small",
3286
+ style: { alignSelf: "end" },
3287
+ onClick: () => void saveGeneral({ projects: { root: projectRoot } })
3288
+ },
3289
+ "\u4FDD\u5B58\u76EE\u5F55"
3290
+ )
3291
+ )
3292
+ ),
3293
+ (0, import_react10.createElement)(
2688
3294
  "div",
2689
3295
  { className: "mailharbor-card" },
2690
- (0, import_react9.createElement)("h3", null, "AI"),
2691
- (0, import_react9.createElement)(Toggle, {
3296
+ (0, import_react10.createElement)("h3", null, "AI"),
3297
+ (0, import_react10.createElement)(Toggle, {
2692
3298
  checked: aiEnabled,
2693
3299
  onChange: (value) => {
2694
3300
  setAiEnabled(value);
@@ -2696,22 +3302,22 @@ function MailSettings() {
2696
3302
  },
2697
3303
  label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001AI \u56DE\u590D\u3001\u7FFB\u8BD1\u3001\u5199\u4F5C\u98CE\u683C\uFF09"
2698
3304
  }),
2699
- aiStatus === null ? null : (0, import_react9.createElement)(
3305
+ aiStatus === null ? null : (0, import_react10.createElement)(
2700
3306
  "div",
2701
3307
  { className: "small", style: { color: aiStatus.available ? "var(--m-ok)" : "var(--m-warn)" } },
2702
3308
  aiStatus.available ? `\u6A21\u578B\u670D\u52A1\u53EF\u7528 \xB7 ${aiStatus.provider} / ${aiStatus.model}` : aiStatus.error ?? "\u5F53\u524D DSH \u7EC4\u5408\u6CA1\u6709\u53EF\u7528\u7684\u6A21\u578B\u670D\u52A1"
2703
3309
  ),
2704
- (0, import_react9.createElement)(
3310
+ (0, import_react10.createElement)(
2705
3311
  "div",
2706
3312
  { className: "mailharbor-grid" },
2707
- (0, import_react9.createElement)(
3313
+ (0, import_react10.createElement)(
2708
3314
  Field,
2709
3315
  { label: "Provider" },
2710
- aiModels.providers.length === 0 ? (0, import_react9.createElement)("input", {
3316
+ aiModels.providers.length === 0 ? (0, import_react10.createElement)("input", {
2711
3317
  value: aiProvider,
2712
3318
  onChange: (event) => setAiProvider(event.target.value),
2713
3319
  onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
2714
- }) : (0, import_react9.createElement)(
3320
+ }) : (0, import_react10.createElement)(
2715
3321
  "select",
2716
3322
  {
2717
3323
  value: aiProvider,
@@ -2729,20 +3335,20 @@ function MailSettings() {
2729
3335
  }
2730
3336
  },
2731
3337
  aiModels.providers.map(
2732
- (item) => (0, import_react9.createElement)("option", { key: item.provider, value: item.provider }, item.name)
3338
+ (item) => (0, import_react10.createElement)("option", { key: item.provider, value: item.provider }, item.name)
2733
3339
  )
2734
3340
  )
2735
3341
  ),
2736
- (0, import_react9.createElement)(
3342
+ (0, import_react10.createElement)(
2737
3343
  Field,
2738
3344
  { label: "\u6A21\u578B" },
2739
3345
  (() => {
2740
3346
  const provider = aiModels.providers.find((item) => item.provider === aiProvider);
2741
- return provider === void 0 || provider.models.length === 0 ? (0, import_react9.createElement)("input", {
3347
+ return provider === void 0 || provider.models.length === 0 ? (0, import_react10.createElement)("input", {
2742
3348
  value: aiModel,
2743
3349
  onChange: (event) => setAiModel(event.target.value),
2744
3350
  onBlur: () => void saveGeneral({ ai: { model: aiModel } })
2745
- }) : (0, import_react9.createElement)(
3351
+ }) : (0, import_react10.createElement)(
2746
3352
  "select",
2747
3353
  {
2748
3354
  value: aiModel,
@@ -2751,37 +3357,37 @@ function MailSettings() {
2751
3357
  void saveGeneral({ ai: { model: event.target.value } });
2752
3358
  }
2753
3359
  },
2754
- provider.models.map((item) => (0, import_react9.createElement)("option", { key: item.id, value: item.id }, item.name))
3360
+ provider.models.map((item) => (0, import_react10.createElement)("option", { key: item.id, value: item.id }, item.name))
2755
3361
  );
2756
3362
  })()
2757
3363
  )
2758
3364
  ),
2759
- (0, import_react9.createElement)("hr", null),
2760
- (0, import_react9.createElement)(
3365
+ (0, import_react10.createElement)("hr", null),
3366
+ (0, import_react10.createElement)(
2761
3367
  "div",
2762
3368
  { style: { display: "flex", alignItems: "center", gap: "8px" } },
2763
- (0, import_react9.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
2764
- (0, import_react9.createElement)(
3369
+ (0, import_react10.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
3370
+ (0, import_react10.createElement)(
2765
3371
  "span",
2766
3372
  { className: "small muted" },
2767
3373
  styleMeta.updatedAt === "" ? "\u8FD8\u6CA1\u6709\u751F\u6210" : `${styleMeta.sampleCount} \u5C01\u5DF2\u53D1\u9001\u90AE\u4EF6 \xB7 ${new Date(styleMeta.updatedAt).toLocaleString()}`
2768
3374
  )
2769
3375
  ),
2770
- (0, import_react9.createElement)(
3376
+ (0, import_react10.createElement)(
2771
3377
  "p",
2772
3378
  { className: "small muted" },
2773
3379
  "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60\u4F60\u7684\u79F0\u547C\u3001\u7ED3\u5C3E\u3001\u8BED\u6C14\u548C\u5E38\u7528\u8868\u8FBE\uFF0CAI \u56DE\u590D\u65F6\u4F1A\u53C2\u8003\u5B83\u3002\u53EA\u4FDD\u5B58\u63D0\u70BC\u540E\u7684\u6863\u6848\uFF0C\u4E0D\u4FDD\u5B58\u90AE\u4EF6\u539F\u6587\u3002"
2774
3380
  ),
2775
- (0, import_react9.createElement)("textarea", {
3381
+ (0, import_react10.createElement)("textarea", {
2776
3382
  rows: 6,
2777
3383
  value: styleText,
2778
3384
  placeholder: "\u70B9\u300C\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60\u300D\u751F\u6210\uFF0C\u6216\u76F4\u63A5\u5199\u4E00\u6BB5\u4F60\u7684\u5199\u4F5C\u98CE\u683C",
2779
3385
  onChange: (event) => setStyleText(event.target.value)
2780
3386
  }),
2781
- (0, import_react9.createElement)(
3387
+ (0, import_react10.createElement)(
2782
3388
  "div",
2783
3389
  { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
2784
- (0, import_react9.createElement)(
3390
+ (0, import_react10.createElement)(
2785
3391
  "button",
2786
3392
  {
2787
3393
  className: "ghost",
@@ -2800,7 +3406,7 @@ function MailSettings() {
2800
3406
  },
2801
3407
  styleBusy === "learn" ? "\u5B66\u4E60\u4E2D\u2026" : "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60"
2802
3408
  ),
2803
- (0, import_react9.createElement)(
3409
+ (0, import_react10.createElement)(
2804
3410
  "button",
2805
3411
  {
2806
3412
  className: "primary",
@@ -2819,36 +3425,36 @@ function MailSettings() {
2819
3425
  "\u4FDD\u5B58\u98CE\u683C"
2820
3426
  )
2821
3427
  ),
2822
- (0, import_react9.createElement)(
3428
+ (0, import_react10.createElement)(
2823
3429
  "p",
2824
3430
  { className: "small muted" },
2825
3431
  "AI \u8C03\u7528\u8D70 DSH \u81EA\u5DF1\u7684\u6A21\u578B\u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4FDD\u5B58\u5BC6\u94A5\uFF1B\u53D1\u9001\u4ECD\u7136\u53EA\u7531\u4F60\u786E\u8BA4\u3002"
2826
3432
  )
2827
3433
  ),
2828
- (0, import_react9.createElement)(
3434
+ (0, import_react10.createElement)(
2829
3435
  "div",
2830
3436
  { className: "mailharbor-card" },
2831
- (0, import_react9.createElement)("h3", null, "\u6570\u636E"),
2832
- (0, import_react9.createElement)(
3437
+ (0, import_react10.createElement)("h3", null, "\u6570\u636E"),
3438
+ (0, import_react10.createElement)(
2833
3439
  "dl",
2834
3440
  { className: "mailharbor-kv" },
2835
- (0, import_react9.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
2836
- (0, import_react9.createElement)("dd", null, settings.dataDir),
2837
- (0, import_react9.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
2838
- (0, import_react9.createElement)("dd", null, settings.configFile),
2839
- (0, import_react9.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
2840
- (0, import_react9.createElement)(
3441
+ (0, import_react10.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
3442
+ (0, import_react10.createElement)("dd", null, settings.dataDir),
3443
+ (0, import_react10.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
3444
+ (0, import_react10.createElement)("dd", null, settings.configFile),
3445
+ (0, import_react10.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
3446
+ (0, import_react10.createElement)(
2841
3447
  "dd",
2842
3448
  null,
2843
3449
  account === null ? "\u2014" : `${account.dataDir.startsWith("/") ? account.dataDir : `${settings.dataDir}/${account.dataDir}`}/mail.db`
2844
3450
  ),
2845
- (0, import_react9.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
2846
- (0, import_react9.createElement)("dd", null, state.version)
3451
+ (0, import_react10.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
3452
+ (0, import_react10.createElement)("dd", null, state.version)
2847
3453
  ),
2848
- (0, import_react9.createElement)(
3454
+ (0, import_react10.createElement)(
2849
3455
  "div",
2850
3456
  { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
2851
- account === null ? null : (0, import_react9.createElement)(
3457
+ account === null ? null : (0, import_react10.createElement)(
2852
3458
  "button",
2853
3459
  {
2854
3460
  className: "ghost",
@@ -2883,20 +3489,41 @@ function AccountRow({
2883
3489
  onSave,
2884
3490
  onRemove
2885
3491
  }) {
2886
- const [password, setPassword] = (0, import_react9.useState)("");
2887
- const [remember, setRemember] = (0, import_react9.useState)(true);
2888
- const [label, setLabel] = (0, import_react9.useState)(account.label);
2889
- const [username, setUsername] = (0, import_react9.useState)(account.username);
2890
- const [imapHost, setImapHost] = (0, import_react9.useState)(account.imap.host);
2891
- const [imapPort, setImapPort] = (0, import_react9.useState)(String(account.imap.port));
2892
- const [imapSecure, setImapSecure] = (0, import_react9.useState)(account.imap.secure);
2893
- const [smtpHost, setSmtpHost] = (0, import_react9.useState)(account.smtp.host);
2894
- const [smtpPort, setSmtpPort] = (0, import_react9.useState)(String(account.smtp.port));
2895
- const [smtpSecure, setSmtpSecure] = (0, import_react9.useState)(account.smtp.secure);
2896
- const [fromName, setFromName] = (0, import_react9.useState)(account.smtp.fromName);
2897
- const [busy, setBusy] = (0, import_react9.useState)(false);
2898
- const [checking, setChecking] = (0, import_react9.useState)(false);
2899
- (0, import_react9.useEffect)(() => {
3492
+ const [password, setPassword] = (0, import_react10.useState)("");
3493
+ const [remember, setRemember] = (0, import_react10.useState)(true);
3494
+ const [label, setLabel] = (0, import_react10.useState)(account.label);
3495
+ const [username, setUsername] = (0, import_react10.useState)(account.username);
3496
+ const [imapHost, setImapHost] = (0, import_react10.useState)(account.imap.host);
3497
+ const [imapPort, setImapPort] = (0, import_react10.useState)(String(account.imap.port));
3498
+ const [imapSecure, setImapSecure] = (0, import_react10.useState)(account.imap.secure);
3499
+ const [smtpHost, setSmtpHost] = (0, import_react10.useState)(account.smtp.host);
3500
+ const [smtpPort, setSmtpPort] = (0, import_react10.useState)(String(account.smtp.port));
3501
+ const [smtpSecure, setSmtpSecure] = (0, import_react10.useState)(account.smtp.secure);
3502
+ const [fromName, setFromName] = (0, import_react10.useState)(account.smtp.fromName);
3503
+ const [busy, setBusy] = (0, import_react10.useState)(false);
3504
+ const [checking, setChecking] = (0, import_react10.useState)(false);
3505
+ const [caldavUrl, setCaldavUrl] = (0, import_react10.useState)(account.caldav?.url ?? "");
3506
+ const [caldavUser, setCaldavUser] = (0, import_react10.useState)(account.caldav?.username ?? "");
3507
+ const [caldavHref, setCaldavHref] = (0, import_react10.useState)(account.caldav?.calendarHref ?? "");
3508
+ const [caldavPassword, setCaldavPassword] = (0, import_react10.useState)("");
3509
+ const [caldavStatus, setCaldavStatus] = (0, import_react10.useState)("");
3510
+ (0, import_react10.useEffect)(() => {
3511
+ if (!editing) return;
3512
+ let cancelled = false;
3513
+ api.taskCaldav(account.id).then((info) => {
3514
+ if (cancelled) return;
3515
+ setCaldavUrl(info.caldav?.url ?? "");
3516
+ setCaldavUser(info.caldav?.username ?? "");
3517
+ setCaldavHref(info.caldav?.calendarHref ?? "");
3518
+ setCaldavStatus(info.hasPassword ? "\u5DF2\u4FDD\u5B58\u5E94\u7528\u5BC6\u7801" : "\u5C1A\u672A\u4FDD\u5B58\u5E94\u7528\u5BC6\u7801");
3519
+ }).catch(() => {
3520
+ if (!cancelled) setCaldavStatus("\u65E0\u6CD5\u8BFB\u53D6 CalDAV \u914D\u7F6E");
3521
+ });
3522
+ return () => {
3523
+ cancelled = true;
3524
+ };
3525
+ }, [editing, account.id]);
3526
+ (0, import_react10.useEffect)(() => {
2900
3527
  setLabel(account.label);
2901
3528
  setUsername(account.username);
2902
3529
  setImapHost(account.imap.host);
@@ -2915,24 +3542,24 @@ function AccountRow({
2915
3542
  setBusy(false);
2916
3543
  }
2917
3544
  };
2918
- return (0, import_react9.createElement)(
3545
+ return (0, import_react10.createElement)(
2919
3546
  "div",
2920
3547
  { className: "mailharbor-card", style: { background: "var(--m-bg-1)" } },
2921
- (0, import_react9.createElement)(
3548
+ (0, import_react10.createElement)(
2922
3549
  "div",
2923
3550
  { className: "mailharbor-account-row" },
2924
- (0, import_react9.createElement)(Avatar, { email: account.email, size: 30 }),
2925
- (0, import_react9.createElement)(
3551
+ (0, import_react10.createElement)(Avatar, { email: account.email, size: 30 }),
3552
+ (0, import_react10.createElement)(
2926
3553
  "div",
2927
3554
  { style: { display: "flex", flexDirection: "column", minWidth: 0 } },
2928
- (0, import_react9.createElement)("strong", null, account.label),
2929
- (0, import_react9.createElement)("span", { className: "small muted" }, account.email)
3555
+ (0, import_react10.createElement)("strong", null, account.label),
3556
+ (0, import_react10.createElement)("span", { className: "small muted" }, account.email)
2930
3557
  ),
2931
- active ? (0, import_react9.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
2932
- loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
2933
- (0, import_react9.createElement)("span", { className: "spacer" }),
2934
- active ? null : (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
2935
- (0, import_react9.createElement)(
3558
+ active ? (0, import_react10.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
3559
+ loggedIn === false ? (0, import_react10.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
3560
+ (0, import_react10.createElement)("span", { className: "spacer" }),
3561
+ active ? null : (0, import_react10.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
3562
+ (0, import_react10.createElement)(
2936
3563
  "button",
2937
3564
  {
2938
3565
  className: "ghost small",
@@ -2944,9 +3571,9 @@ function AccountRow({
2944
3571
  },
2945
3572
  checking ? "\u68C0\u67E5\u4E2D\u2026" : "\u68C0\u67E5\u8FDE\u63A5"
2946
3573
  ),
2947
- (0, import_react9.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
3574
+ (0, import_react10.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
2948
3575
  ),
2949
- checkResult === "" ? null : (0, import_react9.createElement)(
3576
+ checkResult === "" ? null : (0, import_react10.createElement)(
2950
3577
  "div",
2951
3578
  {
2952
3579
  className: "mailharbor-check",
@@ -2954,83 +3581,83 @@ function AccountRow({
2954
3581
  },
2955
3582
  checkResult
2956
3583
  ),
2957
- editing ? (0, import_react9.createElement)(
3584
+ editing ? (0, import_react10.createElement)(
2958
3585
  "div",
2959
3586
  { style: { display: "flex", flexDirection: "column", gap: "10px" } },
2960
- (0, import_react9.createElement)(
3587
+ (0, import_react10.createElement)(
2961
3588
  "div",
2962
3589
  { className: "mailharbor-grid" },
2963
- (0, import_react9.createElement)(
3590
+ (0, import_react10.createElement)(
2964
3591
  Field,
2965
3592
  { label: "\u663E\u793A\u540D\u79F0" },
2966
- (0, import_react9.createElement)("input", {
3593
+ (0, import_react10.createElement)("input", {
2967
3594
  value: label,
2968
3595
  onChange: (event) => setLabel(event.target.value)
2969
3596
  })
2970
3597
  ),
2971
- (0, import_react9.createElement)(
3598
+ (0, import_react10.createElement)(
2972
3599
  Field,
2973
3600
  { label: "\u767B\u5F55\u7528\u6237\u540D" },
2974
- (0, import_react9.createElement)("input", {
3601
+ (0, import_react10.createElement)("input", {
2975
3602
  value: username,
2976
3603
  onChange: (event) => setUsername(event.target.value)
2977
3604
  })
2978
3605
  )
2979
3606
  ),
2980
- (0, import_react9.createElement)(
3607
+ (0, import_react10.createElement)(
2981
3608
  "div",
2982
3609
  { className: "mailharbor-grid three" },
2983
- (0, import_react9.createElement)(
3610
+ (0, import_react10.createElement)(
2984
3611
  Field,
2985
3612
  { label: "IMAP \u4E3B\u673A" },
2986
- (0, import_react9.createElement)("input", {
3613
+ (0, import_react10.createElement)("input", {
2987
3614
  value: imapHost,
2988
3615
  onChange: (event) => setImapHost(event.target.value)
2989
3616
  })
2990
3617
  ),
2991
- (0, import_react9.createElement)(
3618
+ (0, import_react10.createElement)(
2992
3619
  Field,
2993
3620
  { label: "\u7AEF\u53E3" },
2994
- (0, import_react9.createElement)("input", {
3621
+ (0, import_react10.createElement)("input", {
2995
3622
  value: imapPort,
2996
3623
  onChange: (event) => setImapPort(event.target.value)
2997
3624
  })
2998
3625
  ),
2999
- (0, import_react9.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
3626
+ (0, import_react10.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
3000
3627
  ),
3001
- (0, import_react9.createElement)(
3628
+ (0, import_react10.createElement)(
3002
3629
  "div",
3003
3630
  { className: "mailharbor-grid three" },
3004
- (0, import_react9.createElement)(
3631
+ (0, import_react10.createElement)(
3005
3632
  Field,
3006
3633
  { label: "SMTP \u4E3B\u673A" },
3007
- (0, import_react9.createElement)("input", {
3634
+ (0, import_react10.createElement)("input", {
3008
3635
  value: smtpHost,
3009
3636
  onChange: (event) => setSmtpHost(event.target.value)
3010
3637
  })
3011
3638
  ),
3012
- (0, import_react9.createElement)(
3639
+ (0, import_react10.createElement)(
3013
3640
  Field,
3014
3641
  { label: "\u7AEF\u53E3" },
3015
- (0, import_react9.createElement)("input", {
3642
+ (0, import_react10.createElement)("input", {
3016
3643
  value: smtpPort,
3017
3644
  onChange: (event) => setSmtpPort(event.target.value)
3018
3645
  })
3019
3646
  ),
3020
- (0, import_react9.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
3647
+ (0, import_react10.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
3021
3648
  ),
3022
- (0, import_react9.createElement)(
3649
+ (0, import_react10.createElement)(
3023
3650
  Field,
3024
3651
  { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0" },
3025
- (0, import_react9.createElement)("input", {
3652
+ (0, import_react10.createElement)("input", {
3026
3653
  value: fromName,
3027
3654
  onChange: (event) => setFromName(event.target.value)
3028
3655
  })
3029
3656
  ),
3030
- (0, import_react9.createElement)(
3657
+ (0, import_react10.createElement)(
3031
3658
  "div",
3032
3659
  { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
3033
- (0, import_react9.createElement)(
3660
+ (0, import_react10.createElement)(
3034
3661
  "button",
3035
3662
  {
3036
3663
  className: "primary small",
@@ -3046,7 +3673,7 @@ function AccountRow({
3046
3673
  },
3047
3674
  "\u4FDD\u5B58\u8D26\u53F7"
3048
3675
  ),
3049
- (0, import_react9.createElement)(
3676
+ (0, import_react10.createElement)(
3050
3677
  "button",
3051
3678
  {
3052
3679
  className: "ghost small danger",
@@ -3055,21 +3682,90 @@ function AccountRow({
3055
3682
  "\u79FB\u9664\u8D26\u53F7"
3056
3683
  )
3057
3684
  ),
3058
- (0, import_react9.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
3059
- (0, import_react9.createElement)(
3685
+ (0, import_react10.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
3686
+ (0, import_react10.createElement)("h4", { style: { margin: 0, fontSize: "12.5px" } }, "CalDAV \u4EFB\u52A1\u540C\u6B65"),
3687
+ (0, import_react10.createElement)(
3688
+ "div",
3689
+ { className: "mailharbor-grid" },
3690
+ (0, import_react10.createElement)(
3691
+ Field,
3692
+ { label: "CalDAV \u5730\u5740" },
3693
+ (0, import_react10.createElement)("input", {
3694
+ value: caldavUrl,
3695
+ placeholder: "https://caldav.example.com/",
3696
+ onChange: (event) => setCaldavUrl(event.target.value)
3697
+ })
3698
+ ),
3699
+ (0, import_react10.createElement)(
3700
+ Field,
3701
+ { label: "\u7528\u6237\u540D" },
3702
+ (0, import_react10.createElement)("input", {
3703
+ value: caldavUser,
3704
+ onChange: (event) => setCaldavUser(event.target.value)
3705
+ })
3706
+ )
3707
+ ),
3708
+ (0, import_react10.createElement)(
3709
+ "div",
3710
+ { className: "mailharbor-grid" },
3711
+ (0, import_react10.createElement)(
3712
+ Field,
3713
+ { label: "\u5E94\u7528\u5BC6\u7801\uFF08\u7559\u7A7A\u8868\u793A\u4E0D\u4FEE\u6539\uFF09" },
3714
+ (0, import_react10.createElement)("input", {
3715
+ type: "password",
3716
+ value: caldavPassword,
3717
+ onChange: (event) => setCaldavPassword(event.target.value)
3718
+ })
3719
+ ),
3720
+ (0, import_react10.createElement)(
3721
+ Field,
3722
+ { label: "\u4EFB\u52A1\u65E5\u5386\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09" },
3723
+ (0, import_react10.createElement)("input", {
3724
+ value: caldavHref,
3725
+ placeholder: "\u81EA\u52A8\u53D1\u73B0",
3726
+ onChange: (event) => setCaldavHref(event.target.value)
3727
+ })
3728
+ )
3729
+ ),
3730
+ (0, import_react10.createElement)(
3731
+ "div",
3732
+ { style: { display: "flex", gap: "8px", alignItems: "center" } },
3733
+ (0, import_react10.createElement)(
3734
+ "button",
3735
+ {
3736
+ className: "ghost small",
3737
+ onClick: () => {
3738
+ setCaldavStatus("\u4FDD\u5B58\u4E2D\u2026");
3739
+ void api.saveTaskCaldav({
3740
+ accountId: account.id,
3741
+ url: caldavUrl,
3742
+ username: caldavUser,
3743
+ ...caldavPassword === "" ? {} : { password: caldavPassword },
3744
+ calendarHref: caldavHref
3745
+ }).then((info) => {
3746
+ setCaldavStatus(info.hasPassword ? "\u5DF2\u4FDD\u5B58" : "\u672A\u4FDD\u5B58\u5BC6\u7801");
3747
+ setCaldavPassword("");
3748
+ }).catch((caught) => setCaldavStatus(errorText6(caught)));
3749
+ }
3750
+ },
3751
+ "\u4FDD\u5B58 CalDAV"
3752
+ ),
3753
+ (0, import_react10.createElement)("span", { className: "small muted" }, caldavStatus)
3754
+ ),
3755
+ (0, import_react10.createElement)(
3060
3756
  Field,
3061
3757
  { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801\uFF08\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09" },
3062
- (0, import_react9.createElement)("input", {
3758
+ (0, import_react10.createElement)("input", {
3063
3759
  type: "password",
3064
3760
  value: password,
3065
3761
  onChange: (event) => setPassword(event.target.value)
3066
3762
  })
3067
3763
  ),
3068
- (0, import_react9.createElement)(
3764
+ (0, import_react10.createElement)(
3069
3765
  "div",
3070
3766
  { style: { display: "flex", gap: "8px", alignItems: "center" } },
3071
- (0, import_react9.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
3072
- (0, import_react9.createElement)(
3767
+ (0, import_react10.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
3768
+ (0, import_react10.createElement)(
3073
3769
  "button",
3074
3770
  {
3075
3771
  className: "primary small",
@@ -3081,16 +3777,588 @@ function AccountRow({
3081
3777
  },
3082
3778
  "\u767B\u5F55"
3083
3779
  ),
3084
- (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
3780
+ (0, import_react10.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
3085
3781
  )
3086
3782
  ) : null
3087
3783
  );
3088
3784
  }
3089
3785
 
3786
+ // src/client/tasks-panel.tsx
3787
+ var import_react11 = require("react");
3788
+ var FILTERS = [
3789
+ { id: "open", label: "\u5F85\u529E" },
3790
+ { id: "today", label: "\u4ECA\u5929" },
3791
+ { id: "week", label: "\u672C\u5468" },
3792
+ { id: "overdue", label: "\u903E\u671F" },
3793
+ { id: "done", label: "\u5DF2\u5B8C\u6210" },
3794
+ { id: "all", label: "\u5168\u90E8" }
3795
+ ];
3796
+ var STATUS_LABEL = {
3797
+ "NEEDS-ACTION": "\u5F85\u5904\u7406",
3798
+ "IN-PROCESS": "\u8FDB\u884C\u4E2D",
3799
+ COMPLETED: "\u5DF2\u5B8C\u6210",
3800
+ CANCELLED: "\u5DF2\u53D6\u6D88"
3801
+ };
3802
+ function errorText7(error) {
3803
+ return error instanceof ApiError || error instanceof Error ? error.message : String(error);
3804
+ }
3805
+ function dateKey(value) {
3806
+ const pad = (n) => String(n).padStart(2, "0");
3807
+ return `${String(value.getFullYear())}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
3808
+ }
3809
+ function weekEnd() {
3810
+ const now = /* @__PURE__ */ new Date();
3811
+ const end = new Date(now);
3812
+ end.setDate(now.getDate() + (7 - (now.getDay() + 6) % 7));
3813
+ return dateKey(end);
3814
+ }
3815
+ function dueText(due) {
3816
+ if (due === "") return "";
3817
+ const today = dateKey(/* @__PURE__ */ new Date());
3818
+ if (due.slice(0, 10) < today) return `\u903E\u671F ${due.slice(0, 10)}`;
3819
+ return due.slice(0, 10);
3820
+ }
3821
+ var EMPTY_TASK = {
3822
+ title: "",
3823
+ notes: "",
3824
+ due: "",
3825
+ priority: 0,
3826
+ status: "NEEDS-ACTION",
3827
+ owner: "",
3828
+ projectId: ""
3829
+ };
3830
+ function TasksIcon() {
3831
+ const [count, setCount] = (0, import_react11.useState)(0);
3832
+ (0, import_react11.useEffect)(() => {
3833
+ let cancelled = false;
3834
+ const load = async () => {
3835
+ try {
3836
+ const app = await api.state();
3837
+ const accountId = app.settings.activeAccountId;
3838
+ if (accountId === null) return;
3839
+ const result = await api.tasks(accountId);
3840
+ if (!cancelled) {
3841
+ setCount(
3842
+ result.items.filter((task) => task.status !== "COMPLETED" && task.status !== "CANCELLED").length
3843
+ );
3844
+ }
3845
+ } catch {
3846
+ }
3847
+ };
3848
+ void load();
3849
+ const timer = window.setInterval(() => {
3850
+ void load();
3851
+ }, 6e4);
3852
+ return () => {
3853
+ cancelled = true;
3854
+ window.clearInterval(timer);
3855
+ };
3856
+ }, []);
3857
+ return (0, import_react11.createElement)(
3858
+ "span",
3859
+ {
3860
+ style: {
3861
+ position: "relative",
3862
+ display: "inline-flex",
3863
+ alignItems: "center",
3864
+ justifyContent: "center",
3865
+ width: "18px",
3866
+ height: "18px"
3867
+ }
3868
+ },
3869
+ (0, import_react11.createElement)(Icon, { name: "checklist", size: 16 }),
3870
+ count > 0 ? (0, import_react11.createElement)(
3871
+ "span",
3872
+ {
3873
+ style: {
3874
+ position: "absolute",
3875
+ right: "-5px",
3876
+ bottom: "-3px",
3877
+ display: "flex",
3878
+ alignItems: "center",
3879
+ justifyContent: "center",
3880
+ boxSizing: "border-box",
3881
+ minWidth: "13px",
3882
+ height: "13px",
3883
+ padding: "0 3px",
3884
+ borderRadius: "999px",
3885
+ background: "#5686fe",
3886
+ color: "#fff",
3887
+ fontSize: "9px",
3888
+ fontWeight: 600,
3889
+ lineHeight: "13px",
3890
+ boxShadow: "0 0 0 2px var(--dsw-alias-bg-base, #151517)",
3891
+ pointerEvents: "none"
3892
+ }
3893
+ },
3894
+ count > 99 ? "99+" : String(count)
3895
+ ) : null
3896
+ );
3897
+ }
3898
+ function TasksPanel() {
3899
+ const [state, setState] = (0, import_react11.useState)(null);
3900
+ const [tasks, setTasks] = (0, import_react11.useState)([]);
3901
+ const [projects, setProjects] = (0, import_react11.useState)([]);
3902
+ const [calendar, setCalendar] = (0, import_react11.useState)(null);
3903
+ const [filter, setFilter] = (0, import_react11.useState)("open");
3904
+ const [error, setError] = (0, import_react11.useState)("");
3905
+ const [toast, setToast] = (0, import_react11.useState)("");
3906
+ const [busy, setBusy] = (0, import_react11.useState)(false);
3907
+ const [editing, setEditing] = (0, import_react11.useState)(null);
3908
+ const [creating, setCreating] = (0, import_react11.useState)(false);
3909
+ const [tick, setTick] = (0, import_react11.useState)(0);
3910
+ const accountId = state?.settings.activeAccountId ?? null;
3911
+ (0, import_react11.useEffect)(() => {
3912
+ api.state().then(setState).catch((caught) => setError(errorText7(caught)));
3913
+ }, [tick]);
3914
+ (0, import_react11.useEffect)(() => {
3915
+ if (accountId === null) return;
3916
+ let cancelled = false;
3917
+ Promise.all([api.tasks(accountId), api.projects()]).then(([list, projectList]) => {
3918
+ if (cancelled) return;
3919
+ setTasks(list.items);
3920
+ setCalendar(list.calendar);
3921
+ setProjects(projectList.items);
3922
+ }).catch((caught) => {
3923
+ if (!cancelled) setError(errorText7(caught));
3924
+ });
3925
+ return () => {
3926
+ cancelled = true;
3927
+ };
3928
+ }, [accountId, tick]);
3929
+ const refresh = (0, import_react11.useCallback)(() => {
3930
+ setTick((value) => value + 1);
3931
+ }, []);
3932
+ const notify = (message) => {
3933
+ setToast(message);
3934
+ window.setTimeout(() => {
3935
+ setToast("");
3936
+ }, 3e3);
3937
+ };
3938
+ const visible = (0, import_react11.useMemo)(() => {
3939
+ const today = dateKey(/* @__PURE__ */ new Date());
3940
+ const end = weekEnd();
3941
+ return tasks.filter((task) => {
3942
+ const done2 = task.status === "COMPLETED" || task.status === "CANCELLED";
3943
+ if (filter === "done") return done2;
3944
+ if (done2) return false;
3945
+ if (filter === "all") return true;
3946
+ if (filter === "overdue") return task.due !== "" && task.due.slice(0, 10) < today;
3947
+ if (filter === "today") return task.due.slice(0, 10) === today;
3948
+ if (filter === "week") return task.due !== "" && task.due.slice(0, 10) <= end;
3949
+ return true;
3950
+ });
3951
+ }, [tasks, filter]);
3952
+ const open = tasks.filter((task) => task.status !== "COMPLETED" && task.status !== "CANCELLED");
3953
+ const done = tasks.length - open.length;
3954
+ const progress = tasks.length === 0 ? 0 : Math.round(done / tasks.length * 100);
3955
+ const byProject = (0, import_react11.useMemo)(() => {
3956
+ const groups = /* @__PURE__ */ new Map();
3957
+ for (const task of tasks) {
3958
+ const key = task.projectId ?? "";
3959
+ const group = groups.get(key) ?? {
3960
+ project: projects.find((project) => project.id === key) ?? null,
3961
+ total: 0,
3962
+ done: 0
3963
+ };
3964
+ group.total += 1;
3965
+ if (task.status === "COMPLETED" || task.status === "CANCELLED") group.done += 1;
3966
+ groups.set(key, group);
3967
+ }
3968
+ return [...groups.values()].filter((group) => group.project !== null);
3969
+ }, [tasks, projects]);
3970
+ const switchAccount = async (id) => {
3971
+ try {
3972
+ await api.activateAccount(id);
3973
+ refresh();
3974
+ } catch (caught) {
3975
+ setError(errorText7(caught));
3976
+ }
3977
+ };
3978
+ const toggle = async (task) => {
3979
+ try {
3980
+ const next = task.status === "COMPLETED" ? "NEEDS-ACTION" : "COMPLETED";
3981
+ const { task: updated } = await api.updateTask(task.id, { status: next });
3982
+ setTasks((current) => current.map((item) => item.id === task.id ? updated : item));
3983
+ } catch (caught) {
3984
+ setError(errorText7(caught));
3985
+ }
3986
+ };
3987
+ const remove = async (task) => {
3988
+ try {
3989
+ await api.deleteTask(task.id);
3990
+ setTasks((current) => current.filter((item) => item.id !== task.id));
3991
+ } catch (caught) {
3992
+ setError(errorText7(caught));
3993
+ }
3994
+ };
3995
+ const openSource = async (task) => {
3996
+ if (task.messageKey === "") return;
3997
+ try {
3998
+ const found = await api.lookupMessage(task.accountId, task.messageKey);
3999
+ requestOpenMail({ accountId: task.accountId, id: found.id, mailbox: found.mailbox });
4000
+ document.querySelector('button[aria-label="MailHarbor"]')?.click();
4001
+ } catch (caught) {
4002
+ setError(errorText7(caught));
4003
+ }
4004
+ };
4005
+ const sync = async () => {
4006
+ if (accountId === null) return;
4007
+ setBusy(true);
4008
+ setError("");
4009
+ try {
4010
+ const result = await api.syncTasks(accountId);
4011
+ notify(`\u5DF2\u540C\u6B65\uFF1A\u62C9\u53D6 ${String(result.result.pulled)}\uFF0C\u63A8\u9001 ${String(result.result.pushed)}`);
4012
+ refresh();
4013
+ } catch (caught) {
4014
+ setError(errorText7(caught));
4015
+ } finally {
4016
+ setBusy(false);
4017
+ }
4018
+ };
4019
+ const save = async (draft) => {
4020
+ setBusy(true);
4021
+ setError("");
4022
+ try {
4023
+ if (draft.id === void 0) {
4024
+ await api.createTask({
4025
+ accountId,
4026
+ title: draft.title,
4027
+ notes: draft.notes,
4028
+ due: draft.due,
4029
+ priority: draft.priority,
4030
+ status: draft.status,
4031
+ owner: draft.owner,
4032
+ projectId: draft.projectId === "" ? null : draft.projectId
4033
+ });
4034
+ } else {
4035
+ await api.updateTask(draft.id, {
4036
+ title: draft.title,
4037
+ notes: draft.notes,
4038
+ due: draft.due,
4039
+ priority: draft.priority,
4040
+ status: draft.status,
4041
+ owner: draft.owner,
4042
+ projectId: draft.projectId === "" ? null : draft.projectId
4043
+ });
4044
+ }
4045
+ setEditing(null);
4046
+ setCreating(false);
4047
+ refresh();
4048
+ } catch (caught) {
4049
+ setError(errorText7(caught));
4050
+ } finally {
4051
+ setBusy(false);
4052
+ }
4053
+ };
4054
+ return (0, import_react11.createElement)(
4055
+ "div",
4056
+ { className: "mailharbor" },
4057
+ (0, import_react11.createElement)(
4058
+ "div",
4059
+ { className: "mailharbor-topbar" },
4060
+ (0, import_react11.createElement)(Icon, { name: "checklist", size: 16 }),
4061
+ (0, import_react11.createElement)("strong", { style: { fontSize: "13px" } }, "\u4EFB\u52A1"),
4062
+ state === null || state.accounts.length < 2 ? null : (0, import_react11.createElement)(
4063
+ "select",
4064
+ {
4065
+ value: accountId ?? "",
4066
+ onChange: (event) => void switchAccount(event.target.value),
4067
+ "aria-label": "\u8D26\u53F7"
4068
+ },
4069
+ state.accounts.map(
4070
+ (account) => (0, import_react11.createElement)("option", { key: account.id, value: account.id }, account.label)
4071
+ )
4072
+ ),
4073
+ (0, import_react11.createElement)("span", { className: "spacer" }),
4074
+ calendar === null ? null : (0, import_react11.createElement)(
4075
+ "span",
4076
+ { className: "small muted" },
4077
+ calendar.lastSync === "" ? "\u672A\u540C\u6B65" : `\u540C\u6B65\u4E8E ${new Date(calendar.lastSync).toLocaleString()}`
4078
+ ),
4079
+ (0, import_react11.createElement)(
4080
+ "button",
4081
+ { className: "ghost", onClick: () => void sync(), disabled: busy },
4082
+ busy ? "\u540C\u6B65\u4E2D\u2026" : "\u540C\u6B65 CalDAV"
4083
+ ),
4084
+ (0, import_react11.createElement)(
4085
+ "button",
4086
+ { className: "primary", onClick: () => setCreating(true) },
4087
+ (0, import_react11.createElement)(Icon, { name: "plus" }),
4088
+ "\u65B0\u5EFA\u4EFB\u52A1"
4089
+ )
4090
+ ),
4091
+ toast === "" ? null : (0, import_react11.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
4092
+ error === "" ? null : (0, import_react11.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
4093
+ (0, import_react11.createElement)(
4094
+ "div",
4095
+ { className: "tasks-body" },
4096
+ (0, import_react11.createElement)(
4097
+ "div",
4098
+ { className: "tasks-overview" },
4099
+ (0, import_react11.createElement)(
4100
+ "div",
4101
+ { className: "tasks-progress" },
4102
+ (0, import_react11.createElement)(
4103
+ "div",
4104
+ { className: "tasks-progress-head" },
4105
+ (0, import_react11.createElement)("strong", null, "\u603B\u4F53\u8FDB\u5EA6"),
4106
+ (0, import_react11.createElement)(
4107
+ "span",
4108
+ { className: "muted small" },
4109
+ `${String(done)} / ${String(tasks.length)} \xB7 ${String(progress)}%`
4110
+ )
4111
+ ),
4112
+ (0, import_react11.createElement)("div", { className: "tasks-bar" }, (0, import_react11.createElement)("span", { style: { width: `${String(progress)}%` } })),
4113
+ (0, import_react11.createElement)("div", { className: "small muted" }, `\u5F85\u529E ${String(open.length)} \u6761`)
4114
+ ),
4115
+ ...byProject.map(
4116
+ (group) => (0, import_react11.createElement)(
4117
+ "div",
4118
+ { key: group.project?.id ?? "none", className: "tasks-project" },
4119
+ (0, import_react11.createElement)(
4120
+ "div",
4121
+ { className: "tasks-project-head" },
4122
+ (0, import_react11.createElement)("span", { className: "name" }, group.project?.name ?? ""),
4123
+ (0, import_react11.createElement)("span", { className: "muted small" }, `${String(group.done)}/${String(group.total)}`)
4124
+ ),
4125
+ (0, import_react11.createElement)(
4126
+ "div",
4127
+ { className: "tasks-bar small" },
4128
+ (0, import_react11.createElement)("span", {
4129
+ style: {
4130
+ width: `${String(group.total === 0 ? 0 : Math.round(group.done / group.total * 100))}%`
4131
+ }
4132
+ })
4133
+ )
4134
+ )
4135
+ )
4136
+ ),
4137
+ (0, import_react11.createElement)(
4138
+ "div",
4139
+ { className: "tasks-list-wrap" },
4140
+ (0, import_react11.createElement)(
4141
+ "div",
4142
+ { className: "tasks-filters" },
4143
+ FILTERS.map(
4144
+ (item) => (0, import_react11.createElement)(
4145
+ "button",
4146
+ {
4147
+ key: item.id,
4148
+ className: filter === item.id ? "chip active" : "chip",
4149
+ onClick: () => setFilter(item.id)
4150
+ },
4151
+ item.label
4152
+ )
4153
+ )
4154
+ ),
4155
+ visible.length === 0 ? (0, import_react11.createElement)(
4156
+ "div",
4157
+ { className: "mailharbor-empty" },
4158
+ (0, import_react11.createElement)(Icon, { name: "checklist", size: 28 }),
4159
+ (0, import_react11.createElement)("div", { className: "title" }, "\u6CA1\u6709\u4EFB\u52A1"),
4160
+ (0, import_react11.createElement)("div", { className: "small" }, "\u53EF\u4EE5\u5728\u8FD9\u91CC\u65B0\u5EFA\uFF0C\u6216\u5728\u90AE\u4EF6\u91CC\u7528\u300C\u63D0\u53D6\u4EFB\u52A1\u300D\u3002")
4161
+ ) : (0, import_react11.createElement)(
4162
+ "div",
4163
+ { className: "tasks-list" },
4164
+ visible.map(
4165
+ (task) => (0, import_react11.createElement)(
4166
+ "div",
4167
+ { key: task.id, className: `tasks-row${task.status === "COMPLETED" ? " done" : ""}` },
4168
+ (0, import_react11.createElement)("input", {
4169
+ type: "checkbox",
4170
+ checked: task.status === "COMPLETED",
4171
+ "aria-label": "\u5B8C\u6210\u4EFB\u52A1",
4172
+ onChange: () => void toggle(task)
4173
+ }),
4174
+ (0, import_react11.createElement)(
4175
+ "button",
4176
+ { className: "tasks-title", onClick: () => setEditing(task) },
4177
+ (0, import_react11.createElement)("span", null, task.title),
4178
+ (0, import_react11.createElement)(
4179
+ "span",
4180
+ { className: "tasks-meta" },
4181
+ task.due === "" ? null : (0, import_react11.createElement)(
4182
+ "span",
4183
+ {
4184
+ className: task.due.slice(0, 10) < dateKey(/* @__PURE__ */ new Date()) ? "due overdue" : "due"
4185
+ },
4186
+ dueText(task.due)
4187
+ ),
4188
+ task.owner === "" ? null : (0, import_react11.createElement)("span", null, task.owner),
4189
+ (0, import_react11.createElement)("span", { className: "badge" }, STATUS_LABEL[task.status] ?? task.status),
4190
+ task.projectId === null ? null : (0, import_react11.createElement)(
4191
+ "span",
4192
+ { className: "badge" },
4193
+ projects.find((project) => project.id === task.projectId)?.name ?? "\u9879\u76EE"
4194
+ )
4195
+ )
4196
+ ),
4197
+ task.messageKey === "" ? null : (0, import_react11.createElement)(
4198
+ "button",
4199
+ {
4200
+ className: "subtle small",
4201
+ title: "\u6253\u5F00\u6765\u6E90\u90AE\u4EF6",
4202
+ onClick: () => void openSource(task)
4203
+ },
4204
+ "\u6765\u6E90"
4205
+ ),
4206
+ (0, import_react11.createElement)(
4207
+ "button",
4208
+ { className: "subtle icon", title: "\u5220\u9664", onClick: () => void remove(task) },
4209
+ (0, import_react11.createElement)(Icon, { name: "trash", size: 13 })
4210
+ )
4211
+ )
4212
+ )
4213
+ )
4214
+ )
4215
+ ),
4216
+ creating || editing !== null ? (0, import_react11.createElement)(TaskEditor, {
4217
+ task: editing,
4218
+ projects,
4219
+ busy,
4220
+ onClose: () => {
4221
+ setCreating(false);
4222
+ setEditing(null);
4223
+ },
4224
+ onSave: (draft) => void save(draft)
4225
+ }) : null
4226
+ );
4227
+ }
4228
+ function TaskEditor({
4229
+ task,
4230
+ projects,
4231
+ busy,
4232
+ onClose,
4233
+ onSave
4234
+ }) {
4235
+ const [draft, setDraft] = (0, import_react11.useState)({
4236
+ ...EMPTY_TASK,
4237
+ ...task === null ? {} : {
4238
+ id: task.id,
4239
+ title: task.title,
4240
+ notes: task.notes,
4241
+ due: task.due.slice(0, 10),
4242
+ priority: task.priority,
4243
+ status: task.status,
4244
+ owner: task.owner,
4245
+ projectId: task.projectId ?? ""
4246
+ }
4247
+ });
4248
+ const set = (patch) => setDraft((current) => ({ ...current, ...patch }));
4249
+ return (0, import_react11.createElement)(
4250
+ Modal,
4251
+ {
4252
+ title: task === null ? "\u65B0\u5EFA\u4EFB\u52A1" : "\u7F16\u8F91\u4EFB\u52A1",
4253
+ onClose,
4254
+ footer: (0, import_react11.createElement)(
4255
+ "div",
4256
+ { style: { display: "flex", gap: "8px", width: "100%", justifyContent: "flex-end" } },
4257
+ (0, import_react11.createElement)("button", { className: "ghost", onClick: onClose }, "\u53D6\u6D88"),
4258
+ (0, import_react11.createElement)(
4259
+ "button",
4260
+ { className: "primary", disabled: busy || draft.title.trim() === "", onClick: () => onSave(draft) },
4261
+ busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58"
4262
+ )
4263
+ )
4264
+ },
4265
+ (0, import_react11.createElement)(
4266
+ "div",
4267
+ { className: "mailharbor-field" },
4268
+ (0, import_react11.createElement)("label", null, "\u6807\u9898"),
4269
+ (0, import_react11.createElement)("input", {
4270
+ value: draft.title,
4271
+ autoFocus: true,
4272
+ onChange: (event) => set({ title: event.target.value })
4273
+ })
4274
+ ),
4275
+ (0, import_react11.createElement)(
4276
+ "div",
4277
+ { className: "mailharbor-field" },
4278
+ (0, import_react11.createElement)("label", null, "\u5907\u6CE8"),
4279
+ (0, import_react11.createElement)("textarea", {
4280
+ rows: 3,
4281
+ value: draft.notes,
4282
+ onChange: (event) => set({ notes: event.target.value })
4283
+ })
4284
+ ),
4285
+ (0, import_react11.createElement)(
4286
+ "div",
4287
+ { className: "mailharbor-grid" },
4288
+ (0, import_react11.createElement)(
4289
+ "div",
4290
+ { className: "mailharbor-field" },
4291
+ (0, import_react11.createElement)("label", null, "\u622A\u6B62\u65E5\u671F"),
4292
+ (0, import_react11.createElement)("input", {
4293
+ type: "date",
4294
+ value: draft.due,
4295
+ onChange: (event) => set({ due: event.target.value })
4296
+ })
4297
+ ),
4298
+ (0, import_react11.createElement)(
4299
+ "div",
4300
+ { className: "mailharbor-field" },
4301
+ (0, import_react11.createElement)("label", null, "\u4F18\u5148\u7EA7"),
4302
+ (0, import_react11.createElement)(
4303
+ "select",
4304
+ {
4305
+ value: String(draft.priority),
4306
+ onChange: (event) => set({ priority: Number(event.target.value) })
4307
+ },
4308
+ (0, import_react11.createElement)("option", { value: "0" }, "\u65E0"),
4309
+ (0, import_react11.createElement)("option", { value: "5" }, "\u4E2D"),
4310
+ (0, import_react11.createElement)("option", { value: "9" }, "\u9AD8")
4311
+ )
4312
+ )
4313
+ ),
4314
+ (0, import_react11.createElement)(
4315
+ "div",
4316
+ { className: "mailharbor-grid" },
4317
+ (0, import_react11.createElement)(
4318
+ "div",
4319
+ { className: "mailharbor-field" },
4320
+ (0, import_react11.createElement)("label", null, "\u72B6\u6001"),
4321
+ (0, import_react11.createElement)(
4322
+ "select",
4323
+ {
4324
+ value: draft.status,
4325
+ onChange: (event) => set({ status: event.target.value })
4326
+ },
4327
+ Object.entries(STATUS_LABEL).map(([value, label]) => (0, import_react11.createElement)("option", { key: value, value }, label))
4328
+ )
4329
+ ),
4330
+ (0, import_react11.createElement)(
4331
+ "div",
4332
+ { className: "mailharbor-field" },
4333
+ (0, import_react11.createElement)("label", null, "\u9879\u76EE"),
4334
+ (0, import_react11.createElement)(
4335
+ "select",
4336
+ {
4337
+ value: draft.projectId,
4338
+ onChange: (event) => set({ projectId: event.target.value })
4339
+ },
4340
+ (0, import_react11.createElement)("option", { value: "" }, "\u65E0\u9879\u76EE"),
4341
+ projects.map((project) => (0, import_react11.createElement)("option", { key: project.id, value: project.id }, project.name))
4342
+ )
4343
+ )
4344
+ ),
4345
+ (0, import_react11.createElement)(
4346
+ "div",
4347
+ { className: "mailharbor-field" },
4348
+ (0, import_react11.createElement)("label", null, "\u8D1F\u8D23\u4EBA"),
4349
+ (0, import_react11.createElement)("input", {
4350
+ value: draft.owner,
4351
+ onChange: (event) => set({ owner: event.target.value })
4352
+ })
4353
+ )
4354
+ );
4355
+ }
4356
+
3090
4357
  // src/client/index.tsx
3091
- var VERSION = "0.4.1";
4358
+ var VERSION = "0.5.0";
3092
4359
  var inject = ["slots"];
3093
4360
  var PANEL_ID = "mailharbor";
4361
+ var TASKS_PANEL_ID = "mailharbor-tasks";
3094
4362
  var STYLE_ID = "mailharbor-style";
3095
4363
  function ensureStyles() {
3096
4364
  if (document.getElementById(STYLE_ID) !== null) return;
@@ -3126,6 +4394,19 @@ function apply(ctx) {
3126
4394
  MailIcon
3127
4395
  )
3128
4396
  );
4397
+ ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: TASKS_PANEL_ID }, TasksPanel));
4398
+ ctx.slots.inject(
4399
+ "sidebar.panellist",
4400
+ () => ctx.slots.register(
4401
+ {
4402
+ name: "sidebar.panellist",
4403
+ id: TASKS_PANEL_ID,
4404
+ order: 21,
4405
+ label: "\u4EFB\u52A1"
4406
+ },
4407
+ TasksIcon
4408
+ )
4409
+ );
3129
4410
  }
3130
4411
 
3131
4412
  return module.exports;