@zaclinley/dsh-mailharbor 0.2.1 → 0.4.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/CHANGELOG.md +30 -0
- package/README.md +41 -21
- package/README.zh.md +34 -15
- package/lib/client.js +1552 -557
- package/lib/client.js.map +4 -4
- package/lib/index.js +833 -66
- package/lib/index.js.map +4 -4
- package/package.json +3 -2
- package/scripts/migrate-legacy.mjs +2 -1
package/lib/client.js
CHANGED
|
@@ -34,7 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
module.exports = __toCommonJS(index_exports);
|
|
35
35
|
|
|
36
36
|
// 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}\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/* ---------- 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/* ---------- 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";
|
|
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
38
|
|
|
39
39
|
// src/client/icon.tsx
|
|
40
40
|
var import_react = require("react");
|
|
@@ -123,6 +123,44 @@ var api = {
|
|
|
123
123
|
}),
|
|
124
124
|
logout: (id) => request(`accounts/${encodeURIComponent(id)}/logout`, { method: "POST" }),
|
|
125
125
|
check: (id) => request(`accounts/${encodeURIComponent(id)}/check`, { method: "POST" }),
|
|
126
|
+
aiStatus: (accountId) => request(`ai/status${query({ accountId: accountId ?? null })}`),
|
|
127
|
+
aiModels: () => request("ai/models"),
|
|
128
|
+
aiThread: (accountId, key) => request(`ai/thread${query({ accountId: accountId ?? null, key })}`),
|
|
129
|
+
aiAddCard: (payload) => request("ai/cards", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: JSON.stringify(payload)
|
|
132
|
+
}),
|
|
133
|
+
aiDeleteCard: (accountId, key, cardId) => request(`ai/cards${query({ accountId: accountId ?? null, key, cardId })}`, {
|
|
134
|
+
method: "DELETE"
|
|
135
|
+
}),
|
|
136
|
+
aiClearThread: (accountId, key) => request(`ai/thread${query({ accountId: accountId ?? null, key })}`, {
|
|
137
|
+
method: "DELETE"
|
|
138
|
+
}),
|
|
139
|
+
aiCompose: (payload) => request("ai/compose", {
|
|
140
|
+
method: "POST",
|
|
141
|
+
body: JSON.stringify(payload)
|
|
142
|
+
}),
|
|
143
|
+
aiSummary: (payload) => request("ai/summary", {
|
|
144
|
+
method: "POST",
|
|
145
|
+
body: JSON.stringify(payload)
|
|
146
|
+
}),
|
|
147
|
+
aiReply: (payload) => request("ai/reply", {
|
|
148
|
+
method: "POST",
|
|
149
|
+
body: JSON.stringify(payload)
|
|
150
|
+
}),
|
|
151
|
+
aiTranslate: (payload) => request("ai/translate", {
|
|
152
|
+
method: "POST",
|
|
153
|
+
body: JSON.stringify(payload)
|
|
154
|
+
}),
|
|
155
|
+
aiStyle: (accountId) => request(`ai/style${query({ accountId: accountId ?? null })}`),
|
|
156
|
+
aiSaveStyle: (accountId, profile) => request(`ai/style${query({ accountId: accountId ?? null })}`, {
|
|
157
|
+
method: "PUT",
|
|
158
|
+
body: JSON.stringify({ accountId, profile })
|
|
159
|
+
}),
|
|
160
|
+
aiLearnStyle: (accountId) => request(`ai/style/learn${query({ accountId: accountId ?? null })}`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
body: JSON.stringify({ accountId: accountId ?? null })
|
|
163
|
+
}),
|
|
126
164
|
settings: () => request("settings"),
|
|
127
165
|
saveSettings: (patch) => request("settings", { method: "PATCH", body: JSON.stringify(patch) }),
|
|
128
166
|
backupStatus: (accountId) => request(`backup/status${query({ accountId: accountId ?? null })}`),
|
|
@@ -217,17 +255,451 @@ function MailIcon() {
|
|
|
217
255
|
}
|
|
218
256
|
|
|
219
257
|
// src/client/panel.tsx
|
|
220
|
-
var
|
|
258
|
+
var import_react8 = require("react");
|
|
221
259
|
|
|
222
|
-
// src/client/
|
|
260
|
+
// src/client/ai-rail.tsx
|
|
223
261
|
var import_react3 = require("react");
|
|
224
262
|
|
|
225
|
-
// src/client/
|
|
263
|
+
// src/client/icons.tsx
|
|
226
264
|
var import_react2 = require("react");
|
|
265
|
+
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)(
|
|
270
|
+
"g",
|
|
271
|
+
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" })
|
|
274
|
+
),
|
|
275
|
+
paperclip: (0, import_react2.createElement)("path", {
|
|
276
|
+
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
|
+
}),
|
|
278
|
+
mail: (0, import_react2.createElement)(
|
|
279
|
+
"g",
|
|
280
|
+
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" })
|
|
283
|
+
),
|
|
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)(
|
|
290
|
+
"g",
|
|
291
|
+
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" })
|
|
295
|
+
),
|
|
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)(
|
|
300
|
+
"g",
|
|
301
|
+
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" })
|
|
304
|
+
),
|
|
305
|
+
trash: (0, import_react2.createElement)(
|
|
306
|
+
"g",
|
|
307
|
+
null,
|
|
308
|
+
(0, import_react2.createElement)("path", {
|
|
309
|
+
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
|
+
}),
|
|
311
|
+
(0, import_react2.createElement)("path", { d: "M10 11v6M14 11v6" })
|
|
312
|
+
),
|
|
313
|
+
send: (0, import_react2.createElement)("path", { d: "m4 4 17 8-17 8 3.6-8z M7.6 12H21" })
|
|
314
|
+
};
|
|
315
|
+
function Icon({ name, size = 16 }) {
|
|
316
|
+
return (0, import_react2.createElement)(
|
|
317
|
+
"svg",
|
|
318
|
+
{
|
|
319
|
+
width: size,
|
|
320
|
+
height: size,
|
|
321
|
+
viewBox: "0 0 24 24",
|
|
322
|
+
fill: "none",
|
|
323
|
+
stroke: "currentColor",
|
|
324
|
+
strokeWidth: 1.7,
|
|
325
|
+
strokeLinecap: "round",
|
|
326
|
+
strokeLinejoin: "round",
|
|
327
|
+
"aria-hidden": "true",
|
|
328
|
+
focusable: "false"
|
|
329
|
+
},
|
|
330
|
+
PATHS[name] ?? null
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/client/ai-rail.tsx
|
|
335
|
+
function errorText(error) {
|
|
336
|
+
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
337
|
+
}
|
|
338
|
+
function textToHtml(text) {
|
|
339
|
+
return text.split(/\n{2,}/).map(
|
|
340
|
+
(paragraph) => `<p>${paragraph.trim().replaceAll("&", "&").replaceAll("<", "<").replaceAll("\n", "<br>")}</p>`
|
|
341
|
+
).join("");
|
|
342
|
+
}
|
|
343
|
+
function summaryBlocks(text) {
|
|
344
|
+
const blocks = [];
|
|
345
|
+
let current = { title: "", lines: [] };
|
|
346
|
+
const push = () => {
|
|
347
|
+
const body = current.lines.join("\n").trim();
|
|
348
|
+
if (current.title !== "" || body !== "") blocks.push({ title: current.title, body });
|
|
349
|
+
current = { title: "", lines: [] };
|
|
350
|
+
};
|
|
351
|
+
for (const line of text.split("\n")) {
|
|
352
|
+
const heading = /^#{1,4}\s+(.*)$/.exec(line) ?? /^(结论|需要你做什么|关键信息|建议回复要点)\s*$/.exec(line);
|
|
353
|
+
if (heading !== null) {
|
|
354
|
+
push();
|
|
355
|
+
current.title = heading[1] ?? "";
|
|
356
|
+
} else {
|
|
357
|
+
current.lines.push(line);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
push();
|
|
361
|
+
return blocks;
|
|
362
|
+
}
|
|
363
|
+
function suggestionsOf(summary) {
|
|
364
|
+
const match = /建议回复要点[::]?\s*\n([\s\S]*)$/.exec(summary);
|
|
365
|
+
if (match === null) return "\u6839\u636E\u8FD9\u5C01\u90AE\u4EF6\u5199\u4E00\u5C01\u5F97\u4F53\u7684\u56DE\u590D\u3002";
|
|
366
|
+
return `\u6839\u636E\u8FD9\u4E9B\u5EFA\u8BAE\u5199\u56DE\u590D\uFF1A
|
|
367
|
+
${(match[1] ?? "").trim()}`;
|
|
368
|
+
}
|
|
369
|
+
var KIND_LABEL = {
|
|
370
|
+
summary: "AI \u6458\u8981",
|
|
371
|
+
reply: "AI \u56DE\u590D",
|
|
372
|
+
translate: "AI \u7FFB\u8BD1",
|
|
373
|
+
answer: "AI \u56DE\u7B54"
|
|
374
|
+
};
|
|
375
|
+
function threadKey(detail, source) {
|
|
376
|
+
return detail.messageId !== void 0 && detail.messageId !== "" ? `mid:${detail.messageId}` : `${source}:${detail.id}`;
|
|
377
|
+
}
|
|
378
|
+
function AiRail({
|
|
379
|
+
account,
|
|
380
|
+
detail,
|
|
381
|
+
source,
|
|
382
|
+
mailbox,
|
|
383
|
+
request: request2,
|
|
384
|
+
onClose,
|
|
385
|
+
onInsert,
|
|
386
|
+
onSaveDraft,
|
|
387
|
+
onNotice
|
|
388
|
+
}) {
|
|
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);
|
|
398
|
+
const key = detail === null ? "" : threadKey(detail, source);
|
|
399
|
+
(0, import_react3.useEffect)(() => {
|
|
400
|
+
if (account === null) return;
|
|
401
|
+
let cancelled = false;
|
|
402
|
+
api.aiStatus(account.id).then((next) => {
|
|
403
|
+
if (!cancelled) setStatus(next);
|
|
404
|
+
}).catch(() => {
|
|
405
|
+
if (!cancelled)
|
|
406
|
+
setStatus({ available: false, enabled: false, provider: "", model: "", error: "\u65E0\u6CD5\u8BFB\u53D6 AI \u72B6\u6001" });
|
|
407
|
+
});
|
|
408
|
+
return () => {
|
|
409
|
+
cancelled = true;
|
|
410
|
+
};
|
|
411
|
+
}, [account]);
|
|
412
|
+
(0, import_react3.useEffect)(() => {
|
|
413
|
+
if (account === null || key === "") {
|
|
414
|
+
setThread(null);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
let cancelled = false;
|
|
418
|
+
setLoading(true);
|
|
419
|
+
api.aiThread(account.id, key).then((result) => {
|
|
420
|
+
if (!cancelled) setThread(result.thread);
|
|
421
|
+
}).catch(() => {
|
|
422
|
+
if (!cancelled) setThread(null);
|
|
423
|
+
}).finally(() => {
|
|
424
|
+
if (!cancelled) setLoading(false);
|
|
425
|
+
});
|
|
426
|
+
return () => {
|
|
427
|
+
cancelled = true;
|
|
428
|
+
};
|
|
429
|
+
}, [account, key]);
|
|
430
|
+
const generate = (0, import_react3.useCallback)(
|
|
431
|
+
async (kind, extra = {}) => {
|
|
432
|
+
if (detail === null || account === null) return;
|
|
433
|
+
setGenerating(kind);
|
|
434
|
+
setError("");
|
|
435
|
+
try {
|
|
436
|
+
const result = await api.aiAddCard({
|
|
437
|
+
accountId: detail.accountId,
|
|
438
|
+
key,
|
|
439
|
+
kind,
|
|
440
|
+
source,
|
|
441
|
+
id: detail.id,
|
|
442
|
+
mailbox,
|
|
443
|
+
...extra
|
|
444
|
+
});
|
|
445
|
+
setThread(result.thread);
|
|
446
|
+
} catch (caught) {
|
|
447
|
+
setError(errorText(caught));
|
|
448
|
+
} finally {
|
|
449
|
+
setGenerating(null);
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
[detail, account, key, source, mailbox]
|
|
453
|
+
);
|
|
454
|
+
(0, import_react3.useEffect)(() => {
|
|
455
|
+
if (request2 === null || request2.nonce === lastNonce.current) return;
|
|
456
|
+
lastNonce.current = request2.nonce;
|
|
457
|
+
if (detail !== null) void generate(request2.kind);
|
|
458
|
+
}, [request2]);
|
|
459
|
+
(0, import_react3.useEffect)(() => {
|
|
460
|
+
const element = list.current;
|
|
461
|
+
if (element !== null) element.scrollTop = element.scrollHeight;
|
|
462
|
+
}, [thread, generating]);
|
|
463
|
+
const replyInit = (card) => {
|
|
464
|
+
const subject = card.meta?.subject ?? (detail === null ? "" : /^re:/i.test(detail.subject) ? detail.subject : `Re: ${detail.subject}`);
|
|
465
|
+
return {
|
|
466
|
+
to: card.meta?.to ?? detail?.from.address ?? "",
|
|
467
|
+
cc: "",
|
|
468
|
+
subject,
|
|
469
|
+
html: card.html ?? textToHtml(card.output),
|
|
470
|
+
...card.meta?.inReplyTo !== void 0 && card.meta.inReplyTo !== "" ? { inReplyTo: card.meta.inReplyTo } : {},
|
|
471
|
+
...card.meta?.references !== void 0 && card.meta.references !== "" ? { references: card.meta.references } : {}
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
const copy = (card) => {
|
|
475
|
+
void navigator.clipboard?.writeText(card.output).then(() => onNotice("\u5DF2\u590D\u5236"));
|
|
476
|
+
};
|
|
477
|
+
const remove = async (card) => {
|
|
478
|
+
if (account === null) return;
|
|
479
|
+
try {
|
|
480
|
+
const result = await api.aiDeleteCard(account.id, key, card.id);
|
|
481
|
+
setThread(result.thread);
|
|
482
|
+
} catch (caught) {
|
|
483
|
+
setError(errorText(caught));
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
const clear = async () => {
|
|
487
|
+
if (account === null || key === "") return;
|
|
488
|
+
try {
|
|
489
|
+
await api.aiClearThread(account.id, key);
|
|
490
|
+
setThread(null);
|
|
491
|
+
setConfirmClear(false);
|
|
492
|
+
onNotice("\u5DF2\u6E05\u9664\u8FD9\u5C01\u90AE\u4EF6\u7684 AI \u8BB0\u5F55");
|
|
493
|
+
} catch (caught) {
|
|
494
|
+
setError(errorText(caught));
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
const ask = async () => {
|
|
498
|
+
const text = question.trim();
|
|
499
|
+
if (text === "" || generating !== null) return;
|
|
500
|
+
setQuestion("");
|
|
501
|
+
await generate("answer", { instruction: text });
|
|
502
|
+
};
|
|
503
|
+
const disabled = status !== null && (!status.available || !status.enabled);
|
|
504
|
+
const cards = thread?.cards ?? [];
|
|
505
|
+
return (0, import_react3.createElement)(
|
|
506
|
+
"aside",
|
|
507
|
+
{ className: "mailharbor-ai", "aria-label": "AI \u52A9\u624B" },
|
|
508
|
+
(0, import_react3.createElement)(
|
|
509
|
+
"div",
|
|
510
|
+
{ 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)(
|
|
516
|
+
"span",
|
|
517
|
+
{ 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)(
|
|
521
|
+
"button",
|
|
522
|
+
{
|
|
523
|
+
className: "subtle icon",
|
|
524
|
+
title: cards.length === 0 ? "\u5173\u95ED" : "\u6E05\u9664\u8FD9\u5C01\u90AE\u4EF6\u7684\u8BB0\u5F55",
|
|
525
|
+
onClick: () => cards.length === 0 ? onClose() : setConfirmClear(true)
|
|
526
|
+
},
|
|
527
|
+
(0, import_react3.createElement)(Icon, { name: cards.length === 0 ? "close" : "trash", size: 14 })
|
|
528
|
+
),
|
|
529
|
+
(0, import_react3.createElement)(
|
|
530
|
+
"button",
|
|
531
|
+
{ className: "subtle icon", title: "\u6536\u8D77", onClick: onClose },
|
|
532
|
+
(0, import_react3.createElement)(Icon, { name: "chevronRight", size: 14 })
|
|
533
|
+
)
|
|
534
|
+
),
|
|
535
|
+
detail === null ? (0, import_react3.createElement)(
|
|
536
|
+
"div",
|
|
537
|
+
{ 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)(
|
|
542
|
+
"div",
|
|
543
|
+
{ 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")
|
|
547
|
+
),
|
|
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)(
|
|
550
|
+
"div",
|
|
551
|
+
{ className: "mailharbor-ai-quick" },
|
|
552
|
+
(0, import_react3.createElement)(
|
|
553
|
+
"button",
|
|
554
|
+
{
|
|
555
|
+
className: "ghost",
|
|
556
|
+
disabled: disabled || generating !== null,
|
|
557
|
+
onClick: () => void generate("summary")
|
|
558
|
+
},
|
|
559
|
+
generating === "summary" ? "\u603B\u7ED3\u4E2D\u2026" : "\u6458\u8981"
|
|
560
|
+
),
|
|
561
|
+
(0, import_react3.createElement)(
|
|
562
|
+
"button",
|
|
563
|
+
{
|
|
564
|
+
className: "ghost",
|
|
565
|
+
disabled: disabled || generating !== null,
|
|
566
|
+
onClick: () => void generate("reply")
|
|
567
|
+
},
|
|
568
|
+
generating === "reply" ? "\u8D77\u8349\u4E2D\u2026" : "\u56DE\u590D"
|
|
569
|
+
),
|
|
570
|
+
(0, import_react3.createElement)(
|
|
571
|
+
"button",
|
|
572
|
+
{
|
|
573
|
+
className: "ghost",
|
|
574
|
+
disabled: disabled || generating !== null,
|
|
575
|
+
onClick: () => void generate("translate", { target: "en" })
|
|
576
|
+
},
|
|
577
|
+
generating === "translate" ? "\u7FFB\u8BD1\u4E2D\u2026" : "\u7FFB\u8BD1"
|
|
578
|
+
)
|
|
579
|
+
),
|
|
580
|
+
(0, import_react3.createElement)(
|
|
581
|
+
"div",
|
|
582
|
+
{ 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)(
|
|
585
|
+
"div",
|
|
586
|
+
{ 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)(
|
|
589
|
+
"div",
|
|
590
|
+
{ className: "small" },
|
|
591
|
+
"\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
|
+
)
|
|
593
|
+
) : null,
|
|
594
|
+
cards.map(
|
|
595
|
+
(card) => (0, import_react3.createElement)(
|
|
596
|
+
"article",
|
|
597
|
+
{ className: "mailharbor-ai-card", key: card.id },
|
|
598
|
+
(0, import_react3.createElement)(
|
|
599
|
+
"div",
|
|
600
|
+
{ 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)(
|
|
605
|
+
"button",
|
|
606
|
+
{
|
|
607
|
+
className: "subtle icon",
|
|
608
|
+
title: "\u5220\u9664",
|
|
609
|
+
onClick: () => void remove(card)
|
|
610
|
+
},
|
|
611
|
+
(0, import_react3.createElement)(Icon, { name: "close", size: 12 })
|
|
612
|
+
)
|
|
613
|
+
),
|
|
614
|
+
card.kind === "summary" ? (0, import_react3.createElement)(
|
|
615
|
+
"div",
|
|
616
|
+
{ className: "mailharbor-ai-summary" },
|
|
617
|
+
summaryBlocks(card.output).map(
|
|
618
|
+
(block, index) => (0, import_react3.createElement)(
|
|
619
|
+
"section",
|
|
620
|
+
{ 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)
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
) : (0, import_react3.createElement)("div", { className: "mailharbor-ai-text" }, card.output),
|
|
626
|
+
(0, import_react3.createElement)(
|
|
627
|
+
"div",
|
|
628
|
+
{ className: "mailharbor-ai-card-actions" },
|
|
629
|
+
card.kind === "summary" ? (0, import_react3.createElement)(
|
|
630
|
+
"button",
|
|
631
|
+
{
|
|
632
|
+
className: "ghost",
|
|
633
|
+
disabled: generating !== null,
|
|
634
|
+
onClick: () => void generate("reply", { instruction: suggestionsOf(card.output) })
|
|
635
|
+
},
|
|
636
|
+
"\u7528\u8FD9\u4E9B\u8981\u70B9\u56DE\u590D"
|
|
637
|
+
) : null,
|
|
638
|
+
card.kind === "reply" || card.kind === "translate" ? (0, import_react3.createElement)(
|
|
639
|
+
"button",
|
|
640
|
+
{ className: "ghost", onClick: () => onInsert(replyInit(card)) },
|
|
641
|
+
card.kind === "translate" ? "\u63D2\u5165\u56DE\u590D" : "\u63D2\u5165\u6B63\u6587"
|
|
642
|
+
) : 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)(
|
|
645
|
+
"button",
|
|
646
|
+
{
|
|
647
|
+
className: "ghost",
|
|
648
|
+
onClick: () => setQuestion(card.output.slice(0, 80))
|
|
649
|
+
},
|
|
650
|
+
"\u7EE7\u7EED\u8FFD\u95EE"
|
|
651
|
+
) : null,
|
|
652
|
+
(0, import_react3.createElement)("span", { className: "spacer" }),
|
|
653
|
+
(0, import_react3.createElement)("button", { className: "subtle", onClick: () => copy(card) }, "\u590D\u5236")
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
),
|
|
657
|
+
generating !== null ? (0, import_react3.createElement)(
|
|
658
|
+
"article",
|
|
659
|
+
{ 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")
|
|
662
|
+
) : null,
|
|
663
|
+
error === "" ? null : (0, import_react3.createElement)("div", { className: "mailharbor-ai-error" }, error)
|
|
664
|
+
),
|
|
665
|
+
detail === null ? null : (0, import_react3.createElement)(
|
|
666
|
+
"form",
|
|
667
|
+
{
|
|
668
|
+
className: "mailharbor-ai-ask",
|
|
669
|
+
onSubmit: (event) => {
|
|
670
|
+
event.preventDefault();
|
|
671
|
+
void ask();
|
|
672
|
+
}
|
|
673
|
+
},
|
|
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 })
|
|
689
|
+
)
|
|
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");
|
|
227
699
|
var import_react_dom = require("react-dom");
|
|
228
700
|
function Avatar({ email, size = 26 }) {
|
|
229
701
|
const letter = (email.trim()[0] ?? "?").toUpperCase();
|
|
230
|
-
return (0,
|
|
702
|
+
return (0, import_react4.createElement)(
|
|
231
703
|
"span",
|
|
232
704
|
{
|
|
233
705
|
className: "avatar",
|
|
@@ -246,12 +718,12 @@ function Field({
|
|
|
246
718
|
children,
|
|
247
719
|
hint
|
|
248
720
|
}) {
|
|
249
|
-
return (0,
|
|
721
|
+
return (0, import_react4.createElement)(
|
|
250
722
|
"div",
|
|
251
723
|
{ className: "mailharbor-field" },
|
|
252
|
-
(0,
|
|
724
|
+
(0, import_react4.createElement)("label", null, label),
|
|
253
725
|
children,
|
|
254
|
-
hint === void 0 ? null : (0,
|
|
726
|
+
hint === void 0 ? null : (0, import_react4.createElement)("span", { className: "small muted" }, hint)
|
|
255
727
|
);
|
|
256
728
|
}
|
|
257
729
|
function Toggle({
|
|
@@ -259,10 +731,10 @@ function Toggle({
|
|
|
259
731
|
onChange,
|
|
260
732
|
label
|
|
261
733
|
}) {
|
|
262
|
-
return (0,
|
|
734
|
+
return (0, import_react4.createElement)(
|
|
263
735
|
"label",
|
|
264
736
|
{ className: "mailharbor-toggle" },
|
|
265
|
-
(0,
|
|
737
|
+
(0, import_react4.createElement)("input", {
|
|
266
738
|
type: "checkbox",
|
|
267
739
|
checked,
|
|
268
740
|
onChange: (event) => {
|
|
@@ -288,7 +760,7 @@ function Modal({
|
|
|
288
760
|
onClose,
|
|
289
761
|
wide = false
|
|
290
762
|
}) {
|
|
291
|
-
(0,
|
|
763
|
+
(0, import_react4.useEffect)(() => {
|
|
292
764
|
const onKey = (event) => {
|
|
293
765
|
if (event.key === "Escape") onClose();
|
|
294
766
|
};
|
|
@@ -298,7 +770,7 @@ function Modal({
|
|
|
298
770
|
};
|
|
299
771
|
}, [onClose]);
|
|
300
772
|
return (0, import_react_dom.createPortal)(
|
|
301
|
-
(0,
|
|
773
|
+
(0, import_react4.createElement)(
|
|
302
774
|
"div",
|
|
303
775
|
{
|
|
304
776
|
className: "mailharbor-modal-mask",
|
|
@@ -306,7 +778,7 @@ function Modal({
|
|
|
306
778
|
if (event.target === event.currentTarget) onClose();
|
|
307
779
|
}
|
|
308
780
|
},
|
|
309
|
-
(0,
|
|
781
|
+
(0, import_react4.createElement)(
|
|
310
782
|
"div",
|
|
311
783
|
{
|
|
312
784
|
className: "mailharbor-modal",
|
|
@@ -315,27 +787,27 @@ function Modal({
|
|
|
315
787
|
"aria-modal": "true",
|
|
316
788
|
"aria-label": title
|
|
317
789
|
},
|
|
318
|
-
(0,
|
|
790
|
+
(0, import_react4.createElement)(
|
|
319
791
|
"header",
|
|
320
792
|
null,
|
|
321
793
|
title,
|
|
322
|
-
(0,
|
|
323
|
-
(0,
|
|
794
|
+
(0, import_react4.createElement)("span", { className: "spacer" }),
|
|
795
|
+
(0, import_react4.createElement)("button", { className: "subtle", onClick: onClose, "aria-label": "\u5173\u95ED" }, "\u2715")
|
|
324
796
|
),
|
|
325
|
-
(0,
|
|
326
|
-
footer === void 0 ? null : (0,
|
|
797
|
+
(0, import_react4.createElement)("div", { className: "content" }, children),
|
|
798
|
+
footer === void 0 ? null : (0, import_react4.createElement)("footer", null, footer)
|
|
327
799
|
)
|
|
328
800
|
),
|
|
329
801
|
portalRoot()
|
|
330
802
|
);
|
|
331
803
|
}
|
|
332
804
|
function ErrorBar({ message, onDismiss }) {
|
|
333
|
-
return (0,
|
|
805
|
+
return (0, import_react4.createElement)(
|
|
334
806
|
"div",
|
|
335
807
|
{ className: "error", role: "alert" },
|
|
336
|
-
(0,
|
|
337
|
-
(0,
|
|
338
|
-
(0,
|
|
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")
|
|
339
811
|
);
|
|
340
812
|
}
|
|
341
813
|
function shortDate(iso) {
|
|
@@ -370,27 +842,27 @@ function formatBytes(size) {
|
|
|
370
842
|
function domainOf(email) {
|
|
371
843
|
return email.split("@")[1] ?? "example.com";
|
|
372
844
|
}
|
|
373
|
-
function
|
|
845
|
+
function errorText2(error) {
|
|
374
846
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
375
847
|
}
|
|
376
848
|
function AddAccountForm({
|
|
377
849
|
onDone,
|
|
378
850
|
onCancel
|
|
379
851
|
}) {
|
|
380
|
-
const [email, setEmail] = (0,
|
|
381
|
-
const [password, setPassword] = (0,
|
|
382
|
-
const [label, setLabel] = (0,
|
|
383
|
-
const [remember, setRemember] = (0,
|
|
384
|
-
const [advanced, setAdvanced] = (0,
|
|
385
|
-
const [imapHost, setImapHost] = (0,
|
|
386
|
-
const [imapPort, setImapPort] = (0,
|
|
387
|
-
const [imapSecure, setImapSecure] = (0,
|
|
388
|
-
const [smtpHost, setSmtpHost] = (0,
|
|
389
|
-
const [smtpPort, setSmtpPort] = (0,
|
|
390
|
-
const [smtpSecure, setSmtpSecure] = (0,
|
|
391
|
-
const [fromName, setFromName] = (0,
|
|
392
|
-
const [busy, setBusy] = (0,
|
|
393
|
-
const [error, setError] = (0,
|
|
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)("");
|
|
394
866
|
const submit = async () => {
|
|
395
867
|
setBusy(true);
|
|
396
868
|
setError("");
|
|
@@ -417,22 +889,22 @@ function AddAccountForm({
|
|
|
417
889
|
});
|
|
418
890
|
onDone(state);
|
|
419
891
|
} catch (caught) {
|
|
420
|
-
setError(
|
|
892
|
+
setError(errorText2(caught));
|
|
421
893
|
} finally {
|
|
422
894
|
setBusy(false);
|
|
423
895
|
}
|
|
424
896
|
};
|
|
425
|
-
return (0,
|
|
897
|
+
return (0, import_react5.createElement)(
|
|
426
898
|
"div",
|
|
427
899
|
{ className: "mailharbor-card" },
|
|
428
|
-
(0,
|
|
429
|
-
(0,
|
|
900
|
+
(0, import_react5.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
|
|
901
|
+
(0, import_react5.createElement)(
|
|
430
902
|
"div",
|
|
431
903
|
{ className: "mailharbor-grid" },
|
|
432
|
-
(0,
|
|
904
|
+
(0, import_react5.createElement)(
|
|
433
905
|
Field,
|
|
434
906
|
{ label: "\u90AE\u7BB1\u5730\u5740" },
|
|
435
|
-
(0,
|
|
907
|
+
(0, import_react5.createElement)("input", {
|
|
436
908
|
type: "email",
|
|
437
909
|
value: email,
|
|
438
910
|
placeholder: "you@example.com",
|
|
@@ -440,95 +912,95 @@ function AddAccountForm({
|
|
|
440
912
|
autoFocus: true
|
|
441
913
|
})
|
|
442
914
|
),
|
|
443
|
-
(0,
|
|
915
|
+
(0, import_react5.createElement)(
|
|
444
916
|
Field,
|
|
445
917
|
{ label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
|
|
446
|
-
(0,
|
|
918
|
+
(0, import_react5.createElement)("input", {
|
|
447
919
|
type: "password",
|
|
448
920
|
value: password,
|
|
449
921
|
onChange: (event) => setPassword(event.target.value)
|
|
450
922
|
})
|
|
451
923
|
)
|
|
452
924
|
),
|
|
453
|
-
(0,
|
|
925
|
+
(0, import_react5.createElement)(
|
|
454
926
|
Field,
|
|
455
927
|
{ label: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
|
|
456
|
-
(0,
|
|
928
|
+
(0, import_react5.createElement)("input", {
|
|
457
929
|
value: label,
|
|
458
930
|
placeholder: "\u5DE5\u4F5C\u90AE\u7BB1",
|
|
459
931
|
onChange: (event) => setLabel(event.target.value)
|
|
460
932
|
})
|
|
461
933
|
),
|
|
462
|
-
(0,
|
|
463
|
-
(0,
|
|
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)(
|
|
464
936
|
"button",
|
|
465
937
|
{ className: "ghost", onClick: () => setAdvanced(!advanced) },
|
|
466
938
|
advanced ? "\u9690\u85CF\u9AD8\u7EA7\u8BBE\u7F6E" : "\u9AD8\u7EA7\u8BBE\u7F6E\uFF08\u670D\u52A1\u5668\u4E0E\u7AEF\u53E3\uFF09"
|
|
467
939
|
),
|
|
468
|
-
advanced ? (0,
|
|
940
|
+
advanced ? (0, import_react5.createElement)(
|
|
469
941
|
"div",
|
|
470
942
|
{ style: { display: "flex", flexDirection: "column", gap: "10px" } },
|
|
471
|
-
(0,
|
|
943
|
+
(0, import_react5.createElement)(
|
|
472
944
|
"div",
|
|
473
945
|
{ className: "mailharbor-grid three" },
|
|
474
|
-
(0,
|
|
946
|
+
(0, import_react5.createElement)(
|
|
475
947
|
Field,
|
|
476
948
|
{ label: "IMAP \u4E3B\u673A" },
|
|
477
|
-
(0,
|
|
949
|
+
(0, import_react5.createElement)("input", {
|
|
478
950
|
value: imapHost,
|
|
479
951
|
placeholder: `imap.${domainOf(email)}`,
|
|
480
952
|
onChange: (event) => setImapHost(event.target.value)
|
|
481
953
|
})
|
|
482
954
|
),
|
|
483
|
-
(0,
|
|
955
|
+
(0, import_react5.createElement)(
|
|
484
956
|
Field,
|
|
485
957
|
{ label: "\u7AEF\u53E3" },
|
|
486
|
-
(0,
|
|
958
|
+
(0, import_react5.createElement)("input", {
|
|
487
959
|
value: imapPort,
|
|
488
960
|
inputMode: "numeric",
|
|
489
961
|
onChange: (event) => setImapPort(event.target.value)
|
|
490
962
|
})
|
|
491
963
|
),
|
|
492
|
-
(0,
|
|
964
|
+
(0, import_react5.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
|
|
493
965
|
),
|
|
494
|
-
(0,
|
|
966
|
+
(0, import_react5.createElement)(
|
|
495
967
|
"div",
|
|
496
968
|
{ className: "mailharbor-grid three" },
|
|
497
|
-
(0,
|
|
969
|
+
(0, import_react5.createElement)(
|
|
498
970
|
Field,
|
|
499
971
|
{ label: "SMTP \u4E3B\u673A" },
|
|
500
|
-
(0,
|
|
972
|
+
(0, import_react5.createElement)("input", {
|
|
501
973
|
value: smtpHost,
|
|
502
974
|
placeholder: `smtp.${domainOf(email)}`,
|
|
503
975
|
onChange: (event) => setSmtpHost(event.target.value)
|
|
504
976
|
})
|
|
505
977
|
),
|
|
506
|
-
(0,
|
|
978
|
+
(0, import_react5.createElement)(
|
|
507
979
|
Field,
|
|
508
980
|
{ label: "\u7AEF\u53E3" },
|
|
509
|
-
(0,
|
|
981
|
+
(0, import_react5.createElement)("input", {
|
|
510
982
|
value: smtpPort,
|
|
511
983
|
inputMode: "numeric",
|
|
512
984
|
onChange: (event) => setSmtpPort(event.target.value)
|
|
513
985
|
})
|
|
514
986
|
),
|
|
515
|
-
(0,
|
|
987
|
+
(0, import_react5.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
|
|
516
988
|
),
|
|
517
|
-
(0,
|
|
989
|
+
(0, import_react5.createElement)(
|
|
518
990
|
Field,
|
|
519
991
|
{ label: "\u53D1\u4EF6\u4EBA\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
|
|
520
|
-
(0,
|
|
992
|
+
(0, import_react5.createElement)("input", {
|
|
521
993
|
value: fromName,
|
|
522
994
|
onChange: (event) => setFromName(event.target.value)
|
|
523
995
|
})
|
|
524
996
|
)
|
|
525
997
|
) : null,
|
|
526
|
-
error === "" ? null : (0,
|
|
527
|
-
(0,
|
|
998
|
+
error === "" ? null : (0, import_react5.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
|
|
999
|
+
(0, import_react5.createElement)(
|
|
528
1000
|
"div",
|
|
529
1001
|
{ style: { display: "flex", gap: "8px", justifyContent: "flex-end" } },
|
|
530
|
-
onCancel === void 0 ? null : (0,
|
|
531
|
-
(0,
|
|
1002
|
+
onCancel === void 0 ? null : (0, import_react5.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
|
|
1003
|
+
(0, import_react5.createElement)(
|
|
532
1004
|
"button",
|
|
533
1005
|
{
|
|
534
1006
|
className: "primary",
|
|
@@ -541,10 +1013,10 @@ function AddAccountForm({
|
|
|
541
1013
|
);
|
|
542
1014
|
}
|
|
543
1015
|
function LoginForm({ account, onDone }) {
|
|
544
|
-
const [password, setPassword] = (0,
|
|
545
|
-
const [remember, setRemember] = (0,
|
|
546
|
-
const [busy, setBusy] = (0,
|
|
547
|
-
const [error, setError] = (0,
|
|
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)("");
|
|
548
1020
|
const submit = async () => {
|
|
549
1021
|
setBusy(true);
|
|
550
1022
|
setError("");
|
|
@@ -552,24 +1024,24 @@ function LoginForm({ account, onDone }) {
|
|
|
552
1024
|
await api.login(account.id, password, remember);
|
|
553
1025
|
onDone();
|
|
554
1026
|
} catch (caught) {
|
|
555
|
-
setError(
|
|
1027
|
+
setError(errorText2(caught));
|
|
556
1028
|
} finally {
|
|
557
1029
|
setBusy(false);
|
|
558
1030
|
}
|
|
559
1031
|
};
|
|
560
|
-
return (0,
|
|
1032
|
+
return (0, import_react5.createElement)(
|
|
561
1033
|
"div",
|
|
562
1034
|
{ className: "mailharbor-card" },
|
|
563
|
-
(0,
|
|
564
|
-
(0,
|
|
1035
|
+
(0, import_react5.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
|
|
1036
|
+
(0, import_react5.createElement)(
|
|
565
1037
|
"p",
|
|
566
1038
|
{ className: "small muted" },
|
|
567
1039
|
`${account.imap.host}:${String(account.imap.port)} \xB7 ${account.smtp.host}:${String(account.smtp.port)}`
|
|
568
1040
|
),
|
|
569
|
-
(0,
|
|
1041
|
+
(0, import_react5.createElement)(
|
|
570
1042
|
Field,
|
|
571
1043
|
{ label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
|
|
572
|
-
(0,
|
|
1044
|
+
(0, import_react5.createElement)("input", {
|
|
573
1045
|
type: "password",
|
|
574
1046
|
value: password,
|
|
575
1047
|
onChange: (event) => setPassword(event.target.value),
|
|
@@ -579,9 +1051,9 @@ function LoginForm({ account, onDone }) {
|
|
|
579
1051
|
autoFocus: true
|
|
580
1052
|
})
|
|
581
1053
|
),
|
|
582
|
-
(0,
|
|
583
|
-
error === "" ? null : (0,
|
|
584
|
-
(0,
|
|
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)(
|
|
585
1057
|
"button",
|
|
586
1058
|
{ className: "primary", onClick: () => void submit(), disabled: busy || password === "" },
|
|
587
1059
|
busy ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
|
|
@@ -592,10 +1064,10 @@ function AddAccountModal({
|
|
|
592
1064
|
onClose,
|
|
593
1065
|
onDone
|
|
594
1066
|
}) {
|
|
595
|
-
return (0,
|
|
1067
|
+
return (0, import_react5.createElement)(
|
|
596
1068
|
Modal,
|
|
597
1069
|
{ title: "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7", onClose },
|
|
598
|
-
(0,
|
|
1070
|
+
(0, import_react5.createElement)(AddAccountForm, {
|
|
599
1071
|
onDone: (state) => {
|
|
600
1072
|
onDone(state);
|
|
601
1073
|
onClose();
|
|
@@ -606,8 +1078,8 @@ function AddAccountModal({
|
|
|
606
1078
|
}
|
|
607
1079
|
|
|
608
1080
|
// src/client/composer.tsx
|
|
609
|
-
var
|
|
610
|
-
function
|
|
1081
|
+
var import_react6 = require("react");
|
|
1082
|
+
function errorText3(error) {
|
|
611
1083
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
612
1084
|
}
|
|
613
1085
|
function readFileBase64(file) {
|
|
@@ -627,25 +1099,138 @@ function Composer({
|
|
|
627
1099
|
onClose,
|
|
628
1100
|
onSent
|
|
629
1101
|
}) {
|
|
630
|
-
const [to, setTo] = (0,
|
|
631
|
-
const [cc, setCc] = (0,
|
|
632
|
-
const [bcc, setBcc] = (0,
|
|
633
|
-
const [subject, setSubject] = (0,
|
|
634
|
-
const [showCc, setShowCc] = (0,
|
|
635
|
-
const [attachments, setAttachments] = (0,
|
|
636
|
-
const [busy, setBusy] = (0,
|
|
637
|
-
const [error, setError] = (0,
|
|
638
|
-
const [confirming, setConfirming] = (0,
|
|
639
|
-
const [draftId, setDraftId] = (0,
|
|
640
|
-
const
|
|
641
|
-
(0,
|
|
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)(() => {
|
|
642
1125
|
if (editor.current !== null) editor.current.innerHTML = initial.html;
|
|
1126
|
+
setHasBody((editor.current?.innerHTML ?? "").trim() !== "");
|
|
643
1127
|
}, []);
|
|
644
1128
|
const html = () => editor.current?.innerHTML ?? "";
|
|
645
1129
|
const text = () => editor.current?.innerText ?? "";
|
|
1130
|
+
const markDirty = () => {
|
|
1131
|
+
setDirty((value) => value + 1);
|
|
1132
|
+
setHasBody((editor.current?.innerHTML ?? "").trim() !== "");
|
|
1133
|
+
};
|
|
1134
|
+
const saveNow = async () => {
|
|
1135
|
+
if (to.trim() === "" && subject.trim() === "" && html().trim() === "") return false;
|
|
1136
|
+
try {
|
|
1137
|
+
const { draft } = await api.saveDraft(account.id, {
|
|
1138
|
+
...draftId === void 0 ? {} : { id: draftId },
|
|
1139
|
+
to,
|
|
1140
|
+
cc,
|
|
1141
|
+
bcc,
|
|
1142
|
+
subject,
|
|
1143
|
+
html: html(),
|
|
1144
|
+
attachments,
|
|
1145
|
+
...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
|
|
1146
|
+
...initial.references === void 0 ? {} : { references: initial.references }
|
|
1147
|
+
});
|
|
1148
|
+
setDraftId(draft.id);
|
|
1149
|
+
setSavedAt(/* @__PURE__ */ new Date());
|
|
1150
|
+
setDirty(0);
|
|
1151
|
+
return true;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
(0, import_react6.useEffect)(() => {
|
|
1157
|
+
if (dirty === 0) return;
|
|
1158
|
+
const timer = window.setTimeout(() => {
|
|
1159
|
+
void saveNow();
|
|
1160
|
+
}, 2e3);
|
|
1161
|
+
return () => {
|
|
1162
|
+
window.clearTimeout(timer);
|
|
1163
|
+
};
|
|
1164
|
+
}, [dirty, to, cc, bcc, subject, attachments]);
|
|
1165
|
+
const close = () => {
|
|
1166
|
+
if (closing.current) return;
|
|
1167
|
+
closing.current = true;
|
|
1168
|
+
void saveNow().finally(() => {
|
|
1169
|
+
setDirty(0);
|
|
1170
|
+
closing.current = false;
|
|
1171
|
+
onClose();
|
|
1172
|
+
});
|
|
1173
|
+
};
|
|
1174
|
+
const pushVersion = (nextHtml, label) => {
|
|
1175
|
+
const base = versions.slice(0, versionIndex + 1);
|
|
1176
|
+
if (base[base.length - 1]?.html !== html()) base.push({ html: html(), label: "\u624B\u52A8" });
|
|
1177
|
+
base.push({ html: nextHtml, label });
|
|
1178
|
+
setVersions(base.slice(-10));
|
|
1179
|
+
setVersionIndex(Math.min(base.length - 1, 9));
|
|
1180
|
+
if (editor.current !== null) editor.current.innerHTML = nextHtml;
|
|
1181
|
+
setHasBody(nextHtml.trim() !== "");
|
|
1182
|
+
markDirty();
|
|
1183
|
+
};
|
|
1184
|
+
const restoreVersion = (index) => {
|
|
1185
|
+
const version = versions[index];
|
|
1186
|
+
if (version === void 0 || editor.current === null) return;
|
|
1187
|
+
editor.current.innerHTML = version.html;
|
|
1188
|
+
setVersionIndex(index);
|
|
1189
|
+
setHasBody(version.html.trim() !== "");
|
|
1190
|
+
markDirty();
|
|
1191
|
+
};
|
|
1192
|
+
const runAi = async (mode) => {
|
|
1193
|
+
setAiBusy(mode);
|
|
1194
|
+
setError("");
|
|
1195
|
+
try {
|
|
1196
|
+
const result = await api.aiCompose({
|
|
1197
|
+
accountId: account.id,
|
|
1198
|
+
mode,
|
|
1199
|
+
instruction: aiInstruction,
|
|
1200
|
+
html: html(),
|
|
1201
|
+
language: "zh"
|
|
1202
|
+
});
|
|
1203
|
+
pushVersion(result.html, mode === "draft" ? "AI \u8D77\u8349" : mode === "polish" ? "AI \u6DA6\u8272" : "AI \u7F29\u77ED");
|
|
1204
|
+
setAiDraftOpen(false);
|
|
1205
|
+
setAiInstruction("");
|
|
1206
|
+
} catch (caught) {
|
|
1207
|
+
setError(errorText3(caught));
|
|
1208
|
+
} finally {
|
|
1209
|
+
setAiBusy(null);
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
const translate = async (target) => {
|
|
1213
|
+
if (editor.current === null) return;
|
|
1214
|
+
if (html().trim() === "") {
|
|
1215
|
+
setError("\u6B63\u6587\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u7FFB\u8BD1");
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
setPendingLang(null);
|
|
1219
|
+
setTranslating(target);
|
|
1220
|
+
setError("");
|
|
1221
|
+
try {
|
|
1222
|
+
const result = await api.aiTranslate({ accountId: account.id, html: html(), target });
|
|
1223
|
+
pushVersion(result.html, target === "en" ? "\u7FFB\u8BD1 EN" : "\u7FFB\u8BD1 \u4E2D");
|
|
1224
|
+
} catch (caught) {
|
|
1225
|
+
setError(errorText3(caught));
|
|
1226
|
+
} finally {
|
|
1227
|
+
setTranslating(null);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
646
1230
|
const exec = (command, value) => {
|
|
647
1231
|
editor.current?.focus();
|
|
648
1232
|
document.execCommand(command, false, value);
|
|
1233
|
+
markDirty();
|
|
649
1234
|
};
|
|
650
1235
|
const addFiles = async (files) => {
|
|
651
1236
|
if (files === null || files.length === 0) return;
|
|
@@ -662,29 +1247,15 @@ function Composer({
|
|
|
662
1247
|
total += file.size;
|
|
663
1248
|
}
|
|
664
1249
|
setAttachments(next);
|
|
1250
|
+
markDirty();
|
|
665
1251
|
};
|
|
666
1252
|
const saveDraft = async () => {
|
|
667
1253
|
setBusy(true);
|
|
668
1254
|
setError("");
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
cc,
|
|
674
|
-
bcc,
|
|
675
|
-
subject,
|
|
676
|
-
html: html(),
|
|
677
|
-
attachments,
|
|
678
|
-
...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
|
|
679
|
-
...initial.references === void 0 ? {} : { references: initial.references }
|
|
680
|
-
});
|
|
681
|
-
setDraftId(draft.id);
|
|
682
|
-
onSent("\u8349\u7A3F\u5DF2\u4FDD\u5B58");
|
|
683
|
-
} catch (caught) {
|
|
684
|
-
setError(errorText2(caught));
|
|
685
|
-
} finally {
|
|
686
|
-
setBusy(false);
|
|
687
|
-
}
|
|
1255
|
+
const ok = await saveNow();
|
|
1256
|
+
setBusy(false);
|
|
1257
|
+
if (ok) onSent("\u8349\u7A3F\u5DF2\u4FDD\u5B58");
|
|
1258
|
+
else setError("\u8349\u7A3F\u4FDD\u5B58\u5931\u8D25");
|
|
688
1259
|
};
|
|
689
1260
|
const send = async () => {
|
|
690
1261
|
setBusy(true);
|
|
@@ -710,35 +1281,36 @@ function Composer({
|
|
|
710
1281
|
onSent("\u90AE\u4EF6\u5DF2\u53D1\u9001");
|
|
711
1282
|
onClose();
|
|
712
1283
|
} catch (caught) {
|
|
713
|
-
setError(
|
|
1284
|
+
setError(errorText3(caught));
|
|
714
1285
|
} finally {
|
|
715
1286
|
setBusy(false);
|
|
716
1287
|
setConfirming(false);
|
|
717
1288
|
}
|
|
718
1289
|
};
|
|
719
|
-
return (0,
|
|
1290
|
+
return (0, import_react6.createElement)(
|
|
720
1291
|
Modal,
|
|
721
1292
|
{
|
|
722
1293
|
title: initial.mode === "reply" ? "\u56DE\u590D" : initial.mode === "replyAll" ? "\u5168\u90E8\u56DE\u590D" : initial.mode === "forward" ? "\u8F6C\u53D1" : "\u5199\u90AE\u4EF6",
|
|
723
|
-
onClose,
|
|
1294
|
+
onClose: close,
|
|
724
1295
|
wide: true,
|
|
725
|
-
footer: (0,
|
|
1296
|
+
footer: (0, import_react6.createElement)(
|
|
726
1297
|
"div",
|
|
727
|
-
{
|
|
728
|
-
(0,
|
|
729
|
-
(0,
|
|
730
|
-
(0,
|
|
731
|
-
|
|
1298
|
+
{ 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)(
|
|
732
1304
|
"span",
|
|
733
1305
|
{ style: { display: "inline-flex", gap: "6px", alignItems: "center" } },
|
|
734
|
-
(0,
|
|
735
|
-
(0,
|
|
1306
|
+
(0, import_react6.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
|
|
1307
|
+
(0, import_react6.createElement)(
|
|
736
1308
|
"button",
|
|
737
1309
|
{ className: "primary", onClick: () => void send(), disabled: busy },
|
|
738
1310
|
busy ? "\u53D1\u9001\u4E2D\u2026" : "\u786E\u5B9A"
|
|
739
1311
|
),
|
|
740
|
-
(0,
|
|
741
|
-
) : (0,
|
|
1312
|
+
(0, import_react6.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
|
|
1313
|
+
) : (0, import_react6.createElement)(
|
|
742
1314
|
"button",
|
|
743
1315
|
{
|
|
744
1316
|
className: "primary",
|
|
@@ -749,62 +1321,75 @@ function Composer({
|
|
|
749
1321
|
)
|
|
750
1322
|
)
|
|
751
1323
|
},
|
|
752
|
-
error === "" ? null : (0,
|
|
753
|
-
(0,
|
|
1324
|
+
error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
|
|
1325
|
+
(0, import_react6.createElement)(
|
|
754
1326
|
"div",
|
|
755
1327
|
{ className: "mailharbor-grid" },
|
|
756
|
-
(0,
|
|
1328
|
+
(0, import_react6.createElement)(
|
|
757
1329
|
Field,
|
|
758
1330
|
{ label: "\u6536\u4EF6\u4EBA" },
|
|
759
|
-
(0,
|
|
1331
|
+
(0, import_react6.createElement)("input", {
|
|
760
1332
|
value: to,
|
|
761
|
-
onChange: (event) =>
|
|
1333
|
+
onChange: (event) => {
|
|
1334
|
+
setTo(event.target.value);
|
|
1335
|
+
markDirty();
|
|
1336
|
+
},
|
|
762
1337
|
placeholder: "a@example.com, b@example.com"
|
|
763
1338
|
})
|
|
764
1339
|
),
|
|
765
|
-
(0,
|
|
1340
|
+
(0, import_react6.createElement)(
|
|
766
1341
|
Field,
|
|
767
1342
|
{ label: "\u4E3B\u9898" },
|
|
768
|
-
(0,
|
|
1343
|
+
(0, import_react6.createElement)("input", {
|
|
769
1344
|
value: subject,
|
|
770
|
-
onChange: (event) =>
|
|
1345
|
+
onChange: (event) => {
|
|
1346
|
+
setSubject(event.target.value);
|
|
1347
|
+
markDirty();
|
|
1348
|
+
},
|
|
1349
|
+
placeholder: "\u90AE\u4EF6\u4E3B\u9898"
|
|
771
1350
|
})
|
|
772
1351
|
)
|
|
773
1352
|
),
|
|
774
|
-
showCc ? (0,
|
|
1353
|
+
showCc ? (0, import_react6.createElement)(
|
|
775
1354
|
"div",
|
|
776
1355
|
{ className: "mailharbor-grid" },
|
|
777
|
-
(0,
|
|
1356
|
+
(0, import_react6.createElement)(
|
|
778
1357
|
Field,
|
|
779
1358
|
{ label: "\u6284\u9001" },
|
|
780
|
-
(0,
|
|
1359
|
+
(0, import_react6.createElement)("input", {
|
|
781
1360
|
value: cc,
|
|
782
|
-
onChange: (event) =>
|
|
1361
|
+
onChange: (event) => {
|
|
1362
|
+
setCc(event.target.value);
|
|
1363
|
+
markDirty();
|
|
1364
|
+
}
|
|
783
1365
|
})
|
|
784
1366
|
),
|
|
785
|
-
(0,
|
|
1367
|
+
(0, import_react6.createElement)(
|
|
786
1368
|
Field,
|
|
787
1369
|
{ label: "\u5BC6\u9001" },
|
|
788
|
-
(0,
|
|
1370
|
+
(0, import_react6.createElement)("input", {
|
|
789
1371
|
value: bcc,
|
|
790
|
-
onChange: (event) =>
|
|
1372
|
+
onChange: (event) => {
|
|
1373
|
+
setBcc(event.target.value);
|
|
1374
|
+
markDirty();
|
|
1375
|
+
}
|
|
791
1376
|
})
|
|
792
1377
|
)
|
|
793
|
-
) : (0,
|
|
1378
|
+
) : (0, import_react6.createElement)(
|
|
794
1379
|
"button",
|
|
795
1380
|
{ className: "subtle small", style: { alignSelf: "flex-start" }, onClick: () => setShowCc(true) },
|
|
796
1381
|
"\u6DFB\u52A0\u6284\u9001 / \u5BC6\u9001"
|
|
797
1382
|
),
|
|
798
|
-
(0,
|
|
1383
|
+
(0, import_react6.createElement)(
|
|
799
1384
|
"div",
|
|
800
1385
|
{ className: "mailharbor-toolbar" },
|
|
801
|
-
(0,
|
|
802
|
-
(0,
|
|
803
|
-
(0,
|
|
804
|
-
(0,
|
|
805
|
-
(0,
|
|
806
|
-
(0,
|
|
807
|
-
(0,
|
|
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)(
|
|
808
1393
|
"button",
|
|
809
1394
|
{
|
|
810
1395
|
onClick: () => {
|
|
@@ -815,13 +1400,44 @@ function Composer({
|
|
|
815
1400
|
},
|
|
816
1401
|
"\u94FE\u63A5"
|
|
817
1402
|
),
|
|
818
|
-
(0,
|
|
819
|
-
(0,
|
|
820
|
-
(0,
|
|
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)(
|
|
1406
|
+
"button",
|
|
1407
|
+
{
|
|
1408
|
+
className: "ai",
|
|
1409
|
+
onClick: () => setAiDraftOpen(!aiDraftOpen),
|
|
1410
|
+
disabled: aiBusy !== null,
|
|
1411
|
+
title: "\u8BA9 AI \u8D77\u8349\u6B63\u6587"
|
|
1412
|
+
},
|
|
1413
|
+
(0, import_react6.createElement)(Icon, { name: "sparkles", size: 13 }),
|
|
1414
|
+
"\u8D77\u8349"
|
|
1415
|
+
),
|
|
1416
|
+
(0, import_react6.createElement)(
|
|
1417
|
+
"button",
|
|
1418
|
+
{ className: "ai", onClick: () => void runAi("polish"), disabled: aiBusy !== null || !hasBody },
|
|
1419
|
+
aiBusy === "polish" ? "\u6DA6\u8272\u4E2D\u2026" : "\u6DA6\u8272"
|
|
1420
|
+
),
|
|
1421
|
+
(0, import_react6.createElement)(
|
|
1422
|
+
"button",
|
|
1423
|
+
{ className: "ai", onClick: () => void runAi("shorten"), disabled: aiBusy !== null || !hasBody },
|
|
1424
|
+
aiBusy === "shorten" ? "\u7F29\u77ED\u4E2D\u2026" : "\u7F29\u77ED"
|
|
1425
|
+
),
|
|
1426
|
+
(0, import_react6.createElement)(
|
|
1427
|
+
"button",
|
|
1428
|
+
{ onClick: () => setPendingLang("zh"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u4E2D\u6587" },
|
|
1429
|
+
translating === "zh" ? "\u2026" : "\u4E2D"
|
|
1430
|
+
),
|
|
1431
|
+
(0, import_react6.createElement)(
|
|
1432
|
+
"button",
|
|
1433
|
+
{ onClick: () => setPendingLang("en"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u82F1\u6587" },
|
|
1434
|
+
translating === "en" ? "\u2026" : "EN"
|
|
1435
|
+
),
|
|
1436
|
+
(0, import_react6.createElement)(
|
|
821
1437
|
"label",
|
|
822
1438
|
{ className: "mailharbor-toggle small" },
|
|
823
1439
|
"\u9644\u4EF6",
|
|
824
|
-
(0,
|
|
1440
|
+
(0, import_react6.createElement)("input", {
|
|
825
1441
|
type: "file",
|
|
826
1442
|
multiple: true,
|
|
827
1443
|
style: { display: "none" },
|
|
@@ -832,93 +1448,92 @@ function Composer({
|
|
|
832
1448
|
})
|
|
833
1449
|
)
|
|
834
1450
|
),
|
|
835
|
-
|
|
1451
|
+
aiDraftOpen ? (0, import_react6.createElement)(
|
|
1452
|
+
"div",
|
|
1453
|
+
{ className: "mailharbor-confirm" },
|
|
1454
|
+
(0, import_react6.createElement)("input", {
|
|
1455
|
+
value: aiInstruction,
|
|
1456
|
+
placeholder: "\u60F3\u8BA9 AI \u5199\u4EC0\u4E48\uFF1F\u4F8B\u5982\uFF1A\u786E\u8BA4\u6536\u5230\uFF0C\u5468\u4E94\u524D\u7ED9\u7B54\u590D",
|
|
1457
|
+
onChange: (event) => setAiInstruction(event.target.value),
|
|
1458
|
+
onKeyDown: (event) => {
|
|
1459
|
+
if (event.key === "Enter") void runAi("draft");
|
|
1460
|
+
}
|
|
1461
|
+
}),
|
|
1462
|
+
(0, import_react6.createElement)(
|
|
1463
|
+
"button",
|
|
1464
|
+
{ className: "primary", onClick: () => void runAi("draft"), disabled: aiBusy !== null },
|
|
1465
|
+
aiBusy === "draft" ? "\u751F\u6210\u4E2D\u2026" : "\u751F\u6210"
|
|
1466
|
+
),
|
|
1467
|
+
(0, import_react6.createElement)("button", { className: "ghost", onClick: () => setAiDraftOpen(false) }, "\u53D6\u6D88")
|
|
1468
|
+
) : null,
|
|
1469
|
+
pendingLang === null ? null : (0, import_react6.createElement)(
|
|
1470
|
+
"div",
|
|
1471
|
+
{ 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")
|
|
1476
|
+
),
|
|
1477
|
+
attachments.length === 0 ? null : (0, import_react6.createElement)(
|
|
836
1478
|
"div",
|
|
837
1479
|
{ className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
|
|
838
1480
|
attachments.map(
|
|
839
|
-
(file, index) => (0,
|
|
1481
|
+
(file, index) => (0, import_react6.createElement)(
|
|
840
1482
|
"span",
|
|
841
1483
|
{ className: "mailharbor-attachment", key: `${file.filename}:${String(index)}` },
|
|
842
|
-
(0,
|
|
843
|
-
(0,
|
|
1484
|
+
(0, import_react6.createElement)("span", { className: "name" }, file.filename),
|
|
1485
|
+
(0, import_react6.createElement)(
|
|
844
1486
|
"button",
|
|
845
1487
|
{
|
|
846
1488
|
className: "subtle",
|
|
847
|
-
onClick: () =>
|
|
1489
|
+
onClick: () => {
|
|
1490
|
+
setAttachments(attachments.filter((_item, position) => position !== index));
|
|
1491
|
+
markDirty();
|
|
1492
|
+
}
|
|
848
1493
|
},
|
|
849
1494
|
"\u2715"
|
|
850
1495
|
)
|
|
851
1496
|
)
|
|
852
1497
|
)
|
|
853
1498
|
),
|
|
854
|
-
(0,
|
|
1499
|
+
(0, import_react6.createElement)("div", {
|
|
855
1500
|
className: "mailharbor-editor",
|
|
856
1501
|
ref: editor,
|
|
857
1502
|
contentEditable: true,
|
|
858
1503
|
suppressContentEditableWarning: true,
|
|
859
1504
|
"data-placeholder": "\u5199\u70B9\u4EC0\u4E48\u2026",
|
|
860
|
-
spellCheck: true
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
reply: (0, import_react5.createElement)("path", { d: "M9 7 4 12l5 5M4 12h9a6 6 0 0 1 6 6v1" }),
|
|
888
|
-
replyAll: (0, import_react5.createElement)("path", { d: "m9 7-5 5 5 5M14 7l-5 5 5 5M13 12h5a5 5 0 0 1 5 5" }),
|
|
889
|
-
forward: (0, import_react5.createElement)("path", { d: "m15 7 5 5-5 5M20 12h-9a6 6 0 0 0-6 6v1" }),
|
|
890
|
-
check: (0, import_react5.createElement)("path", { d: "m5 13 4 4L19 7" }),
|
|
891
|
-
image: (0, import_react5.createElement)(
|
|
892
|
-
"g",
|
|
893
|
-
null,
|
|
894
|
-
(0, import_react5.createElement)("rect", { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
|
|
895
|
-
(0, import_react5.createElement)("circle", { cx: 8.5, cy: 9.5, r: 1.5 }),
|
|
896
|
-
(0, import_react5.createElement)("path", { d: "m4 18 5-5 4 4 3-3 4 4" })
|
|
897
|
-
),
|
|
898
|
-
close: (0, import_react5.createElement)("path", { d: "M6 6l12 12M18 6 6 18" }),
|
|
899
|
-
chevronLeft: (0, import_react5.createElement)("path", { d: "m14 6-6 6 6 6" })
|
|
900
|
-
};
|
|
901
|
-
function Icon({ name, size = 16 }) {
|
|
902
|
-
return (0, import_react5.createElement)(
|
|
903
|
-
"svg",
|
|
904
|
-
{
|
|
905
|
-
width: size,
|
|
906
|
-
height: size,
|
|
907
|
-
viewBox: "0 0 24 24",
|
|
908
|
-
fill: "none",
|
|
909
|
-
stroke: "currentColor",
|
|
910
|
-
strokeWidth: 1.7,
|
|
911
|
-
strokeLinecap: "round",
|
|
912
|
-
strokeLinejoin: "round",
|
|
913
|
-
"aria-hidden": "true",
|
|
914
|
-
focusable: "false"
|
|
915
|
-
},
|
|
916
|
-
PATHS[name] ?? null
|
|
1505
|
+
spellCheck: true,
|
|
1506
|
+
onInput: markDirty
|
|
1507
|
+
}),
|
|
1508
|
+
(0, import_react6.createElement)(
|
|
1509
|
+
"div",
|
|
1510
|
+
{ className: "mailharbor-versions" },
|
|
1511
|
+
(0, import_react6.createElement)("span", { className: "small muted" }, "\u7248\u672C"),
|
|
1512
|
+
versions.map(
|
|
1513
|
+
(version, index) => (0, import_react6.createElement)(
|
|
1514
|
+
"button",
|
|
1515
|
+
{
|
|
1516
|
+
key: `${version.label}:${String(index)}`,
|
|
1517
|
+
className: index === versionIndex ? "chip active" : "chip",
|
|
1518
|
+
title: version.label,
|
|
1519
|
+
onClick: () => restoreVersion(index)
|
|
1520
|
+
},
|
|
1521
|
+
String(index + 1)
|
|
1522
|
+
)
|
|
1523
|
+
),
|
|
1524
|
+
versions.length > 1 ? (0, import_react6.createElement)(
|
|
1525
|
+
"button",
|
|
1526
|
+
{ className: "subtle small", onClick: () => restoreVersion(Math.max(0, versionIndex - 1)) },
|
|
1527
|
+
"\u6062\u590D\u4E0A\u4E00\u7248"
|
|
1528
|
+
) : null,
|
|
1529
|
+
(0, import_react6.createElement)("span", { className: "spacer" }),
|
|
1530
|
+
(0, import_react6.createElement)("span", { className: "small muted" }, versions[versionIndex]?.label ?? "")
|
|
1531
|
+
)
|
|
917
1532
|
);
|
|
918
1533
|
}
|
|
919
1534
|
|
|
920
1535
|
// src/client/reader.tsx
|
|
921
|
-
var
|
|
1536
|
+
var import_react7 = require("react");
|
|
922
1537
|
var IFRAME_STYLE = `
|
|
923
1538
|
html, body { margin: 0; padding: 0; }
|
|
924
1539
|
body {
|
|
@@ -956,20 +1571,23 @@ function hasRemoteImages(detail) {
|
|
|
956
1571
|
function addressLine(list) {
|
|
957
1572
|
return list.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join("\u3001");
|
|
958
1573
|
}
|
|
1574
|
+
function errorText4(error) {
|
|
1575
|
+
return error instanceof Error ? error.message : String(error);
|
|
1576
|
+
}
|
|
959
1577
|
function Reader({
|
|
960
1578
|
detail,
|
|
961
|
-
account,
|
|
962
1579
|
source,
|
|
963
1580
|
mailbox,
|
|
964
1581
|
onChanged,
|
|
965
1582
|
onReply,
|
|
1583
|
+
onAiAction,
|
|
966
1584
|
onClose
|
|
967
1585
|
}) {
|
|
968
|
-
const [plain, setPlain] = (0,
|
|
969
|
-
const [loadImages, setLoadImages] = (0,
|
|
970
|
-
const [busy, setBusy] = (0,
|
|
971
|
-
const [error, setError] = (0,
|
|
972
|
-
const remoteImages = (0,
|
|
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]);
|
|
973
1591
|
const html = detail.html !== null && detail.html !== "";
|
|
974
1592
|
const toggleRead = async () => {
|
|
975
1593
|
setBusy(true);
|
|
@@ -977,34 +1595,34 @@ function Reader({
|
|
|
977
1595
|
try {
|
|
978
1596
|
const result = await api.setRead({
|
|
979
1597
|
id: detail.id,
|
|
980
|
-
accountId:
|
|
1598
|
+
accountId: detail.accountId,
|
|
981
1599
|
source,
|
|
982
1600
|
mailbox,
|
|
983
1601
|
read: detail.unread
|
|
984
1602
|
});
|
|
985
1603
|
onChanged({ unread: !result.read });
|
|
986
1604
|
} catch (caught) {
|
|
987
|
-
setError(
|
|
1605
|
+
setError(errorText4(caught));
|
|
988
1606
|
} finally {
|
|
989
1607
|
setBusy(false);
|
|
990
1608
|
}
|
|
991
1609
|
};
|
|
992
|
-
return (0,
|
|
1610
|
+
return (0, import_react7.createElement)(
|
|
993
1611
|
"div",
|
|
994
1612
|
{ className: "mailharbor-reader" },
|
|
995
|
-
(0,
|
|
1613
|
+
(0, import_react7.createElement)(
|
|
996
1614
|
"div",
|
|
997
1615
|
{ className: "mailharbor-reader-head" },
|
|
998
|
-
(0,
|
|
1616
|
+
(0, import_react7.createElement)(
|
|
999
1617
|
"div",
|
|
1000
1618
|
{ className: "mailharbor-reader-title" },
|
|
1001
|
-
onClose === void 0 ? null : (0,
|
|
1619
|
+
onClose === void 0 ? null : (0, import_react7.createElement)(
|
|
1002
1620
|
"button",
|
|
1003
1621
|
{ className: "ghost icon", onClick: onClose, "aria-label": "\u8FD4\u56DE\u5217\u8868", title: "\u8FD4\u56DE\u5217\u8868" },
|
|
1004
|
-
(0,
|
|
1622
|
+
(0, import_react7.createElement)(Icon, { name: "chevronLeft" })
|
|
1005
1623
|
),
|
|
1006
|
-
(0,
|
|
1007
|
-
(0,
|
|
1624
|
+
(0, import_react7.createElement)("h2", { style: { flex: 1 } }, detail.subject),
|
|
1625
|
+
(0, import_react7.createElement)(
|
|
1008
1626
|
"button",
|
|
1009
1627
|
{
|
|
1010
1628
|
className: "ghost icon",
|
|
@@ -1013,91 +1631,100 @@ function Reader({
|
|
|
1013
1631
|
title: detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB",
|
|
1014
1632
|
"aria-label": detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB"
|
|
1015
1633
|
},
|
|
1016
|
-
(0,
|
|
1634
|
+
(0, import_react7.createElement)(Icon, { name: "check" })
|
|
1017
1635
|
)
|
|
1018
1636
|
),
|
|
1019
|
-
(0,
|
|
1637
|
+
(0, import_react7.createElement)(
|
|
1020
1638
|
"div",
|
|
1021
1639
|
{ className: "mailharbor-reader-meta" },
|
|
1022
|
-
(0,
|
|
1640
|
+
(0, import_react7.createElement)(
|
|
1023
1641
|
"span",
|
|
1024
1642
|
{ className: "who" },
|
|
1025
|
-
(0,
|
|
1026
|
-
(0,
|
|
1027
|
-
detail.from.name !== "" ? (0,
|
|
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
|
|
1028
1646
|
),
|
|
1029
|
-
(0,
|
|
1030
|
-
detail.backedUp === false ? (0,
|
|
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
|
|
1031
1649
|
),
|
|
1032
|
-
(0,
|
|
1650
|
+
(0, import_react7.createElement)(
|
|
1033
1651
|
"div",
|
|
1034
1652
|
{ className: "mailharbor-reader-meta" },
|
|
1035
|
-
(0,
|
|
1036
|
-
detail.cc.length === 0 ? null : (0,
|
|
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)}`)
|
|
1037
1655
|
),
|
|
1038
|
-
(0,
|
|
1656
|
+
(0, import_react7.createElement)(
|
|
1039
1657
|
"div",
|
|
1040
1658
|
{ className: "mailharbor-reader-actions" },
|
|
1041
|
-
(0,
|
|
1659
|
+
(0, import_react7.createElement)(
|
|
1042
1660
|
"button",
|
|
1043
1661
|
{ className: "ghost", onClick: () => onReply("reply") },
|
|
1044
|
-
(0,
|
|
1662
|
+
(0, import_react7.createElement)(Icon, { name: "reply" }),
|
|
1045
1663
|
"\u56DE\u590D"
|
|
1046
1664
|
),
|
|
1047
|
-
(0,
|
|
1665
|
+
(0, import_react7.createElement)(
|
|
1048
1666
|
"button",
|
|
1049
1667
|
{ className: "ghost", onClick: () => onReply("replyAll") },
|
|
1050
|
-
(0,
|
|
1668
|
+
(0, import_react7.createElement)(Icon, { name: "replyAll" }),
|
|
1051
1669
|
"\u5168\u90E8\u56DE\u590D"
|
|
1052
1670
|
),
|
|
1053
|
-
(0,
|
|
1671
|
+
(0, import_react7.createElement)(
|
|
1054
1672
|
"button",
|
|
1055
1673
|
{ className: "ghost", onClick: () => onReply("forward") },
|
|
1056
|
-
(0,
|
|
1674
|
+
(0, import_react7.createElement)(Icon, { name: "forward" }),
|
|
1057
1675
|
"\u8F6C\u53D1"
|
|
1058
1676
|
),
|
|
1059
|
-
(0,
|
|
1060
|
-
|
|
1677
|
+
(0, import_react7.createElement)("span", { className: "mailharbor-divider" }),
|
|
1678
|
+
(0, import_react7.createElement)(
|
|
1679
|
+
"button",
|
|
1680
|
+
{ className: "ghost", onClick: () => onAiAction("summary") },
|
|
1681
|
+
(0, import_react7.createElement)(Icon, { name: "sparkles", size: 14 }),
|
|
1682
|
+
"AI \u6458\u8981"
|
|
1683
|
+
),
|
|
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)(
|
|
1061
1688
|
"button",
|
|
1062
1689
|
{ className: "subtle", onClick: () => setPlain(!plain) },
|
|
1063
1690
|
plain ? "\u663E\u793A HTML" : "\u7EAF\u6587\u672C"
|
|
1064
1691
|
) : null,
|
|
1065
|
-
remoteImages && !loadImages ? (0,
|
|
1692
|
+
remoteImages && !loadImages ? (0, import_react7.createElement)(
|
|
1066
1693
|
"button",
|
|
1067
1694
|
{ className: "subtle", onClick: () => setLoadImages(true) },
|
|
1068
|
-
(0,
|
|
1695
|
+
(0, import_react7.createElement)(Icon, { name: "image", size: 14 }),
|
|
1069
1696
|
"\u663E\u793A\u56FE\u7247"
|
|
1070
1697
|
) : null
|
|
1071
1698
|
),
|
|
1072
|
-
error === "" ? null : (0,
|
|
1699
|
+
error === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error)
|
|
1073
1700
|
),
|
|
1074
|
-
(0,
|
|
1701
|
+
(0, import_react7.createElement)(
|
|
1075
1702
|
"div",
|
|
1076
1703
|
{ className: "mailharbor-reader-body" },
|
|
1077
|
-
html && !plain ? (0,
|
|
1704
|
+
html && !plain ? (0, import_react7.createElement)("iframe", {
|
|
1078
1705
|
title: "\u90AE\u4EF6\u6B63\u6587",
|
|
1079
1706
|
sandbox: "",
|
|
1080
1707
|
srcDoc: srcDoc(detail, loadImages),
|
|
1081
1708
|
referrerPolicy: "no-referrer"
|
|
1082
|
-
}) : (0,
|
|
1709
|
+
}) : (0, import_react7.createElement)("pre", null, detail.text !== "" ? detail.text : "\uFF08\u65E0\u6B63\u6587\uFF09")
|
|
1083
1710
|
),
|
|
1084
|
-
detail.attachments.length === 0 ? null : (0,
|
|
1711
|
+
detail.attachments.length === 0 ? null : (0, import_react7.createElement)(
|
|
1085
1712
|
"div",
|
|
1086
1713
|
{ className: "mailharbor-attachments" },
|
|
1087
1714
|
detail.attachments.map(
|
|
1088
|
-
(file) => (0,
|
|
1715
|
+
(file) => (0, import_react7.createElement)(
|
|
1089
1716
|
"a",
|
|
1090
1717
|
{
|
|
1091
1718
|
className: "mailharbor-attachment",
|
|
1092
1719
|
key: file.id,
|
|
1093
1720
|
href: source === "server" ? api.attachmentUrl({
|
|
1094
|
-
accountId:
|
|
1721
|
+
accountId: detail.accountId,
|
|
1095
1722
|
source: "server",
|
|
1096
1723
|
mailbox,
|
|
1097
1724
|
uid: detail.uid ?? "",
|
|
1098
1725
|
part: file.id
|
|
1099
1726
|
}) : api.attachmentUrl({
|
|
1100
|
-
accountId:
|
|
1727
|
+
accountId: detail.accountId,
|
|
1101
1728
|
source: "local",
|
|
1102
1729
|
emailId: detail.id,
|
|
1103
1730
|
attachmentId: file.id
|
|
@@ -1105,9 +1732,9 @@ function Reader({
|
|
|
1105
1732
|
download: file.filename,
|
|
1106
1733
|
title: file.filename
|
|
1107
1734
|
},
|
|
1108
|
-
(0,
|
|
1109
|
-
(0,
|
|
1110
|
-
(0,
|
|
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))
|
|
1111
1738
|
)
|
|
1112
1739
|
)
|
|
1113
1740
|
)
|
|
@@ -1116,7 +1743,8 @@ function Reader({
|
|
|
1116
1743
|
|
|
1117
1744
|
// src/client/panel.tsx
|
|
1118
1745
|
var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
|
|
1119
|
-
|
|
1746
|
+
var DRAFTS = "__drafts";
|
|
1747
|
+
function errorText5(error) {
|
|
1120
1748
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
1121
1749
|
}
|
|
1122
1750
|
function matchesNarrow() {
|
|
@@ -1127,61 +1755,71 @@ function quoteForReply(detail) {
|
|
|
1127
1755
|
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>`;
|
|
1128
1756
|
}
|
|
1129
1757
|
function MailPanel() {
|
|
1130
|
-
const [state, setState] = (0,
|
|
1131
|
-
const [error, setError] = (0,
|
|
1132
|
-
const [
|
|
1133
|
-
const [
|
|
1134
|
-
const [
|
|
1135
|
-
const [
|
|
1136
|
-
const [
|
|
1137
|
-
const [
|
|
1138
|
-
const [
|
|
1139
|
-
const [
|
|
1140
|
-
const [
|
|
1141
|
-
const [
|
|
1142
|
-
const [
|
|
1143
|
-
const [
|
|
1144
|
-
const [
|
|
1145
|
-
const [
|
|
1146
|
-
const [
|
|
1147
|
-
const [
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
1150
|
-
const
|
|
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);
|
|
1780
|
+
const accounts = state?.accounts ?? [];
|
|
1781
|
+
const isAll = accountScope === "all";
|
|
1782
|
+
const account = accounts.find((item) => item.id === accountScope) ?? null;
|
|
1783
|
+
const composeAccount = accounts.find((item) => item.id === compose?.accountId) ?? account ?? accounts[0] ?? null;
|
|
1784
|
+
const showingDrafts = mailbox === DRAFTS && source === "local";
|
|
1785
|
+
const refresh = (0, import_react8.useCallback)(() => {
|
|
1151
1786
|
setTick((value) => value + 1);
|
|
1152
1787
|
}, []);
|
|
1153
|
-
const notice = (0,
|
|
1788
|
+
const notice = (0, import_react8.useCallback)((message) => {
|
|
1154
1789
|
setToast(message);
|
|
1155
1790
|
window.setTimeout(() => {
|
|
1156
1791
|
setToast("");
|
|
1157
1792
|
}, 3200);
|
|
1158
1793
|
}, []);
|
|
1159
|
-
(0,
|
|
1794
|
+
(0, import_react8.useEffect)(() => {
|
|
1160
1795
|
let cancelled = false;
|
|
1161
1796
|
api.state().then((next) => {
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1797
|
+
if (cancelled) return;
|
|
1798
|
+
setState(next);
|
|
1799
|
+
if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
|
|
1800
|
+
setAccountScope((current) => current ?? next.activeAccountId);
|
|
1166
1801
|
}).catch((caught) => {
|
|
1167
|
-
if (!cancelled) setError(
|
|
1802
|
+
if (!cancelled) setError(errorText5(caught));
|
|
1168
1803
|
});
|
|
1169
1804
|
return () => {
|
|
1170
1805
|
cancelled = true;
|
|
1171
1806
|
};
|
|
1172
1807
|
}, [tick]);
|
|
1173
|
-
(0,
|
|
1174
|
-
if (
|
|
1808
|
+
(0, import_react8.useEffect)(() => {
|
|
1809
|
+
if (accountScope === null) return;
|
|
1175
1810
|
let cancelled = false;
|
|
1176
|
-
api.folders({ accountId, source }).then((result) => {
|
|
1811
|
+
api.folders({ accountId: accountScope, source }).then((result) => {
|
|
1177
1812
|
if (cancelled) return;
|
|
1178
1813
|
const items2 = source === "local" ? [
|
|
1179
1814
|
{
|
|
1180
1815
|
name: "all",
|
|
1181
1816
|
label: "\u5168\u90E8\u90AE\u4EF6",
|
|
1182
|
-
count: result.items.reduce((sum, item) => sum + item.count, 0)
|
|
1817
|
+
count: result.items.filter((item) => item.name !== DRAFTS).reduce((sum, item) => sum + item.count, 0)
|
|
1183
1818
|
},
|
|
1184
|
-
...result.items.map((item) => ({
|
|
1819
|
+
...result.items.map((item) => ({
|
|
1820
|
+
...item,
|
|
1821
|
+
label: item.name === DRAFTS ? "\u8349\u7A3F" : item.label ?? item.name
|
|
1822
|
+
}))
|
|
1185
1823
|
] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
|
|
1186
1824
|
setFolders(items2);
|
|
1187
1825
|
}).catch(() => {
|
|
@@ -1190,27 +1828,57 @@ function MailPanel() {
|
|
|
1190
1828
|
return () => {
|
|
1191
1829
|
cancelled = true;
|
|
1192
1830
|
};
|
|
1193
|
-
}, [
|
|
1194
|
-
(0,
|
|
1831
|
+
}, [accountScope, source, tick]);
|
|
1832
|
+
(0, import_react8.useEffect)(() => {
|
|
1195
1833
|
setMailbox(source === "local" ? "all" : "INBOX");
|
|
1196
1834
|
setSelected(null);
|
|
1835
|
+
setLimit(state?.settings.render.listLimit ?? 40);
|
|
1197
1836
|
}, [source]);
|
|
1198
|
-
(0,
|
|
1199
|
-
if (
|
|
1837
|
+
(0, import_react8.useEffect)(() => {
|
|
1838
|
+
if (accountScope === "all" && source === "server") setSource("local");
|
|
1839
|
+
setSelected(null);
|
|
1840
|
+
}, [accountScope, source]);
|
|
1841
|
+
(0, import_react8.useEffect)(() => {
|
|
1842
|
+
if (accountScope === null) return;
|
|
1200
1843
|
let cancelled = false;
|
|
1201
1844
|
setLoading(true);
|
|
1202
|
-
|
|
1203
|
-
|
|
1845
|
+
if (showingDrafts) {
|
|
1846
|
+
const targets = isAll ? accounts : account === null ? [] : [account];
|
|
1847
|
+
Promise.all(
|
|
1848
|
+
targets.map(async (target) => {
|
|
1849
|
+
try {
|
|
1850
|
+
const result = await api.drafts(target.id);
|
|
1851
|
+
return result.items.map((draft) => ({ ...draft, accountId: target.id }));
|
|
1852
|
+
} catch {
|
|
1853
|
+
return [];
|
|
1854
|
+
}
|
|
1855
|
+
})
|
|
1856
|
+
).then((groups) => {
|
|
1857
|
+
if (cancelled) return;
|
|
1858
|
+
setDrafts(groups.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
|
|
1859
|
+
setPage(EMPTY_PAGE);
|
|
1860
|
+
}).finally(() => {
|
|
1861
|
+
if (!cancelled) setLoading(false);
|
|
1862
|
+
});
|
|
1863
|
+
return () => {
|
|
1864
|
+
cancelled = true;
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
api.messages({ accountId: accountScope, source, mailbox, query: search, limit }).then((result) => {
|
|
1868
|
+
if (!cancelled) {
|
|
1869
|
+
setPage(result);
|
|
1870
|
+
setDrafts([]);
|
|
1871
|
+
}
|
|
1204
1872
|
}).catch((caught) => {
|
|
1205
|
-
if (!cancelled) setError(
|
|
1873
|
+
if (!cancelled) setError(errorText5(caught));
|
|
1206
1874
|
}).finally(() => {
|
|
1207
1875
|
if (!cancelled) setLoading(false);
|
|
1208
1876
|
});
|
|
1209
1877
|
return () => {
|
|
1210
1878
|
cancelled = true;
|
|
1211
1879
|
};
|
|
1212
|
-
}, [
|
|
1213
|
-
(0,
|
|
1880
|
+
}, [accountScope, source, mailbox, search, limit, tick]);
|
|
1881
|
+
(0, import_react8.useEffect)(() => {
|
|
1214
1882
|
if (typeof window.matchMedia !== "function") return;
|
|
1215
1883
|
const media = window.matchMedia("(max-width: 900px)");
|
|
1216
1884
|
const onChange = () => setNarrow(media.matches);
|
|
@@ -1219,12 +1887,12 @@ function MailPanel() {
|
|
|
1219
1887
|
media.removeEventListener("change", onChange);
|
|
1220
1888
|
};
|
|
1221
1889
|
}, []);
|
|
1222
|
-
(0,
|
|
1223
|
-
if (!showBackup ||
|
|
1890
|
+
(0, import_react8.useEffect)(() => {
|
|
1891
|
+
if (!showBackup || accountScope === null || isAll) return;
|
|
1224
1892
|
let cancelled = false;
|
|
1225
1893
|
const poll = async () => {
|
|
1226
1894
|
try {
|
|
1227
|
-
const status = await api.backupStatus(
|
|
1895
|
+
const status = await api.backupStatus(accountScope);
|
|
1228
1896
|
if (!cancelled) setBackup(status);
|
|
1229
1897
|
} catch {
|
|
1230
1898
|
}
|
|
@@ -1237,16 +1905,17 @@ function MailPanel() {
|
|
|
1237
1905
|
cancelled = true;
|
|
1238
1906
|
window.clearInterval(timer);
|
|
1239
1907
|
};
|
|
1240
|
-
}, [showBackup,
|
|
1908
|
+
}, [showBackup, accountScope, isAll]);
|
|
1241
1909
|
const openMessage = async (item) => {
|
|
1242
|
-
|
|
1910
|
+
const owner = item.accountId;
|
|
1911
|
+
const ownerSource = source;
|
|
1243
1912
|
setError("");
|
|
1244
1913
|
try {
|
|
1245
|
-
const detail = await api.message({ id: item.id, accountId:
|
|
1914
|
+
const detail = await api.message({ id: item.id, accountId: owner, source: ownerSource, mailbox });
|
|
1246
1915
|
setSelected(detail);
|
|
1247
1916
|
if (detail.unread && state?.settings.read.markOnOpen === true) {
|
|
1248
1917
|
try {
|
|
1249
|
-
await api.setRead({ id: detail.id, accountId:
|
|
1918
|
+
await api.setRead({ id: detail.id, accountId: owner, source: ownerSource, mailbox, read: true });
|
|
1250
1919
|
setSelected({ ...detail, unread: false });
|
|
1251
1920
|
setPage((current) => ({
|
|
1252
1921
|
...current,
|
|
@@ -1256,14 +1925,15 @@ function MailPanel() {
|
|
|
1256
1925
|
}
|
|
1257
1926
|
}
|
|
1258
1927
|
} catch (caught) {
|
|
1259
|
-
setError(
|
|
1928
|
+
setError(errorText5(caught));
|
|
1260
1929
|
}
|
|
1261
1930
|
};
|
|
1262
1931
|
const startCompose = (mode) => {
|
|
1263
|
-
|
|
1932
|
+
const owner = account ?? accounts.find((item) => item.id === state?.activeAccountId) ?? accounts[0];
|
|
1933
|
+
if (owner === void 0) return;
|
|
1264
1934
|
const detail = selected;
|
|
1265
1935
|
if (mode === "new" || detail === null) {
|
|
1266
|
-
setCompose({ mode: "new", to: "", cc: "", bcc: "", subject: "", html: "" });
|
|
1936
|
+
setCompose({ mode: "new", accountId: owner.id, to: "", cc: "", bcc: "", subject: "", html: "" });
|
|
1267
1937
|
return;
|
|
1268
1938
|
}
|
|
1269
1939
|
const subjectPrefix = mode === "forward" ? "Fwd: " : "Re: ";
|
|
@@ -1271,6 +1941,7 @@ function MailPanel() {
|
|
|
1271
1941
|
if (mode === "forward") {
|
|
1272
1942
|
setCompose({
|
|
1273
1943
|
mode,
|
|
1944
|
+
accountId: detail.accountId,
|
|
1274
1945
|
to: "",
|
|
1275
1946
|
cc: "",
|
|
1276
1947
|
bcc: "",
|
|
@@ -1281,9 +1952,10 @@ function MailPanel() {
|
|
|
1281
1952
|
return;
|
|
1282
1953
|
}
|
|
1283
1954
|
const to = detail.from.address;
|
|
1284
|
-
const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !==
|
|
1955
|
+
const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !== owner.email && address !== to).join(", ") : "";
|
|
1285
1956
|
setCompose({
|
|
1286
1957
|
mode,
|
|
1958
|
+
accountId: detail.accountId,
|
|
1287
1959
|
to,
|
|
1288
1960
|
cc,
|
|
1289
1961
|
bcc: "",
|
|
@@ -1292,35 +1964,98 @@ function MailPanel() {
|
|
|
1292
1964
|
...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
|
|
1293
1965
|
});
|
|
1294
1966
|
};
|
|
1967
|
+
const requestAi = (kind) => {
|
|
1968
|
+
setRailOpen(true);
|
|
1969
|
+
setAiRequest({ kind, nonce: Date.now() });
|
|
1970
|
+
};
|
|
1971
|
+
const insertReply = (init) => {
|
|
1972
|
+
if (selected === null) return;
|
|
1973
|
+
setCompose({
|
|
1974
|
+
mode: "reply",
|
|
1975
|
+
accountId: selected.accountId,
|
|
1976
|
+
to: init.to,
|
|
1977
|
+
cc: init.cc,
|
|
1978
|
+
bcc: "",
|
|
1979
|
+
subject: init.subject,
|
|
1980
|
+
html: init.html,
|
|
1981
|
+
...init.inReplyTo === void 0 ? {} : { inReplyTo: init.inReplyTo },
|
|
1982
|
+
...init.references === void 0 ? {} : { references: init.references }
|
|
1983
|
+
});
|
|
1984
|
+
};
|
|
1985
|
+
const saveReplyDraft = async (init) => {
|
|
1986
|
+
if (selected === null) return;
|
|
1987
|
+
try {
|
|
1988
|
+
await api.saveDraft(selected.accountId, {
|
|
1989
|
+
to: init.to,
|
|
1990
|
+
cc: init.cc,
|
|
1991
|
+
bcc: "",
|
|
1992
|
+
subject: init.subject,
|
|
1993
|
+
html: init.html,
|
|
1994
|
+
attachments: [],
|
|
1995
|
+
...init.inReplyTo === void 0 ? {} : { inReplyTo: init.inReplyTo },
|
|
1996
|
+
...init.references === void 0 ? {} : { references: init.references }
|
|
1997
|
+
});
|
|
1998
|
+
notice("\u5DF2\u5B58\u4E3A\u8349\u7A3F");
|
|
1999
|
+
refresh();
|
|
2000
|
+
} catch (caught) {
|
|
2001
|
+
setError(errorText5(caught));
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
const openDraft = (draft) => {
|
|
2005
|
+
setCompose({
|
|
2006
|
+
mode: "new",
|
|
2007
|
+
accountId: draft.accountId,
|
|
2008
|
+
to: draft.to,
|
|
2009
|
+
cc: draft.cc,
|
|
2010
|
+
bcc: draft.bcc,
|
|
2011
|
+
subject: draft.subject,
|
|
2012
|
+
html: draft.html,
|
|
2013
|
+
attachments: draft.attachments,
|
|
2014
|
+
draftId: draft.id
|
|
2015
|
+
});
|
|
2016
|
+
};
|
|
2017
|
+
const removeDraft = async (id, accountId) => {
|
|
2018
|
+
try {
|
|
2019
|
+
await api.deleteDraft(id, accountId);
|
|
2020
|
+
setDrafts((current) => current.filter((draft) => draft.id !== id));
|
|
2021
|
+
notice("\u8349\u7A3F\u5DF2\u5220\u9664");
|
|
2022
|
+
refresh();
|
|
2023
|
+
} catch (caught) {
|
|
2024
|
+
setError(errorText5(caught));
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
1295
2027
|
const switchAccount = async (id) => {
|
|
1296
2028
|
try {
|
|
1297
|
-
await api.activateAccount(id);
|
|
2029
|
+
if (id !== "all") await api.activateAccount(id);
|
|
2030
|
+
setAccountScope(id);
|
|
1298
2031
|
setSelected(null);
|
|
2032
|
+
if (id === "all") setSource("local");
|
|
1299
2033
|
refresh();
|
|
1300
2034
|
} catch (caught) {
|
|
1301
|
-
setError(
|
|
2035
|
+
setError(errorText5(caught));
|
|
1302
2036
|
}
|
|
1303
2037
|
};
|
|
1304
2038
|
const startBackup = async () => {
|
|
1305
|
-
|
|
2039
|
+
const owner = accountScope === null || isAll ? accounts[0] : accounts.find((item) => item.id === accountScope);
|
|
2040
|
+
if (owner === void 0) return;
|
|
1306
2041
|
try {
|
|
1307
|
-
const result = await api.startBackup(
|
|
2042
|
+
const result = await api.startBackup(owner.id);
|
|
1308
2043
|
setBackup(result.status);
|
|
1309
2044
|
setShowBackup(true);
|
|
1310
2045
|
} catch (caught) {
|
|
1311
|
-
setError(
|
|
2046
|
+
setError(errorText5(caught));
|
|
1312
2047
|
}
|
|
1313
2048
|
};
|
|
1314
2049
|
const stopBackup = async () => {
|
|
1315
|
-
if (
|
|
2050
|
+
if (accountScope === null || isAll) return;
|
|
1316
2051
|
try {
|
|
1317
|
-
const result = await api.stopBackup(
|
|
2052
|
+
const result = await api.stopBackup(accountScope);
|
|
1318
2053
|
setBackup(result.status);
|
|
1319
2054
|
} catch (caught) {
|
|
1320
|
-
setError(
|
|
2055
|
+
setError(errorText5(caught));
|
|
1321
2056
|
}
|
|
1322
2057
|
};
|
|
1323
|
-
const folderOptions = (0,
|
|
2058
|
+
const folderOptions = (0, import_react8.useMemo)(
|
|
1324
2059
|
() => folders.map((folder) => ({
|
|
1325
2060
|
value: folder.name,
|
|
1326
2061
|
label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
|
|
@@ -1328,74 +2063,90 @@ function MailPanel() {
|
|
|
1328
2063
|
[folders]
|
|
1329
2064
|
);
|
|
1330
2065
|
if (state === null) {
|
|
1331
|
-
return (0,
|
|
2066
|
+
return (0, import_react8.createElement)(
|
|
1332
2067
|
"div",
|
|
1333
2068
|
{ className: "mailharbor" },
|
|
1334
|
-
(0,
|
|
2069
|
+
(0, import_react8.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
|
|
1335
2070
|
);
|
|
1336
2071
|
}
|
|
1337
|
-
if (
|
|
1338
|
-
return (0,
|
|
2072
|
+
if (accounts.length === 0) {
|
|
2073
|
+
return (0, import_react8.createElement)(
|
|
1339
2074
|
"div",
|
|
1340
2075
|
{ className: "mailharbor" },
|
|
1341
|
-
(0,
|
|
2076
|
+
(0, import_react8.createElement)(
|
|
1342
2077
|
"div",
|
|
1343
2078
|
{ className: "mailharbor-settings" },
|
|
1344
|
-
(0,
|
|
1345
|
-
(0,
|
|
2079
|
+
(0, import_react8.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
|
|
2080
|
+
(0, import_react8.createElement)(
|
|
1346
2081
|
"p",
|
|
1347
2082
|
{ className: "muted small" },
|
|
1348
2083
|
"\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"
|
|
1349
2084
|
),
|
|
1350
|
-
error === "" ? null : (0,
|
|
1351
|
-
(0,
|
|
2085
|
+
error === "" ? null : (0, import_react8.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
|
|
2086
|
+
(0, import_react8.createElement)(AddAccountForm, {
|
|
1352
2087
|
onDone: (next) => {
|
|
1353
2088
|
setState(next);
|
|
2089
|
+
setAccountScope(next.activeAccountId);
|
|
1354
2090
|
setSource("local");
|
|
1355
2091
|
}
|
|
1356
2092
|
})
|
|
1357
2093
|
)
|
|
1358
2094
|
);
|
|
1359
2095
|
}
|
|
1360
|
-
const
|
|
2096
|
+
const currentAccount = account ?? accounts[0] ?? null;
|
|
2097
|
+
const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
|
|
2098
|
+
const needsLogin = source === "server" && !isAll && state.loggedIn === false;
|
|
1361
2099
|
const items = page.items;
|
|
1362
|
-
return (0,
|
|
2100
|
+
return (0, import_react8.createElement)(
|
|
1363
2101
|
"div",
|
|
1364
2102
|
{ className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
|
|
1365
|
-
(0,
|
|
2103
|
+
(0, import_react8.createElement)(
|
|
1366
2104
|
"div",
|
|
1367
2105
|
{ className: "mailharbor-topbar" },
|
|
1368
|
-
(0,
|
|
2106
|
+
(0, import_react8.createElement)(
|
|
1369
2107
|
"div",
|
|
1370
2108
|
{ className: "mailharbor-account" },
|
|
1371
|
-
(0,
|
|
1372
|
-
|
|
2109
|
+
(0, import_react8.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
|
|
2110
|
+
accounts.length > 1 ? (0, import_react8.createElement)(
|
|
1373
2111
|
"select",
|
|
1374
2112
|
{
|
|
1375
|
-
value:
|
|
2113
|
+
value: accountScope ?? "",
|
|
1376
2114
|
onChange: (event) => void switchAccount(event.target.value),
|
|
1377
2115
|
"aria-label": "\u8D26\u53F7"
|
|
1378
2116
|
},
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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
|
|
1382
2122
|
),
|
|
1383
|
-
(0,
|
|
2123
|
+
(0, import_react8.createElement)(
|
|
1384
2124
|
"div",
|
|
1385
2125
|
{ className: "mailharbor-segment", role: "tablist" },
|
|
1386
|
-
(0,
|
|
2126
|
+
(0, import_react8.createElement)(
|
|
1387
2127
|
"button",
|
|
1388
2128
|
{ className: source === "local" ? "active" : "", onClick: () => setSource("local"), role: "tab" },
|
|
1389
2129
|
"\u672C\u5730\u5907\u4EFD"
|
|
1390
2130
|
),
|
|
1391
|
-
(0,
|
|
2131
|
+
(0, import_react8.createElement)(
|
|
1392
2132
|
"button",
|
|
1393
|
-
{
|
|
2133
|
+
{
|
|
2134
|
+
className: source === "server" ? "active" : "",
|
|
2135
|
+
onClick: () => {
|
|
2136
|
+
if (isAll) {
|
|
2137
|
+
notice("\u7EDF\u4E00\u6536\u4EF6\u7BB1\u53EA\u652F\u6301\u672C\u5730\u5907\u4EFD\uFF0C\u5207\u6362\u5230\u5355\u4E2A\u8D26\u53F7\u53EF\u4F7F\u7528\u670D\u52A1\u5668\u5B9E\u65F6");
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
setSource("server");
|
|
2141
|
+
},
|
|
2142
|
+
role: "tab",
|
|
2143
|
+
title: isAll ? "\u7EDF\u4E00\u6536\u4EF6\u7BB1\u53EA\u652F\u6301\u672C\u5730\u5907\u4EFD" : "\u8BFB\u53D6\u670D\u52A1\u5668\u4E0A\u7684\u6700\u65B0\u90AE\u4EF6"
|
|
2144
|
+
},
|
|
1394
2145
|
"\u670D\u52A1\u5668\u5B9E\u65F6"
|
|
1395
2146
|
)
|
|
1396
2147
|
),
|
|
1397
|
-
(0,
|
|
1398
|
-
(0,
|
|
2148
|
+
(0, import_react8.createElement)("span", { className: "spacer" }),
|
|
2149
|
+
(0, import_react8.createElement)(
|
|
1399
2150
|
"button",
|
|
1400
2151
|
{
|
|
1401
2152
|
className: "ghost icon",
|
|
@@ -1403,38 +2154,56 @@ function MailPanel() {
|
|
|
1403
2154
|
"aria-label": "\u6DFB\u52A0\u8D26\u53F7",
|
|
1404
2155
|
onClick: () => setShowAdd(true)
|
|
1405
2156
|
},
|
|
1406
|
-
(0,
|
|
2157
|
+
(0, import_react8.createElement)(Icon, { name: "plus" })
|
|
1407
2158
|
),
|
|
1408
|
-
(0,
|
|
2159
|
+
(0, import_react8.createElement)(
|
|
1409
2160
|
"button",
|
|
1410
2161
|
{ className: "ghost icon", title: "\u5237\u65B0\u5217\u8868", "aria-label": "\u5237\u65B0\u5217\u8868", onClick: refresh },
|
|
1411
|
-
(0,
|
|
2162
|
+
(0, import_react8.createElement)(Icon, { name: "refresh" })
|
|
1412
2163
|
),
|
|
1413
|
-
(0,
|
|
2164
|
+
(0, import_react8.createElement)(
|
|
1414
2165
|
"button",
|
|
1415
|
-
{
|
|
1416
|
-
|
|
2166
|
+
{
|
|
2167
|
+
className: "ghost",
|
|
2168
|
+
onClick: () => void startBackup(),
|
|
2169
|
+
title: isAll ? "\u5907\u4EFD\u5F53\u524D\u6D3B\u52A8\u8D26\u53F7" : "\u5907\u4EFD\u8FD9\u4E2A\u8D26\u53F7"
|
|
2170
|
+
},
|
|
2171
|
+
(0, import_react8.createElement)(Icon, { name: "archive" }),
|
|
1417
2172
|
"\u5907\u4EFD"
|
|
1418
2173
|
),
|
|
1419
|
-
(0,
|
|
2174
|
+
(0, import_react8.createElement)(
|
|
2175
|
+
"button",
|
|
2176
|
+
{
|
|
2177
|
+
className: railOpen ? "ghost active" : "ghost",
|
|
2178
|
+
onClick: () => setRailOpen(!railOpen),
|
|
2179
|
+
title: "AI \u5DE5\u4F5C\u53F0"
|
|
2180
|
+
},
|
|
2181
|
+
(0, import_react8.createElement)(Icon, { name: "sparkles" }),
|
|
2182
|
+
"AI"
|
|
2183
|
+
),
|
|
2184
|
+
(0, import_react8.createElement)(
|
|
1420
2185
|
"button",
|
|
1421
2186
|
{ className: "primary", onClick: () => startCompose("new") },
|
|
1422
|
-
(0,
|
|
2187
|
+
(0, import_react8.createElement)(Icon, { name: "compose" }),
|
|
1423
2188
|
"\u5199\u90AE\u4EF6"
|
|
1424
2189
|
)
|
|
1425
2190
|
),
|
|
1426
|
-
toast === "" ? null : (0,
|
|
1427
|
-
error === "" ? null : (0,
|
|
1428
|
-
needsLogin ? (0,
|
|
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)(
|
|
2194
|
+
"div",
|
|
2195
|
+
{ className: "mailharbor-settings" },
|
|
2196
|
+
(0, import_react8.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
|
|
2197
|
+
) : (0, import_react8.createElement)(
|
|
1429
2198
|
"div",
|
|
1430
2199
|
{ className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
|
|
1431
|
-
(0,
|
|
2200
|
+
(0, import_react8.createElement)(
|
|
1432
2201
|
"div",
|
|
1433
2202
|
{ className: "mailharbor-list" },
|
|
1434
|
-
(0,
|
|
2203
|
+
(0, import_react8.createElement)(
|
|
1435
2204
|
"div",
|
|
1436
2205
|
{ className: "mailharbor-filters" },
|
|
1437
|
-
(0,
|
|
2206
|
+
(0, import_react8.createElement)(
|
|
1438
2207
|
"select",
|
|
1439
2208
|
{
|
|
1440
2209
|
value: mailbox,
|
|
@@ -1445,12 +2214,12 @@ function MailPanel() {
|
|
|
1445
2214
|
"aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
|
|
1446
2215
|
},
|
|
1447
2216
|
folderOptions.map(
|
|
1448
|
-
(option) => (0,
|
|
2217
|
+
(option) => (0, import_react8.createElement)("option", { key: option.value, value: option.value }, option.label)
|
|
1449
2218
|
)
|
|
1450
2219
|
),
|
|
1451
|
-
(0,
|
|
2220
|
+
(0, import_react8.createElement)("input", {
|
|
1452
2221
|
type: "search",
|
|
1453
|
-
placeholder: "\u641C\u7D22\u90AE\u4EF6",
|
|
2222
|
+
placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
|
|
1454
2223
|
"aria-label": "\u641C\u7D22\u90AE\u4EF6",
|
|
1455
2224
|
value: query2,
|
|
1456
2225
|
onChange: (event) => setQuery(event.target.value),
|
|
@@ -1462,7 +2231,7 @@ function MailPanel() {
|
|
|
1462
2231
|
}
|
|
1463
2232
|
}
|
|
1464
2233
|
}),
|
|
1465
|
-
query2 !== search || search !== "" ? (0,
|
|
2234
|
+
query2 !== search || search !== "" ? (0, import_react8.createElement)(
|
|
1466
2235
|
"button",
|
|
1467
2236
|
{
|
|
1468
2237
|
className: "subtle",
|
|
@@ -1472,32 +2241,98 @@ function MailPanel() {
|
|
|
1472
2241
|
setSearch("");
|
|
1473
2242
|
}
|
|
1474
2243
|
},
|
|
1475
|
-
(0,
|
|
2244
|
+
(0, import_react8.createElement)(Icon, { name: "close", size: 14 })
|
|
1476
2245
|
) : null
|
|
1477
2246
|
),
|
|
1478
|
-
(0,
|
|
2247
|
+
(0, import_react8.createElement)(
|
|
1479
2248
|
"div",
|
|
1480
2249
|
{ className: "mailharbor-rows" },
|
|
1481
|
-
loading && items.length === 0 ? (0,
|
|
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)(
|
|
2251
|
+
"div",
|
|
2252
|
+
{ 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")
|
|
2256
|
+
) : drafts.filter(
|
|
2257
|
+
(draft) => search === "" ? true : `${draft.subject} ${draft.to} ${draft.cc}`.toLowerCase().includes(search.toLowerCase())
|
|
2258
|
+
).map(
|
|
2259
|
+
(draft) => (0, import_react8.createElement)(
|
|
2260
|
+
"div",
|
|
2261
|
+
{
|
|
2262
|
+
key: draft.id,
|
|
2263
|
+
className: "mailharbor-row",
|
|
2264
|
+
role: "button",
|
|
2265
|
+
tabIndex: 0,
|
|
2266
|
+
onClick: () => openDraft(draft),
|
|
2267
|
+
onKeyDown: (event) => {
|
|
2268
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2269
|
+
event.preventDefault();
|
|
2270
|
+
openDraft(draft);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2274
|
+
(0, import_react8.createElement)(
|
|
2275
|
+
"span",
|
|
2276
|
+
{ className: "line-1" },
|
|
2277
|
+
(0, import_react8.createElement)(
|
|
2278
|
+
"span",
|
|
2279
|
+
{ className: "from" },
|
|
2280
|
+
`\u8349\u7A3F${draft.to === "" ? "" : ` \xB7 \u6536\u4EF6\u4EBA ${draft.to}`}`
|
|
2281
|
+
),
|
|
2282
|
+
isAll ? (0, import_react8.createElement)(
|
|
2283
|
+
"span",
|
|
2284
|
+
{ className: "badge" },
|
|
2285
|
+
accounts.find((item) => item.id === draft.accountId)?.label ?? ""
|
|
2286
|
+
) : null,
|
|
2287
|
+
(0, import_react8.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
|
|
2288
|
+
(0, import_react8.createElement)(
|
|
2289
|
+
"button",
|
|
2290
|
+
{
|
|
2291
|
+
className: "subtle",
|
|
2292
|
+
title: "\u5220\u9664\u8349\u7A3F",
|
|
2293
|
+
onClick: (event) => {
|
|
2294
|
+
event.stopPropagation();
|
|
2295
|
+
void removeDraft(draft.id, draft.accountId);
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
(0, import_react8.createElement)(Icon, { name: "close", size: 13 })
|
|
2299
|
+
)
|
|
2300
|
+
),
|
|
2301
|
+
(0, import_react8.createElement)(
|
|
2302
|
+
"span",
|
|
2303
|
+
{ className: "line-2" },
|
|
2304
|
+
(0, import_react8.createElement)(
|
|
2305
|
+
"span",
|
|
2306
|
+
{ className: "subject" },
|
|
2307
|
+
draft.subject === "" ? "(\u65E0\u4E3B\u9898)" : draft.subject
|
|
2308
|
+
)
|
|
2309
|
+
),
|
|
2310
|
+
draft.html === "" ? null : (0, import_react8.createElement)(
|
|
2311
|
+
"span",
|
|
2312
|
+
{ className: "snippet" },
|
|
2313
|
+
draft.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120)
|
|
2314
|
+
)
|
|
2315
|
+
)
|
|
2316
|
+
) : items.length === 0 ? (0, import_react8.createElement)(
|
|
1482
2317
|
"div",
|
|
1483
2318
|
{ className: "mailharbor-empty" },
|
|
1484
|
-
(0,
|
|
1485
|
-
(0,
|
|
2319
|
+
(0, import_react8.createElement)(Icon, { name: "mail", size: 28 }),
|
|
2320
|
+
(0, import_react8.createElement)(
|
|
1486
2321
|
"div",
|
|
1487
2322
|
{ className: "title" },
|
|
1488
2323
|
source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6"
|
|
1489
2324
|
),
|
|
1490
|
-
(0,
|
|
2325
|
+
(0, import_react8.createElement)(
|
|
1491
2326
|
"div",
|
|
1492
2327
|
{ className: "small" },
|
|
1493
2328
|
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"
|
|
1494
2329
|
)
|
|
1495
2330
|
) : items.map(
|
|
1496
|
-
(item) => (0,
|
|
2331
|
+
(item) => (0, import_react8.createElement)(
|
|
1497
2332
|
"div",
|
|
1498
2333
|
{
|
|
1499
|
-
key: item.id
|
|
1500
|
-
className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id ? " selected" : ""}`,
|
|
2334
|
+
key: `${item.accountId}:${item.id}`,
|
|
2335
|
+
className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}`,
|
|
1501
2336
|
role: "button",
|
|
1502
2337
|
tabIndex: 0,
|
|
1503
2338
|
onClick: () => void openMessage(item),
|
|
@@ -1508,102 +2343,127 @@ function MailPanel() {
|
|
|
1508
2343
|
}
|
|
1509
2344
|
}
|
|
1510
2345
|
},
|
|
1511
|
-
(0,
|
|
2346
|
+
(0, import_react8.createElement)(
|
|
1512
2347
|
"span",
|
|
1513
2348
|
{ className: "line-1" },
|
|
1514
|
-
item.unread ? (0,
|
|
1515
|
-
(0,
|
|
2349
|
+
item.unread ? (0, import_react8.createElement)("span", { className: "dot" }) : null,
|
|
2350
|
+
(0, import_react8.createElement)(
|
|
1516
2351
|
"span",
|
|
1517
2352
|
{ className: "from" },
|
|
1518
2353
|
item.from.name !== "" ? item.from.name : item.from.address
|
|
1519
2354
|
),
|
|
1520
|
-
item.hasAttachments ? (0,
|
|
1521
|
-
|
|
1522
|
-
|
|
2355
|
+
item.hasAttachments ? (0, import_react8.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
|
|
2356
|
+
isAll ? (0, import_react8.createElement)(
|
|
2357
|
+
"span",
|
|
2358
|
+
{ className: "badge" },
|
|
2359
|
+
accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
|
|
2360
|
+
) : 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))
|
|
2363
|
+
),
|
|
2364
|
+
(0, import_react8.createElement)(
|
|
2365
|
+
"span",
|
|
2366
|
+
{ className: "line-2" },
|
|
2367
|
+
(0, import_react8.createElement)("span", { className: "subject" }, item.subject)
|
|
1523
2368
|
),
|
|
1524
|
-
|
|
1525
|
-
item.snippet === "" ? null : (0, import_react7.createElement)("span", { className: "snippet" }, item.snippet)
|
|
2369
|
+
item.snippet === "" ? null : (0, import_react8.createElement)("span", { className: "snippet" }, item.snippet)
|
|
1526
2370
|
)
|
|
1527
2371
|
)
|
|
1528
2372
|
),
|
|
1529
|
-
items.length > 0 ? (0,
|
|
2373
|
+
!showingDrafts && items.length > 0 ? (0, import_react8.createElement)(
|
|
1530
2374
|
"div",
|
|
1531
2375
|
{ className: "mailharbor-list-foot" },
|
|
1532
|
-
(0,
|
|
1533
|
-
(0,
|
|
1534
|
-
page.total > items.length ? (0,
|
|
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)(
|
|
1535
2379
|
"button",
|
|
1536
2380
|
{ className: "subtle", onClick: () => setLimit((current) => current + 40) },
|
|
1537
2381
|
"\u52A0\u8F7D\u66F4\u591A"
|
|
1538
2382
|
) : null
|
|
2383
|
+
) : showingDrafts && drafts.length > 0 ? (0, import_react8.createElement)(
|
|
2384
|
+
"div",
|
|
2385
|
+
{ className: "mailharbor-list-foot" },
|
|
2386
|
+
(0, import_react8.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
|
|
1539
2387
|
) : null
|
|
1540
2388
|
),
|
|
1541
|
-
(0,
|
|
2389
|
+
(0, import_react8.createElement)(
|
|
1542
2390
|
"div",
|
|
1543
2391
|
{ className: "mailharbor-reader" },
|
|
1544
|
-
selected === null ? (0,
|
|
2392
|
+
selected === null ? (0, import_react8.createElement)(
|
|
1545
2393
|
"div",
|
|
1546
2394
|
{ className: "mailharbor-empty" },
|
|
1547
|
-
(0,
|
|
1548
|
-
(0,
|
|
1549
|
-
(0,
|
|
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)(
|
|
1550
2398
|
"div",
|
|
1551
2399
|
{ className: "small" },
|
|
1552
|
-
|
|
2400
|
+
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"
|
|
1553
2401
|
)
|
|
1554
|
-
) : (0,
|
|
2402
|
+
) : (0, import_react8.createElement)(Reader, {
|
|
1555
2403
|
detail: selected,
|
|
1556
|
-
account,
|
|
1557
2404
|
source,
|
|
1558
2405
|
mailbox,
|
|
1559
2406
|
onChanged: (patch) => setSelected({ ...selected, ...patch }),
|
|
1560
2407
|
onReply: (mode) => startCompose(mode),
|
|
2408
|
+
onAiAction: requestAi,
|
|
1561
2409
|
onClose: narrow ? () => setSelected(null) : void 0
|
|
1562
2410
|
})
|
|
1563
|
-
)
|
|
2411
|
+
),
|
|
2412
|
+
railOpen ? (0, import_react8.createElement)(AiRail, {
|
|
2413
|
+
account: accounts.find((item) => item.id === (selected?.accountId ?? accountScope)) ?? currentAccount,
|
|
2414
|
+
detail: selected,
|
|
2415
|
+
source,
|
|
2416
|
+
mailbox,
|
|
2417
|
+
request: aiRequest,
|
|
2418
|
+
onClose: () => setRailOpen(false),
|
|
2419
|
+
onInsert: insertReply,
|
|
2420
|
+
onSaveDraft: (init) => void saveReplyDraft(init),
|
|
2421
|
+
onNotice: notice
|
|
2422
|
+
}) : null
|
|
1564
2423
|
),
|
|
1565
|
-
showBackup && backup !== null ? (0,
|
|
2424
|
+
showBackup && backup !== null ? (0, import_react8.createElement)(
|
|
1566
2425
|
Modal,
|
|
1567
2426
|
{
|
|
1568
2427
|
title: "\u5907\u4EFD",
|
|
1569
2428
|
onClose: () => setShowBackup(false),
|
|
1570
|
-
footer: (0,
|
|
2429
|
+
footer: (0, import_react8.createElement)(
|
|
1571
2430
|
"div",
|
|
1572
2431
|
{ style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
|
|
1573
|
-
(0,
|
|
1574
|
-
(0,
|
|
1575
|
-
backup.running ? (0,
|
|
1576
|
-
(0,
|
|
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")
|
|
1577
2436
|
)
|
|
1578
2437
|
},
|
|
1579
|
-
(0,
|
|
2438
|
+
(0, import_react8.createElement)(
|
|
1580
2439
|
"div",
|
|
1581
2440
|
{ className: "mailharbor-kv" },
|
|
1582
|
-
(0,
|
|
1583
|
-
(0,
|
|
1584
|
-
(0,
|
|
1585
|
-
(0,
|
|
1586
|
-
(0,
|
|
1587
|
-
(0,
|
|
1588
|
-
(0,
|
|
1589
|
-
(0,
|
|
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))
|
|
1590
2449
|
),
|
|
1591
|
-
(0,
|
|
2450
|
+
(0, import_react8.createElement)(
|
|
1592
2451
|
"div",
|
|
1593
2452
|
{ className: "mailharbor-log" },
|
|
1594
2453
|
backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
|
|
1595
2454
|
)
|
|
1596
2455
|
) : null,
|
|
1597
|
-
compose !== null ? (0,
|
|
1598
|
-
account,
|
|
2456
|
+
compose !== null && composeAccount !== null ? (0, import_react8.createElement)(Composer, {
|
|
2457
|
+
account: composeAccount,
|
|
1599
2458
|
initial: compose,
|
|
1600
2459
|
onClose: () => setCompose(null),
|
|
1601
2460
|
onSent: (message) => notice(message)
|
|
1602
2461
|
}) : null,
|
|
1603
|
-
showAdd ? (0,
|
|
2462
|
+
showAdd ? (0, import_react8.createElement)(AddAccountModal, {
|
|
1604
2463
|
onClose: () => setShowAdd(false),
|
|
1605
2464
|
onDone: (next) => {
|
|
1606
2465
|
setState(next);
|
|
2466
|
+
setAccountScope(next.activeAccountId);
|
|
1607
2467
|
refresh();
|
|
1608
2468
|
}
|
|
1609
2469
|
}) : null
|
|
@@ -1611,27 +2471,32 @@ function MailPanel() {
|
|
|
1611
2471
|
}
|
|
1612
2472
|
|
|
1613
2473
|
// src/client/settings.tsx
|
|
1614
|
-
var
|
|
1615
|
-
function
|
|
2474
|
+
var import_react9 = require("react");
|
|
2475
|
+
function errorText6(error) {
|
|
1616
2476
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
1617
2477
|
}
|
|
1618
2478
|
function MailSettings() {
|
|
1619
|
-
const [state, setState] = (0,
|
|
1620
|
-
const [settings, setSettings] = (0,
|
|
1621
|
-
const [error, setError] = (0,
|
|
1622
|
-
const [toast, setToast] = (0,
|
|
1623
|
-
const [signature, setSignature] = (0,
|
|
1624
|
-
const [markOnOpen, setMarkOnOpen] = (0,
|
|
1625
|
-
const [pollSeconds, setPollSeconds] = (0,
|
|
1626
|
-
const [listLimit, setListLimit] = (0,
|
|
1627
|
-
const [aiEnabled, setAiEnabled] = (0,
|
|
1628
|
-
const [aiProvider, setAiProvider] = (0,
|
|
1629
|
-
const [aiModel, setAiModel] = (0,
|
|
1630
|
-
const [
|
|
1631
|
-
const [
|
|
1632
|
-
const [
|
|
1633
|
-
const [
|
|
1634
|
-
(0,
|
|
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 [checkResult, setCheckResult] = (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)(() => {
|
|
1635
2500
|
let cancelled = false;
|
|
1636
2501
|
Promise.all([api.state(), api.settings()]).then(([appState, view]) => {
|
|
1637
2502
|
if (cancelled) return;
|
|
@@ -1645,12 +2510,37 @@ function MailSettings() {
|
|
|
1645
2510
|
setAiProvider(view.ai.provider);
|
|
1646
2511
|
setAiModel(view.ai.model);
|
|
1647
2512
|
}).catch((caught) => {
|
|
1648
|
-
if (!cancelled) setError(
|
|
2513
|
+
if (!cancelled) setError(errorText6(caught));
|
|
1649
2514
|
});
|
|
1650
2515
|
return () => {
|
|
1651
2516
|
cancelled = true;
|
|
1652
2517
|
};
|
|
1653
2518
|
}, [tick]);
|
|
2519
|
+
(0, import_react9.useEffect)(() => {
|
|
2520
|
+
let cancelled = false;
|
|
2521
|
+
const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
|
|
2522
|
+
if (accountId === null) return;
|
|
2523
|
+
Promise.all([api.aiStatus(accountId), api.aiModels(), api.aiStyle(accountId)]).then(([status, models, style]) => {
|
|
2524
|
+
if (cancelled) return;
|
|
2525
|
+
setAiStatus(status);
|
|
2526
|
+
setAiModels(models);
|
|
2527
|
+
setStyleText(style.style?.profile ?? "");
|
|
2528
|
+
setStyleMeta({
|
|
2529
|
+
updatedAt: style.style?.updatedAt ?? "",
|
|
2530
|
+
sampleCount: style.style?.sampleCount ?? 0
|
|
2531
|
+
});
|
|
2532
|
+
const preferred = models.providers.find((item) => item.provider === status.provider) ?? models.providers[0];
|
|
2533
|
+
if (preferred !== void 0 && status.provider === "") {
|
|
2534
|
+
setAiProvider(preferred.provider);
|
|
2535
|
+
const firstModel = preferred.models[0];
|
|
2536
|
+
if (firstModel !== void 0) setAiModel(firstModel.id);
|
|
2537
|
+
}
|
|
2538
|
+
}).catch(() => {
|
|
2539
|
+
});
|
|
2540
|
+
return () => {
|
|
2541
|
+
cancelled = true;
|
|
2542
|
+
};
|
|
2543
|
+
}, [tick, settings?.activeAccountId]);
|
|
1654
2544
|
const reload = () => setTick((value) => value + 1);
|
|
1655
2545
|
const notify = (message) => {
|
|
1656
2546
|
setToast(message);
|
|
@@ -1664,29 +2554,29 @@ function MailSettings() {
|
|
|
1664
2554
|
setSettings(result.settings);
|
|
1665
2555
|
notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
|
|
1666
2556
|
} catch (caught) {
|
|
1667
|
-
setError(
|
|
2557
|
+
setError(errorText6(caught));
|
|
1668
2558
|
}
|
|
1669
2559
|
};
|
|
1670
2560
|
const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
|
|
1671
2561
|
if (state === null || settings === null) {
|
|
1672
|
-
return (0,
|
|
2562
|
+
return (0, import_react9.createElement)("div", { className: "mailharbor mailharbor-settings" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" : error);
|
|
1673
2563
|
}
|
|
1674
|
-
return (0,
|
|
2564
|
+
return (0, import_react9.createElement)(
|
|
1675
2565
|
"div",
|
|
1676
2566
|
{ className: "mailharbor" },
|
|
1677
|
-
(0,
|
|
2567
|
+
(0, import_react9.createElement)(
|
|
1678
2568
|
"div",
|
|
1679
2569
|
{ className: "mailharbor-settings" },
|
|
1680
|
-
(0,
|
|
1681
|
-
(0,
|
|
1682
|
-
toast === "" ? null : (0,
|
|
1683
|
-
error === "" ? null : (0,
|
|
1684
|
-
(0,
|
|
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)(
|
|
1685
2575
|
"div",
|
|
1686
2576
|
{ className: "mailharbor-card" },
|
|
1687
|
-
(0,
|
|
1688
|
-
settings.accounts.length === 0 ? (0,
|
|
1689
|
-
(item) => (0,
|
|
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, {
|
|
1690
2580
|
key: item.id,
|
|
1691
2581
|
account: item,
|
|
1692
2582
|
active: item.id === settings.activeAccountId,
|
|
@@ -1716,7 +2606,7 @@ function MailSettings() {
|
|
|
1716
2606
|
`IMAP \u6B63\u5E38\uFF08${String(result.imap.mailboxes.length)} \u4E2A\u6587\u4EF6\u5939\uFF09\xB7 SMTP ${result.smtp.ok ? "\u6B63\u5E38" : `\u5931\u8D25: ${result.smtp.error ?? ""}`}`
|
|
1717
2607
|
);
|
|
1718
2608
|
} catch (caught) {
|
|
1719
|
-
setCheckResult(`\u5931\u8D25: ${
|
|
2609
|
+
setCheckResult(`\u5931\u8D25: ${errorText6(caught)}`);
|
|
1720
2610
|
}
|
|
1721
2611
|
},
|
|
1722
2612
|
onSave: async (patch) => {
|
|
@@ -1730,32 +2620,32 @@ function MailSettings() {
|
|
|
1730
2620
|
}
|
|
1731
2621
|
})
|
|
1732
2622
|
),
|
|
1733
|
-
adding ? (0,
|
|
2623
|
+
adding ? (0, import_react9.createElement)(AddAccountForm, {
|
|
1734
2624
|
onDone: () => {
|
|
1735
2625
|
setAdding(false);
|
|
1736
2626
|
reload();
|
|
1737
2627
|
},
|
|
1738
2628
|
onCancel: () => setAdding(false)
|
|
1739
|
-
}) : (0,
|
|
2629
|
+
}) : (0, import_react9.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
|
|
1740
2630
|
),
|
|
1741
|
-
(0,
|
|
2631
|
+
(0, import_react9.createElement)(
|
|
1742
2632
|
"div",
|
|
1743
2633
|
{ className: "mailharbor-card" },
|
|
1744
|
-
(0,
|
|
1745
|
-
(0,
|
|
2634
|
+
(0, import_react9.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
|
|
2635
|
+
(0, import_react9.createElement)(
|
|
1746
2636
|
Field,
|
|
1747
2637
|
{ label: "\u7B7E\u540D\uFF08\u7EAF\u6587\u672C\uFF0C\u53D1\u9001\u65F6\u8FFD\u52A0\u5230\u6B63\u6587\u672B\u5C3E\uFF09" },
|
|
1748
|
-
(0,
|
|
2638
|
+
(0, import_react9.createElement)("textarea", {
|
|
1749
2639
|
rows: 4,
|
|
1750
2640
|
value: signature,
|
|
1751
2641
|
onChange: (event) => setSignature(event.target.value)
|
|
1752
2642
|
})
|
|
1753
2643
|
),
|
|
1754
|
-
(0,
|
|
2644
|
+
(0, import_react9.createElement)(
|
|
1755
2645
|
"div",
|
|
1756
2646
|
{ style: { display: "flex", gap: "10px", alignItems: "center", flexWrap: "wrap" } },
|
|
1757
|
-
(0,
|
|
1758
|
-
(0,
|
|
2647
|
+
(0, import_react9.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
|
|
2648
|
+
(0, import_react9.createElement)(Toggle, {
|
|
1759
2649
|
checked: markOnOpen,
|
|
1760
2650
|
onChange: (value) => {
|
|
1761
2651
|
setMarkOnOpen(value);
|
|
@@ -1764,13 +2654,13 @@ function MailSettings() {
|
|
|
1764
2654
|
label: "\u6253\u5F00\u90AE\u4EF6\u65F6\u6807\u4E3A\u5DF2\u8BFB"
|
|
1765
2655
|
})
|
|
1766
2656
|
),
|
|
1767
|
-
(0,
|
|
2657
|
+
(0, import_react9.createElement)(
|
|
1768
2658
|
"div",
|
|
1769
2659
|
{ className: "mailharbor-grid" },
|
|
1770
|
-
(0,
|
|
2660
|
+
(0, import_react9.createElement)(
|
|
1771
2661
|
Field,
|
|
1772
2662
|
{ label: "\u5217\u8868\u6BCF\u9875\u6570\u91CF" },
|
|
1773
|
-
(0,
|
|
2663
|
+
(0, import_react9.createElement)("input", {
|
|
1774
2664
|
type: "number",
|
|
1775
2665
|
min: 10,
|
|
1776
2666
|
max: 200,
|
|
@@ -1779,10 +2669,10 @@ function MailSettings() {
|
|
|
1779
2669
|
onBlur: () => void saveGeneral({ render: { listLimit } })
|
|
1780
2670
|
})
|
|
1781
2671
|
),
|
|
1782
|
-
(0,
|
|
2672
|
+
(0, import_react9.createElement)(
|
|
1783
2673
|
Field,
|
|
1784
2674
|
{ label: "\u672A\u8BFB\u89D2\u6807\u8F6E\u8BE2\uFF08\u79D2\uFF09" },
|
|
1785
|
-
(0,
|
|
2675
|
+
(0, import_react9.createElement)("input", {
|
|
1786
2676
|
type: "number",
|
|
1787
2677
|
min: 15,
|
|
1788
2678
|
max: 3600,
|
|
@@ -1793,66 +2683,171 @@ function MailSettings() {
|
|
|
1793
2683
|
)
|
|
1794
2684
|
)
|
|
1795
2685
|
),
|
|
1796
|
-
(0,
|
|
2686
|
+
(0, import_react9.createElement)(
|
|
1797
2687
|
"div",
|
|
1798
2688
|
{ className: "mailharbor-card" },
|
|
1799
|
-
(0,
|
|
1800
|
-
(0,
|
|
2689
|
+
(0, import_react9.createElement)("h3", null, "AI"),
|
|
2690
|
+
(0, import_react9.createElement)(Toggle, {
|
|
1801
2691
|
checked: aiEnabled,
|
|
1802
2692
|
onChange: (value) => {
|
|
1803
2693
|
setAiEnabled(value);
|
|
1804
2694
|
void saveGeneral({ ai: { enabled: value } });
|
|
1805
2695
|
},
|
|
1806
|
-
label: "\u542F\u7528 AI\uFF08\u6458\u8981\
|
|
2696
|
+
label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001AI \u56DE\u590D\u3001\u7FFB\u8BD1\u3001\u5199\u4F5C\u98CE\u683C\uFF09"
|
|
1807
2697
|
}),
|
|
1808
|
-
(0,
|
|
2698
|
+
aiStatus === null ? null : (0, import_react9.createElement)(
|
|
2699
|
+
"div",
|
|
2700
|
+
{ className: "small", style: { color: aiStatus.available ? "var(--m-ok)" : "var(--m-warn)" } },
|
|
2701
|
+
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"
|
|
2702
|
+
),
|
|
2703
|
+
(0, import_react9.createElement)(
|
|
1809
2704
|
"div",
|
|
1810
2705
|
{ className: "mailharbor-grid" },
|
|
1811
|
-
(0,
|
|
2706
|
+
(0, import_react9.createElement)(
|
|
1812
2707
|
Field,
|
|
1813
2708
|
{ label: "Provider" },
|
|
1814
|
-
(0,
|
|
2709
|
+
aiModels.providers.length === 0 ? (0, import_react9.createElement)("input", {
|
|
1815
2710
|
value: aiProvider,
|
|
1816
2711
|
onChange: (event) => setAiProvider(event.target.value),
|
|
1817
2712
|
onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
|
|
1818
|
-
})
|
|
2713
|
+
}) : (0, import_react9.createElement)(
|
|
2714
|
+
"select",
|
|
2715
|
+
{
|
|
2716
|
+
value: aiProvider,
|
|
2717
|
+
onChange: (event) => {
|
|
2718
|
+
setAiProvider(event.target.value);
|
|
2719
|
+
const provider = aiModels.providers.find((item) => item.provider === event.target.value);
|
|
2720
|
+
const first = provider?.models[0];
|
|
2721
|
+
if (first !== void 0) setAiModel(first.id);
|
|
2722
|
+
void saveGeneral({
|
|
2723
|
+
ai: {
|
|
2724
|
+
provider: event.target.value,
|
|
2725
|
+
...first === void 0 ? {} : { model: first.id }
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
},
|
|
2730
|
+
aiModels.providers.map(
|
|
2731
|
+
(item) => (0, import_react9.createElement)("option", { key: item.provider, value: item.provider }, item.name)
|
|
2732
|
+
)
|
|
2733
|
+
)
|
|
1819
2734
|
),
|
|
1820
|
-
(0,
|
|
2735
|
+
(0, import_react9.createElement)(
|
|
1821
2736
|
Field,
|
|
1822
2737
|
{ label: "\u6A21\u578B" },
|
|
1823
|
-
(
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2738
|
+
(() => {
|
|
2739
|
+
const provider = aiModels.providers.find((item) => item.provider === aiProvider);
|
|
2740
|
+
return provider === void 0 || provider.models.length === 0 ? (0, import_react9.createElement)("input", {
|
|
2741
|
+
value: aiModel,
|
|
2742
|
+
onChange: (event) => setAiModel(event.target.value),
|
|
2743
|
+
onBlur: () => void saveGeneral({ ai: { model: aiModel } })
|
|
2744
|
+
}) : (0, import_react9.createElement)(
|
|
2745
|
+
"select",
|
|
2746
|
+
{
|
|
2747
|
+
value: aiModel,
|
|
2748
|
+
onChange: (event) => {
|
|
2749
|
+
setAiModel(event.target.value);
|
|
2750
|
+
void saveGeneral({ ai: { model: event.target.value } });
|
|
2751
|
+
}
|
|
2752
|
+
},
|
|
2753
|
+
provider.models.map((item) => (0, import_react9.createElement)("option", { key: item.id, value: item.id }, item.name))
|
|
2754
|
+
);
|
|
2755
|
+
})()
|
|
1828
2756
|
)
|
|
1829
2757
|
),
|
|
1830
|
-
(0,
|
|
2758
|
+
(0, import_react9.createElement)("hr", null),
|
|
2759
|
+
(0, import_react9.createElement)(
|
|
2760
|
+
"div",
|
|
2761
|
+
{ style: { display: "flex", alignItems: "center", gap: "8px" } },
|
|
2762
|
+
(0, import_react9.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
|
|
2763
|
+
(0, import_react9.createElement)(
|
|
2764
|
+
"span",
|
|
2765
|
+
{ className: "small muted" },
|
|
2766
|
+
styleMeta.updatedAt === "" ? "\u8FD8\u6CA1\u6709\u751F\u6210" : `${styleMeta.sampleCount} \u5C01\u5DF2\u53D1\u9001\u90AE\u4EF6 \xB7 ${new Date(styleMeta.updatedAt).toLocaleString()}`
|
|
2767
|
+
)
|
|
2768
|
+
),
|
|
2769
|
+
(0, import_react9.createElement)(
|
|
2770
|
+
"p",
|
|
2771
|
+
{ className: "small muted" },
|
|
2772
|
+
"\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"
|
|
2773
|
+
),
|
|
2774
|
+
(0, import_react9.createElement)("textarea", {
|
|
2775
|
+
rows: 6,
|
|
2776
|
+
value: styleText,
|
|
2777
|
+
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",
|
|
2778
|
+
onChange: (event) => setStyleText(event.target.value)
|
|
2779
|
+
}),
|
|
2780
|
+
(0, import_react9.createElement)(
|
|
2781
|
+
"div",
|
|
2782
|
+
{ style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
|
|
2783
|
+
(0, import_react9.createElement)(
|
|
2784
|
+
"button",
|
|
2785
|
+
{
|
|
2786
|
+
className: "ghost",
|
|
2787
|
+
disabled: styleBusy !== null,
|
|
2788
|
+
onClick: () => {
|
|
2789
|
+
const accountId = settings.activeAccountId;
|
|
2790
|
+
if (accountId === null) return;
|
|
2791
|
+
setStyleBusy("learn");
|
|
2792
|
+
setError("");
|
|
2793
|
+
api.aiLearnStyle(accountId).then((result) => {
|
|
2794
|
+
setStyleText(result.style.profile);
|
|
2795
|
+
setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
|
|
2796
|
+
notify("\u5DF2\u6839\u636E\u5DF2\u53D1\u9001\u90AE\u4EF6\u751F\u6210\u5199\u4F5C\u98CE\u683C");
|
|
2797
|
+
}).catch((caught) => setError(errorText6(caught))).finally(() => setStyleBusy(null));
|
|
2798
|
+
}
|
|
2799
|
+
},
|
|
2800
|
+
styleBusy === "learn" ? "\u5B66\u4E60\u4E2D\u2026" : "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60"
|
|
2801
|
+
),
|
|
2802
|
+
(0, import_react9.createElement)(
|
|
2803
|
+
"button",
|
|
2804
|
+
{
|
|
2805
|
+
className: "primary",
|
|
2806
|
+
disabled: styleBusy !== null || styleText.trim() === "",
|
|
2807
|
+
onClick: () => {
|
|
2808
|
+
const accountId = settings.activeAccountId;
|
|
2809
|
+
if (accountId === null) return;
|
|
2810
|
+
setStyleBusy("save");
|
|
2811
|
+
setError("");
|
|
2812
|
+
api.aiSaveStyle(accountId, styleText).then((result) => {
|
|
2813
|
+
setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
|
|
2814
|
+
notify("\u5199\u4F5C\u98CE\u683C\u5DF2\u4FDD\u5B58");
|
|
2815
|
+
}).catch((caught) => setError(errorText6(caught))).finally(() => setStyleBusy(null));
|
|
2816
|
+
}
|
|
2817
|
+
},
|
|
2818
|
+
"\u4FDD\u5B58\u98CE\u683C"
|
|
2819
|
+
)
|
|
2820
|
+
),
|
|
2821
|
+
(0, import_react9.createElement)(
|
|
1831
2822
|
"p",
|
|
1832
2823
|
{ className: "small muted" },
|
|
1833
|
-
"AI \u8C03\u7528\u8D70 DSH \u81EA\u5DF1\u7684\u6A21\u578B\u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4FDD\u5B58\u5BC6\u94A5\
|
|
2824
|
+
"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"
|
|
1834
2825
|
)
|
|
1835
2826
|
),
|
|
1836
|
-
(0,
|
|
2827
|
+
(0, import_react9.createElement)(
|
|
1837
2828
|
"div",
|
|
1838
2829
|
{ className: "mailharbor-card" },
|
|
1839
|
-
(0,
|
|
1840
|
-
(0,
|
|
2830
|
+
(0, import_react9.createElement)("h3", null, "\u6570\u636E"),
|
|
2831
|
+
(0, import_react9.createElement)(
|
|
1841
2832
|
"dl",
|
|
1842
2833
|
{ className: "mailharbor-kv" },
|
|
1843
|
-
(0,
|
|
1844
|
-
(0,
|
|
1845
|
-
(0,
|
|
1846
|
-
(0,
|
|
1847
|
-
(0,
|
|
1848
|
-
(0,
|
|
1849
|
-
|
|
1850
|
-
|
|
2834
|
+
(0, import_react9.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
|
|
2835
|
+
(0, import_react9.createElement)("dd", null, settings.dataDir),
|
|
2836
|
+
(0, import_react9.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
|
|
2837
|
+
(0, import_react9.createElement)("dd", null, settings.configFile),
|
|
2838
|
+
(0, import_react9.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
|
|
2839
|
+
(0, import_react9.createElement)(
|
|
2840
|
+
"dd",
|
|
2841
|
+
null,
|
|
2842
|
+
account === null ? "\u2014" : `${account.dataDir.startsWith("/") ? account.dataDir : `${settings.dataDir}/${account.dataDir}`}/mail.db`
|
|
2843
|
+
),
|
|
2844
|
+
(0, import_react9.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
|
|
2845
|
+
(0, import_react9.createElement)("dd", null, state.version)
|
|
1851
2846
|
),
|
|
1852
|
-
(0,
|
|
2847
|
+
(0, import_react9.createElement)(
|
|
1853
2848
|
"div",
|
|
1854
2849
|
{ style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
|
|
1855
|
-
account === null ? null : (0,
|
|
2850
|
+
account === null ? null : (0, import_react9.createElement)(
|
|
1856
2851
|
"button",
|
|
1857
2852
|
{
|
|
1858
2853
|
className: "ghost",
|
|
@@ -1862,7 +2857,7 @@ function MailSettings() {
|
|
|
1862
2857
|
notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
|
|
1863
2858
|
reload();
|
|
1864
2859
|
} catch (caught) {
|
|
1865
|
-
setError(
|
|
2860
|
+
setError(errorText6(caught));
|
|
1866
2861
|
}
|
|
1867
2862
|
}
|
|
1868
2863
|
},
|
|
@@ -1887,19 +2882,19 @@ function AccountRow({
|
|
|
1887
2882
|
onSave,
|
|
1888
2883
|
onRemove
|
|
1889
2884
|
}) {
|
|
1890
|
-
const [password, setPassword] = (0,
|
|
1891
|
-
const [remember, setRemember] = (0,
|
|
1892
|
-
const [label, setLabel] = (0,
|
|
1893
|
-
const [username, setUsername] = (0,
|
|
1894
|
-
const [imapHost, setImapHost] = (0,
|
|
1895
|
-
const [imapPort, setImapPort] = (0,
|
|
1896
|
-
const [imapSecure, setImapSecure] = (0,
|
|
1897
|
-
const [smtpHost, setSmtpHost] = (0,
|
|
1898
|
-
const [smtpPort, setSmtpPort] = (0,
|
|
1899
|
-
const [smtpSecure, setSmtpSecure] = (0,
|
|
1900
|
-
const [fromName, setFromName] = (0,
|
|
1901
|
-
const [busy, setBusy] = (0,
|
|
1902
|
-
(0,
|
|
2885
|
+
const [password, setPassword] = (0, import_react9.useState)("");
|
|
2886
|
+
const [remember, setRemember] = (0, import_react9.useState)(true);
|
|
2887
|
+
const [label, setLabel] = (0, import_react9.useState)(account.label);
|
|
2888
|
+
const [username, setUsername] = (0, import_react9.useState)(account.username);
|
|
2889
|
+
const [imapHost, setImapHost] = (0, import_react9.useState)(account.imap.host);
|
|
2890
|
+
const [imapPort, setImapPort] = (0, import_react9.useState)(String(account.imap.port));
|
|
2891
|
+
const [imapSecure, setImapSecure] = (0, import_react9.useState)(account.imap.secure);
|
|
2892
|
+
const [smtpHost, setSmtpHost] = (0, import_react9.useState)(account.smtp.host);
|
|
2893
|
+
const [smtpPort, setSmtpPort] = (0, import_react9.useState)(String(account.smtp.port));
|
|
2894
|
+
const [smtpSecure, setSmtpSecure] = (0, import_react9.useState)(account.smtp.secure);
|
|
2895
|
+
const [fromName, setFromName] = (0, import_react9.useState)(account.smtp.fromName);
|
|
2896
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
2897
|
+
(0, import_react9.useEffect)(() => {
|
|
1903
2898
|
setLabel(account.label);
|
|
1904
2899
|
setUsername(account.username);
|
|
1905
2900
|
setImapHost(account.imap.host);
|
|
@@ -1918,103 +2913,103 @@ function AccountRow({
|
|
|
1918
2913
|
setBusy(false);
|
|
1919
2914
|
}
|
|
1920
2915
|
};
|
|
1921
|
-
return (0,
|
|
2916
|
+
return (0, import_react9.createElement)(
|
|
1922
2917
|
"div",
|
|
1923
2918
|
{ className: "mailharbor-card", style: { background: "var(--m-bg-1)" } },
|
|
1924
|
-
(0,
|
|
2919
|
+
(0, import_react9.createElement)(
|
|
1925
2920
|
"div",
|
|
1926
2921
|
{ className: "mailharbor-account-row" },
|
|
1927
|
-
(0,
|
|
1928
|
-
(0,
|
|
2922
|
+
(0, import_react9.createElement)(Avatar, { email: account.email, size: 30 }),
|
|
2923
|
+
(0, import_react9.createElement)(
|
|
1929
2924
|
"div",
|
|
1930
2925
|
{ style: { display: "flex", flexDirection: "column", minWidth: 0 } },
|
|
1931
|
-
(0,
|
|
1932
|
-
(0,
|
|
2926
|
+
(0, import_react9.createElement)("strong", null, account.label),
|
|
2927
|
+
(0, import_react9.createElement)("span", { className: "small muted" }, account.email)
|
|
1933
2928
|
),
|
|
1934
|
-
active ? (0,
|
|
1935
|
-
loggedIn === false ? (0,
|
|
1936
|
-
(0,
|
|
1937
|
-
active ? null : (0,
|
|
1938
|
-
(0,
|
|
1939
|
-
(0,
|
|
2929
|
+
active ? (0, import_react9.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
|
|
2930
|
+
loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
|
|
2931
|
+
(0, import_react9.createElement)("span", { className: "spacer" }),
|
|
2932
|
+
active ? null : (0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
|
|
2933
|
+
(0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onCheck) }, "\u68C0\u67E5\u8FDE\u63A5"),
|
|
2934
|
+
(0, import_react9.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
|
|
1940
2935
|
),
|
|
1941
|
-
editing ? (0,
|
|
2936
|
+
editing ? (0, import_react9.createElement)(
|
|
1942
2937
|
"div",
|
|
1943
2938
|
{ style: { display: "flex", flexDirection: "column", gap: "10px" } },
|
|
1944
|
-
(0,
|
|
2939
|
+
(0, import_react9.createElement)(
|
|
1945
2940
|
"div",
|
|
1946
2941
|
{ className: "mailharbor-grid" },
|
|
1947
|
-
(0,
|
|
2942
|
+
(0, import_react9.createElement)(
|
|
1948
2943
|
Field,
|
|
1949
2944
|
{ label: "\u663E\u793A\u540D\u79F0" },
|
|
1950
|
-
(0,
|
|
2945
|
+
(0, import_react9.createElement)("input", {
|
|
1951
2946
|
value: label,
|
|
1952
2947
|
onChange: (event) => setLabel(event.target.value)
|
|
1953
2948
|
})
|
|
1954
2949
|
),
|
|
1955
|
-
(0,
|
|
2950
|
+
(0, import_react9.createElement)(
|
|
1956
2951
|
Field,
|
|
1957
2952
|
{ label: "\u767B\u5F55\u7528\u6237\u540D" },
|
|
1958
|
-
(0,
|
|
2953
|
+
(0, import_react9.createElement)("input", {
|
|
1959
2954
|
value: username,
|
|
1960
2955
|
onChange: (event) => setUsername(event.target.value)
|
|
1961
2956
|
})
|
|
1962
2957
|
)
|
|
1963
2958
|
),
|
|
1964
|
-
(0,
|
|
2959
|
+
(0, import_react9.createElement)(
|
|
1965
2960
|
"div",
|
|
1966
2961
|
{ className: "mailharbor-grid three" },
|
|
1967
|
-
(0,
|
|
2962
|
+
(0, import_react9.createElement)(
|
|
1968
2963
|
Field,
|
|
1969
2964
|
{ label: "IMAP \u4E3B\u673A" },
|
|
1970
|
-
(0,
|
|
2965
|
+
(0, import_react9.createElement)("input", {
|
|
1971
2966
|
value: imapHost,
|
|
1972
2967
|
onChange: (event) => setImapHost(event.target.value)
|
|
1973
2968
|
})
|
|
1974
2969
|
),
|
|
1975
|
-
(0,
|
|
2970
|
+
(0, import_react9.createElement)(
|
|
1976
2971
|
Field,
|
|
1977
2972
|
{ label: "\u7AEF\u53E3" },
|
|
1978
|
-
(0,
|
|
2973
|
+
(0, import_react9.createElement)("input", {
|
|
1979
2974
|
value: imapPort,
|
|
1980
2975
|
onChange: (event) => setImapPort(event.target.value)
|
|
1981
2976
|
})
|
|
1982
2977
|
),
|
|
1983
|
-
(0,
|
|
2978
|
+
(0, import_react9.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
|
|
1984
2979
|
),
|
|
1985
|
-
(0,
|
|
2980
|
+
(0, import_react9.createElement)(
|
|
1986
2981
|
"div",
|
|
1987
2982
|
{ className: "mailharbor-grid three" },
|
|
1988
|
-
(0,
|
|
2983
|
+
(0, import_react9.createElement)(
|
|
1989
2984
|
Field,
|
|
1990
2985
|
{ label: "SMTP \u4E3B\u673A" },
|
|
1991
|
-
(0,
|
|
2986
|
+
(0, import_react9.createElement)("input", {
|
|
1992
2987
|
value: smtpHost,
|
|
1993
2988
|
onChange: (event) => setSmtpHost(event.target.value)
|
|
1994
2989
|
})
|
|
1995
2990
|
),
|
|
1996
|
-
(0,
|
|
2991
|
+
(0, import_react9.createElement)(
|
|
1997
2992
|
Field,
|
|
1998
2993
|
{ label: "\u7AEF\u53E3" },
|
|
1999
|
-
(0,
|
|
2994
|
+
(0, import_react9.createElement)("input", {
|
|
2000
2995
|
value: smtpPort,
|
|
2001
2996
|
onChange: (event) => setSmtpPort(event.target.value)
|
|
2002
2997
|
})
|
|
2003
2998
|
),
|
|
2004
|
-
(0,
|
|
2999
|
+
(0, import_react9.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
|
|
2005
3000
|
),
|
|
2006
|
-
(0,
|
|
3001
|
+
(0, import_react9.createElement)(
|
|
2007
3002
|
Field,
|
|
2008
3003
|
{ label: "\u53D1\u4EF6\u4EBA\u540D\u79F0" },
|
|
2009
|
-
(0,
|
|
3004
|
+
(0, import_react9.createElement)("input", {
|
|
2010
3005
|
value: fromName,
|
|
2011
3006
|
onChange: (event) => setFromName(event.target.value)
|
|
2012
3007
|
})
|
|
2013
3008
|
),
|
|
2014
|
-
(0,
|
|
3009
|
+
(0, import_react9.createElement)(
|
|
2015
3010
|
"div",
|
|
2016
3011
|
{ style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
|
|
2017
|
-
(0,
|
|
3012
|
+
(0, import_react9.createElement)(
|
|
2018
3013
|
"button",
|
|
2019
3014
|
{
|
|
2020
3015
|
className: "primary small",
|
|
@@ -2030,7 +3025,7 @@ function AccountRow({
|
|
|
2030
3025
|
},
|
|
2031
3026
|
"\u4FDD\u5B58\u8D26\u53F7"
|
|
2032
3027
|
),
|
|
2033
|
-
(0,
|
|
3028
|
+
(0, import_react9.createElement)(
|
|
2034
3029
|
"button",
|
|
2035
3030
|
{
|
|
2036
3031
|
className: "ghost small danger",
|
|
@@ -2039,21 +3034,21 @@ function AccountRow({
|
|
|
2039
3034
|
"\u79FB\u9664\u8D26\u53F7"
|
|
2040
3035
|
)
|
|
2041
3036
|
),
|
|
2042
|
-
(0,
|
|
2043
|
-
(0,
|
|
3037
|
+
(0, import_react9.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
|
|
3038
|
+
(0, import_react9.createElement)(
|
|
2044
3039
|
Field,
|
|
2045
3040
|
{ label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801\uFF08\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09" },
|
|
2046
|
-
(0,
|
|
3041
|
+
(0, import_react9.createElement)("input", {
|
|
2047
3042
|
type: "password",
|
|
2048
3043
|
value: password,
|
|
2049
3044
|
onChange: (event) => setPassword(event.target.value)
|
|
2050
3045
|
})
|
|
2051
3046
|
),
|
|
2052
|
-
(0,
|
|
3047
|
+
(0, import_react9.createElement)(
|
|
2053
3048
|
"div",
|
|
2054
3049
|
{ style: { display: "flex", gap: "8px", alignItems: "center" } },
|
|
2055
|
-
(0,
|
|
2056
|
-
(0,
|
|
3050
|
+
(0, import_react9.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
|
|
3051
|
+
(0, import_react9.createElement)(
|
|
2057
3052
|
"button",
|
|
2058
3053
|
{
|
|
2059
3054
|
className: "primary small",
|
|
@@ -2065,15 +3060,15 @@ function AccountRow({
|
|
|
2065
3060
|
},
|
|
2066
3061
|
"\u767B\u5F55"
|
|
2067
3062
|
),
|
|
2068
|
-
(0,
|
|
3063
|
+
(0, import_react9.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
|
|
2069
3064
|
),
|
|
2070
|
-
checkResult === "" ? null : (0,
|
|
3065
|
+
checkResult === "" ? null : (0, import_react9.createElement)("div", { className: "small muted" }, checkResult)
|
|
2071
3066
|
) : null
|
|
2072
3067
|
);
|
|
2073
3068
|
}
|
|
2074
3069
|
|
|
2075
3070
|
// src/client/index.tsx
|
|
2076
|
-
var VERSION = "0.
|
|
3071
|
+
var VERSION = "0.4.0";
|
|
2077
3072
|
var inject = ["slots"];
|
|
2078
3073
|
var PANEL_ID = "mailharbor";
|
|
2079
3074
|
var STYLE_ID = "mailharbor-style";
|