@zaclinley/dsh-mailharbor 0.2.1 → 0.3.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 +15 -0
- package/README.md +38 -21
- package/README.zh.md +31 -15
- package/lib/client.js +649 -80
- package/lib/client.js.map +3 -3
- package/lib/index.js +519 -64
- 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}\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 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/* ---------- 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,29 @@ 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
|
+
aiSummary: (payload) => request("ai/summary", {
|
|
129
|
+
method: "POST",
|
|
130
|
+
body: JSON.stringify(payload)
|
|
131
|
+
}),
|
|
132
|
+
aiReply: (payload) => request("ai/reply", {
|
|
133
|
+
method: "POST",
|
|
134
|
+
body: JSON.stringify(payload)
|
|
135
|
+
}),
|
|
136
|
+
aiTranslate: (payload) => request("ai/translate", {
|
|
137
|
+
method: "POST",
|
|
138
|
+
body: JSON.stringify(payload)
|
|
139
|
+
}),
|
|
140
|
+
aiStyle: (accountId) => request(`ai/style${query({ accountId: accountId ?? null })}`),
|
|
141
|
+
aiSaveStyle: (accountId, profile) => request(`ai/style${query({ accountId: accountId ?? null })}`, {
|
|
142
|
+
method: "PUT",
|
|
143
|
+
body: JSON.stringify({ accountId, profile })
|
|
144
|
+
}),
|
|
145
|
+
aiLearnStyle: (accountId) => request(`ai/style/learn${query({ accountId: accountId ?? null })}`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: JSON.stringify({ accountId: accountId ?? null })
|
|
148
|
+
}),
|
|
126
149
|
settings: () => request("settings"),
|
|
127
150
|
saveSettings: (patch) => request("settings", { method: "PATCH", body: JSON.stringify(patch) }),
|
|
128
151
|
backupStatus: (accountId) => request(`backup/status${query({ accountId: accountId ?? null })}`),
|
|
@@ -637,6 +660,8 @@ function Composer({
|
|
|
637
660
|
const [error, setError] = (0, import_react4.useState)("");
|
|
638
661
|
const [confirming, setConfirming] = (0, import_react4.useState)(false);
|
|
639
662
|
const [draftId, setDraftId] = (0, import_react4.useState)(initial.draftId);
|
|
663
|
+
const [translating, setTranslating] = (0, import_react4.useState)(null);
|
|
664
|
+
const [pendingLang, setPendingLang] = (0, import_react4.useState)(null);
|
|
640
665
|
const editor = (0, import_react4.useRef)(null);
|
|
641
666
|
(0, import_react4.useEffect)(() => {
|
|
642
667
|
if (editor.current !== null) editor.current.innerHTML = initial.html;
|
|
@@ -647,6 +672,24 @@ function Composer({
|
|
|
647
672
|
editor.current?.focus();
|
|
648
673
|
document.execCommand(command, false, value);
|
|
649
674
|
};
|
|
675
|
+
const translate = async (target) => {
|
|
676
|
+
if (editor.current === null) return;
|
|
677
|
+
if (html().trim() === "") {
|
|
678
|
+
setError("\u6B63\u6587\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u7FFB\u8BD1");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
setPendingLang(null);
|
|
682
|
+
setTranslating(target);
|
|
683
|
+
setError("");
|
|
684
|
+
try {
|
|
685
|
+
const result = await api.aiTranslate({ accountId: account.id, html: html(), target });
|
|
686
|
+
editor.current.innerHTML = result.html;
|
|
687
|
+
} catch (caught) {
|
|
688
|
+
setError(errorText2(caught));
|
|
689
|
+
} finally {
|
|
690
|
+
setTranslating(null);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
650
693
|
const addFiles = async (files) => {
|
|
651
694
|
if (files === null || files.length === 0) return;
|
|
652
695
|
setError("");
|
|
@@ -817,6 +860,16 @@ function Composer({
|
|
|
817
860
|
),
|
|
818
861
|
(0, import_react4.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
|
|
819
862
|
(0, import_react4.createElement)("span", { className: "spacer" }),
|
|
863
|
+
(0, import_react4.createElement)(
|
|
864
|
+
"button",
|
|
865
|
+
{ onClick: () => setPendingLang("zh"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u4E2D\u6587" },
|
|
866
|
+
translating === "zh" ? "\u7FFB\u8BD1\u4E2D\u2026" : "\u4E2D"
|
|
867
|
+
),
|
|
868
|
+
(0, import_react4.createElement)(
|
|
869
|
+
"button",
|
|
870
|
+
{ onClick: () => setPendingLang("en"), disabled: translating !== null, title: "AI \u7FFB\u8BD1\u6210\u82F1\u6587" },
|
|
871
|
+
translating === "en" ? "\u2026" : "EN"
|
|
872
|
+
),
|
|
820
873
|
(0, import_react4.createElement)(
|
|
821
874
|
"label",
|
|
822
875
|
{ className: "mailharbor-toggle small" },
|
|
@@ -832,6 +885,14 @@ function Composer({
|
|
|
832
885
|
})
|
|
833
886
|
)
|
|
834
887
|
),
|
|
888
|
+
pendingLang === null ? null : (0, import_react4.createElement)(
|
|
889
|
+
"div",
|
|
890
|
+
{ className: "mailharbor-confirm" },
|
|
891
|
+
(0, import_react4.createElement)("span", null, pendingLang === "en" ? "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u82F1\u6587\uFF1F" : "\u628A\u6B63\u6587\u7FFB\u8BD1\u6210\u4E2D\u6587\uFF1F"),
|
|
892
|
+
(0, import_react4.createElement)("span", { className: "spacer" }),
|
|
893
|
+
(0, import_react4.createElement)("button", { className: "ghost", onClick: () => setPendingLang(null) }, "\u53D6\u6D88"),
|
|
894
|
+
(0, import_react4.createElement)("button", { className: "primary", onClick: () => void translate(pendingLang) }, "\u7FFB\u8BD1")
|
|
895
|
+
),
|
|
835
896
|
attachments.length === 0 ? null : (0, import_react4.createElement)(
|
|
836
897
|
"div",
|
|
837
898
|
{ className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
|
|
@@ -950,25 +1011,43 @@ function srcDoc(detail, allowRemoteImages) {
|
|
|
950
1011
|
function escapeHtml(value) {
|
|
951
1012
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
952
1013
|
}
|
|
1014
|
+
function textToHtml(text) {
|
|
1015
|
+
return text.split(/\n{2,}/).map(
|
|
1016
|
+
(paragraph) => `<p>${paragraph.trim().replaceAll("&", "&").replaceAll("<", "<").replaceAll("\n", "<br>")}</p>`
|
|
1017
|
+
).join("");
|
|
1018
|
+
}
|
|
953
1019
|
function hasRemoteImages(detail) {
|
|
954
1020
|
return detail.html !== null && /<img[^>]+src=["']https?:/i.test(detail.html);
|
|
955
1021
|
}
|
|
956
1022
|
function addressLine(list) {
|
|
957
1023
|
return list.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join("\u3001");
|
|
958
1024
|
}
|
|
1025
|
+
function errorText3(error) {
|
|
1026
|
+
return error instanceof Error ? error.message : String(error);
|
|
1027
|
+
}
|
|
959
1028
|
function Reader({
|
|
960
1029
|
detail,
|
|
961
|
-
account,
|
|
962
1030
|
source,
|
|
963
1031
|
mailbox,
|
|
964
1032
|
onChanged,
|
|
965
1033
|
onReply,
|
|
1034
|
+
onAiReply,
|
|
966
1035
|
onClose
|
|
967
1036
|
}) {
|
|
968
1037
|
const [plain, setPlain] = (0, import_react6.useState)(false);
|
|
969
1038
|
const [loadImages, setLoadImages] = (0, import_react6.useState)(false);
|
|
970
1039
|
const [busy, setBusy] = (0, import_react6.useState)(false);
|
|
971
1040
|
const [error, setError] = (0, import_react6.useState)("");
|
|
1041
|
+
const [summaryOpen, setSummaryOpen] = (0, import_react6.useState)(false);
|
|
1042
|
+
const [summaryBusy, setSummaryBusy] = (0, import_react6.useState)(false);
|
|
1043
|
+
const [summaryError, setSummaryError] = (0, import_react6.useState)("");
|
|
1044
|
+
const [summaryBlocks, setSummaryBlocks] = (0, import_react6.useState)([]);
|
|
1045
|
+
const [replyOpen, setReplyOpen] = (0, import_react6.useState)(false);
|
|
1046
|
+
const [replyBusy, setReplyBusy] = (0, import_react6.useState)(false);
|
|
1047
|
+
const [replyError, setReplyError] = (0, import_react6.useState)("");
|
|
1048
|
+
const [replyInstruction, setReplyInstruction] = (0, import_react6.useState)("");
|
|
1049
|
+
const [replyLanguage, setReplyLanguage] = (0, import_react6.useState)("zh");
|
|
1050
|
+
const [replyText, setReplyText] = (0, import_react6.useState)("");
|
|
972
1051
|
const remoteImages = (0, import_react6.useMemo)(() => hasRemoteImages(detail), [detail]);
|
|
973
1052
|
const html = detail.html !== null && detail.html !== "";
|
|
974
1053
|
const toggleRead = async () => {
|
|
@@ -977,18 +1056,74 @@ function Reader({
|
|
|
977
1056
|
try {
|
|
978
1057
|
const result = await api.setRead({
|
|
979
1058
|
id: detail.id,
|
|
980
|
-
accountId:
|
|
1059
|
+
accountId: detail.accountId,
|
|
981
1060
|
source,
|
|
982
1061
|
mailbox,
|
|
983
1062
|
read: detail.unread
|
|
984
1063
|
});
|
|
985
1064
|
onChanged({ unread: !result.read });
|
|
986
1065
|
} catch (caught) {
|
|
987
|
-
setError(
|
|
1066
|
+
setError(errorText3(caught));
|
|
988
1067
|
} finally {
|
|
989
1068
|
setBusy(false);
|
|
990
1069
|
}
|
|
991
1070
|
};
|
|
1071
|
+
const openSummary = async () => {
|
|
1072
|
+
setSummaryOpen(true);
|
|
1073
|
+
setSummaryBusy(true);
|
|
1074
|
+
setSummaryError("");
|
|
1075
|
+
setSummaryBlocks([]);
|
|
1076
|
+
try {
|
|
1077
|
+
const result = await api.aiSummary({
|
|
1078
|
+
accountId: detail.accountId,
|
|
1079
|
+
source,
|
|
1080
|
+
id: detail.id,
|
|
1081
|
+
mailbox
|
|
1082
|
+
});
|
|
1083
|
+
setSummaryBlocks(result.blocks);
|
|
1084
|
+
} catch (caught) {
|
|
1085
|
+
setSummaryError(errorText3(caught));
|
|
1086
|
+
} finally {
|
|
1087
|
+
setSummaryBusy(false);
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
const openReply = () => {
|
|
1091
|
+
setReplyOpen(true);
|
|
1092
|
+
setReplyText("");
|
|
1093
|
+
setReplyError("");
|
|
1094
|
+
setReplyInstruction("");
|
|
1095
|
+
setReplyLanguage("zh");
|
|
1096
|
+
};
|
|
1097
|
+
const generateReply = async () => {
|
|
1098
|
+
setReplyBusy(true);
|
|
1099
|
+
setReplyError("");
|
|
1100
|
+
try {
|
|
1101
|
+
const result = await api.aiReply({
|
|
1102
|
+
accountId: detail.accountId,
|
|
1103
|
+
source,
|
|
1104
|
+
id: detail.id,
|
|
1105
|
+
mailbox,
|
|
1106
|
+
instruction: replyInstruction,
|
|
1107
|
+
language: replyLanguage
|
|
1108
|
+
});
|
|
1109
|
+
setReplyText(result.text);
|
|
1110
|
+
} catch (caught) {
|
|
1111
|
+
setReplyError(errorText3(caught));
|
|
1112
|
+
} finally {
|
|
1113
|
+
setReplyBusy(false);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
const insertReply = () => {
|
|
1117
|
+
const subject = /^re:/i.test(detail.subject) ? detail.subject : `Re: ${detail.subject}`;
|
|
1118
|
+
onAiReply({
|
|
1119
|
+
to: detail.from.address,
|
|
1120
|
+
cc: "",
|
|
1121
|
+
subject,
|
|
1122
|
+
html: textToHtml(replyText),
|
|
1123
|
+
...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
|
|
1124
|
+
});
|
|
1125
|
+
setReplyOpen(false);
|
|
1126
|
+
};
|
|
992
1127
|
return (0, import_react6.createElement)(
|
|
993
1128
|
"div",
|
|
994
1129
|
{ className: "mailharbor-reader" },
|
|
@@ -1056,6 +1191,9 @@ function Reader({
|
|
|
1056
1191
|
(0, import_react6.createElement)(Icon, { name: "forward" }),
|
|
1057
1192
|
"\u8F6C\u53D1"
|
|
1058
1193
|
),
|
|
1194
|
+
(0, import_react6.createElement)("span", { className: "mailharbor-divider" }),
|
|
1195
|
+
(0, import_react6.createElement)("button", { className: "ghost", onClick: () => void openSummary() }, "AI \u6458\u8981"),
|
|
1196
|
+
(0, import_react6.createElement)("button", { className: "ghost", onClick: openReply }, "AI \u56DE\u590D"),
|
|
1059
1197
|
(0, import_react6.createElement)("span", { className: "spacer" }),
|
|
1060
1198
|
html ? (0, import_react6.createElement)(
|
|
1061
1199
|
"button",
|
|
@@ -1091,13 +1229,13 @@ function Reader({
|
|
|
1091
1229
|
className: "mailharbor-attachment",
|
|
1092
1230
|
key: file.id,
|
|
1093
1231
|
href: source === "server" ? api.attachmentUrl({
|
|
1094
|
-
accountId:
|
|
1232
|
+
accountId: detail.accountId,
|
|
1095
1233
|
source: "server",
|
|
1096
1234
|
mailbox,
|
|
1097
1235
|
uid: detail.uid ?? "",
|
|
1098
1236
|
part: file.id
|
|
1099
1237
|
}) : api.attachmentUrl({
|
|
1100
|
-
accountId:
|
|
1238
|
+
accountId: detail.accountId,
|
|
1101
1239
|
source: "local",
|
|
1102
1240
|
emailId: detail.id,
|
|
1103
1241
|
attachmentId: file.id
|
|
@@ -1110,13 +1248,127 @@ function Reader({
|
|
|
1110
1248
|
(0, import_react6.createElement)("span", { className: "muted small" }, formatBytes(file.size))
|
|
1111
1249
|
)
|
|
1112
1250
|
)
|
|
1113
|
-
)
|
|
1251
|
+
),
|
|
1252
|
+
summaryOpen ? (0, import_react6.createElement)(
|
|
1253
|
+
Modal,
|
|
1254
|
+
{
|
|
1255
|
+
title: "AI \u6458\u8981",
|
|
1256
|
+
onClose: () => setSummaryOpen(false),
|
|
1257
|
+
footer: (0, import_react6.createElement)(
|
|
1258
|
+
"div",
|
|
1259
|
+
{ style: { display: "flex", gap: "8px", width: "100%", justifyContent: "flex-end" } },
|
|
1260
|
+
(0, import_react6.createElement)("button", { className: "ghost", onClick: () => setSummaryOpen(false) }, "\u5173\u95ED"),
|
|
1261
|
+
summaryBlocks.length === 0 ? null : (0, import_react6.createElement)(
|
|
1262
|
+
"button",
|
|
1263
|
+
{
|
|
1264
|
+
className: "primary",
|
|
1265
|
+
onClick: () => {
|
|
1266
|
+
void navigator.clipboard?.writeText(
|
|
1267
|
+
summaryBlocks.map((block) => `${block.title}
|
|
1268
|
+
${block.body}`).join("\n\n")
|
|
1269
|
+
).then(() => setSummaryOpen(false));
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
"\u590D\u5236"
|
|
1273
|
+
)
|
|
1274
|
+
)
|
|
1275
|
+
},
|
|
1276
|
+
summaryBusy ? "\u6B63\u5728\u9605\u8BFB\u5E76\u603B\u7ED3\u2026" : null,
|
|
1277
|
+
summaryError === "" ? null : (0, import_react6.createElement)("div", { style: { color: "var(--m-danger)" } }, summaryError),
|
|
1278
|
+
summaryBlocks.length === 0 ? null : (0, import_react6.createElement)(
|
|
1279
|
+
"div",
|
|
1280
|
+
{ className: "mailharbor-summary" },
|
|
1281
|
+
summaryBlocks.map(
|
|
1282
|
+
(block, index) => (0, import_react6.createElement)(
|
|
1283
|
+
"section",
|
|
1284
|
+
{ key: `${block.title}:${String(index)}` },
|
|
1285
|
+
block.title === "" ? null : (0, import_react6.createElement)("h4", null, block.title),
|
|
1286
|
+
(0, import_react6.createElement)("p", null, block.body)
|
|
1287
|
+
)
|
|
1288
|
+
)
|
|
1289
|
+
)
|
|
1290
|
+
) : null,
|
|
1291
|
+
replyOpen ? (0, import_react6.createElement)(
|
|
1292
|
+
Modal,
|
|
1293
|
+
{
|
|
1294
|
+
title: "AI \u56DE\u590D",
|
|
1295
|
+
onClose: () => setReplyOpen(false),
|
|
1296
|
+
footer: (0, import_react6.createElement)(
|
|
1297
|
+
"div",
|
|
1298
|
+
{ style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
|
|
1299
|
+
(0, import_react6.createElement)("span", { className: "small muted" }, "\u751F\u6210\u540E\u53EF\u5728\u6B63\u6587\u91CC\u7EE7\u7EED\u4FEE\u6539\uFF0C\u53D1\u9001\u4ECD\u9700\u4F60\u786E\u8BA4"),
|
|
1300
|
+
(0, import_react6.createElement)("span", { className: "spacer" }),
|
|
1301
|
+
(0, import_react6.createElement)("button", { className: "ghost", onClick: () => setReplyOpen(false) }, "\u53D6\u6D88"),
|
|
1302
|
+
(0, import_react6.createElement)(
|
|
1303
|
+
"button",
|
|
1304
|
+
{ className: "ghost", onClick: () => void generateReply(), disabled: replyBusy },
|
|
1305
|
+
replyBusy ? "\u751F\u6210\u4E2D\u2026" : replyText === "" ? "\u751F\u6210\u56DE\u590D" : "\u91CD\u65B0\u751F\u6210"
|
|
1306
|
+
),
|
|
1307
|
+
(0, import_react6.createElement)(
|
|
1308
|
+
"button",
|
|
1309
|
+
{
|
|
1310
|
+
className: "primary",
|
|
1311
|
+
onClick: insertReply,
|
|
1312
|
+
disabled: replyBusy || replyText.trim() === ""
|
|
1313
|
+
},
|
|
1314
|
+
"\u63D2\u5165\u5230\u6B63\u6587"
|
|
1315
|
+
)
|
|
1316
|
+
)
|
|
1317
|
+
},
|
|
1318
|
+
(0, import_react6.createElement)(
|
|
1319
|
+
"div",
|
|
1320
|
+
{ className: "mailharbor-grid" },
|
|
1321
|
+
(0, import_react6.createElement)(
|
|
1322
|
+
"div",
|
|
1323
|
+
{ className: "mailharbor-field" },
|
|
1324
|
+
(0, import_react6.createElement)("label", null, "\u60F3\u8BA9 AI \u600E\u4E48\u5199\uFF08\u53EF\u7559\u7A7A\uFF09"),
|
|
1325
|
+
(0, import_react6.createElement)("input", {
|
|
1326
|
+
value: replyInstruction,
|
|
1327
|
+
placeholder: "\u4F8B\u5982\uFF1A\u786E\u8BA4\u6536\u5230\uFF0C\u8BF4\u660E\u5468\u4E94\u524D\u7ED9\u7B54\u590D",
|
|
1328
|
+
onChange: (event) => setReplyInstruction(event.target.value)
|
|
1329
|
+
})
|
|
1330
|
+
),
|
|
1331
|
+
(0, import_react6.createElement)(
|
|
1332
|
+
"div",
|
|
1333
|
+
{ className: "mailharbor-field" },
|
|
1334
|
+
(0, import_react6.createElement)("label", null, "\u8BED\u8A00"),
|
|
1335
|
+
(0, import_react6.createElement)(
|
|
1336
|
+
"div",
|
|
1337
|
+
{ className: "mailharbor-segment" },
|
|
1338
|
+
(0, import_react6.createElement)(
|
|
1339
|
+
"button",
|
|
1340
|
+
{
|
|
1341
|
+
className: replyLanguage === "zh" ? "active" : "",
|
|
1342
|
+
onClick: () => setReplyLanguage("zh")
|
|
1343
|
+
},
|
|
1344
|
+
"\u4E2D\u6587"
|
|
1345
|
+
),
|
|
1346
|
+
(0, import_react6.createElement)(
|
|
1347
|
+
"button",
|
|
1348
|
+
{
|
|
1349
|
+
className: replyLanguage === "en" ? "active" : "",
|
|
1350
|
+
onClick: () => setReplyLanguage("en")
|
|
1351
|
+
},
|
|
1352
|
+
"English"
|
|
1353
|
+
)
|
|
1354
|
+
)
|
|
1355
|
+
)
|
|
1356
|
+
),
|
|
1357
|
+
replyError === "" ? null : (0, import_react6.createElement)("div", { style: { color: "var(--m-danger)" } }, replyError),
|
|
1358
|
+
(0, import_react6.createElement)("textarea", {
|
|
1359
|
+
rows: 12,
|
|
1360
|
+
value: replyText,
|
|
1361
|
+
placeholder: replyBusy ? "\u6B63\u5728\u751F\u6210\u2026" : "\u70B9\u300C\u751F\u6210\u56DE\u590D\u300D\u8BA9 AI \u8D77\u8349\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC",
|
|
1362
|
+
onChange: (event) => setReplyText(event.target.value)
|
|
1363
|
+
})
|
|
1364
|
+
) : null
|
|
1114
1365
|
);
|
|
1115
1366
|
}
|
|
1116
1367
|
|
|
1117
1368
|
// src/client/panel.tsx
|
|
1118
1369
|
var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
|
|
1119
|
-
|
|
1370
|
+
var DRAFTS = "__drafts";
|
|
1371
|
+
function errorText4(error) {
|
|
1120
1372
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
1121
1373
|
}
|
|
1122
1374
|
function matchesNarrow() {
|
|
@@ -1129,12 +1381,14 @@ function quoteForReply(detail) {
|
|
|
1129
1381
|
function MailPanel() {
|
|
1130
1382
|
const [state, setState] = (0, import_react7.useState)(null);
|
|
1131
1383
|
const [error, setError] = (0, import_react7.useState)("");
|
|
1384
|
+
const [accountScope, setAccountScope] = (0, import_react7.useState)(null);
|
|
1132
1385
|
const [source, setSource] = (0, import_react7.useState)("local");
|
|
1133
1386
|
const [mailbox, setMailbox] = (0, import_react7.useState)("all");
|
|
1134
1387
|
const [folders, setFolders] = (0, import_react7.useState)([]);
|
|
1135
1388
|
const [query2, setQuery] = (0, import_react7.useState)("");
|
|
1136
1389
|
const [search, setSearch] = (0, import_react7.useState)("");
|
|
1137
1390
|
const [page, setPage] = (0, import_react7.useState)(EMPTY_PAGE);
|
|
1391
|
+
const [drafts, setDrafts] = (0, import_react7.useState)([]);
|
|
1138
1392
|
const [limit, setLimit] = (0, import_react7.useState)(40);
|
|
1139
1393
|
const [loading, setLoading] = (0, import_react7.useState)(false);
|
|
1140
1394
|
const [selected, setSelected] = (0, import_react7.useState)(null);
|
|
@@ -1145,8 +1399,11 @@ function MailPanel() {
|
|
|
1145
1399
|
const [toast, setToast] = (0, import_react7.useState)("");
|
|
1146
1400
|
const [narrow, setNarrow] = (0, import_react7.useState)(() => matchesNarrow());
|
|
1147
1401
|
const [tick, setTick] = (0, import_react7.useState)(0);
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
1402
|
+
const accounts = state?.accounts ?? [];
|
|
1403
|
+
const isAll = accountScope === "all";
|
|
1404
|
+
const account = accounts.find((item) => item.id === accountScope) ?? null;
|
|
1405
|
+
const composeAccount = accounts.find((item) => item.id === compose?.accountId) ?? account ?? accounts[0] ?? null;
|
|
1406
|
+
const showingDrafts = mailbox === DRAFTS && source === "local";
|
|
1150
1407
|
const refresh = (0, import_react7.useCallback)(() => {
|
|
1151
1408
|
setTick((value) => value + 1);
|
|
1152
1409
|
}, []);
|
|
@@ -1159,29 +1416,32 @@ function MailPanel() {
|
|
|
1159
1416
|
(0, import_react7.useEffect)(() => {
|
|
1160
1417
|
let cancelled = false;
|
|
1161
1418
|
api.state().then((next) => {
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1419
|
+
if (cancelled) return;
|
|
1420
|
+
setState(next);
|
|
1421
|
+
if (next.settings.render.listLimit > 0) setLimit(next.settings.render.listLimit);
|
|
1422
|
+
setAccountScope((current) => current ?? next.activeAccountId);
|
|
1166
1423
|
}).catch((caught) => {
|
|
1167
|
-
if (!cancelled) setError(
|
|
1424
|
+
if (!cancelled) setError(errorText4(caught));
|
|
1168
1425
|
});
|
|
1169
1426
|
return () => {
|
|
1170
1427
|
cancelled = true;
|
|
1171
1428
|
};
|
|
1172
1429
|
}, [tick]);
|
|
1173
1430
|
(0, import_react7.useEffect)(() => {
|
|
1174
|
-
if (
|
|
1431
|
+
if (accountScope === null) return;
|
|
1175
1432
|
let cancelled = false;
|
|
1176
|
-
api.folders({ accountId, source }).then((result) => {
|
|
1433
|
+
api.folders({ accountId: accountScope, source }).then((result) => {
|
|
1177
1434
|
if (cancelled) return;
|
|
1178
1435
|
const items2 = source === "local" ? [
|
|
1179
1436
|
{
|
|
1180
1437
|
name: "all",
|
|
1181
1438
|
label: "\u5168\u90E8\u90AE\u4EF6",
|
|
1182
|
-
count: result.items.reduce((sum, item) => sum + item.count, 0)
|
|
1439
|
+
count: result.items.filter((item) => item.name !== DRAFTS).reduce((sum, item) => sum + item.count, 0)
|
|
1183
1440
|
},
|
|
1184
|
-
...result.items.map((item) => ({
|
|
1441
|
+
...result.items.map((item) => ({
|
|
1442
|
+
...item,
|
|
1443
|
+
label: item.name === DRAFTS ? "\u8349\u7A3F" : item.label ?? item.name
|
|
1444
|
+
}))
|
|
1185
1445
|
] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
|
|
1186
1446
|
setFolders(items2);
|
|
1187
1447
|
}).catch(() => {
|
|
@@ -1190,26 +1450,56 @@ function MailPanel() {
|
|
|
1190
1450
|
return () => {
|
|
1191
1451
|
cancelled = true;
|
|
1192
1452
|
};
|
|
1193
|
-
}, [
|
|
1453
|
+
}, [accountScope, source, tick]);
|
|
1194
1454
|
(0, import_react7.useEffect)(() => {
|
|
1195
1455
|
setMailbox(source === "local" ? "all" : "INBOX");
|
|
1196
1456
|
setSelected(null);
|
|
1457
|
+
setLimit(state?.settings.render.listLimit ?? 40);
|
|
1197
1458
|
}, [source]);
|
|
1198
1459
|
(0, import_react7.useEffect)(() => {
|
|
1199
|
-
if (
|
|
1460
|
+
if (accountScope === "all" && source === "server") setSource("local");
|
|
1461
|
+
setSelected(null);
|
|
1462
|
+
}, [accountScope, source]);
|
|
1463
|
+
(0, import_react7.useEffect)(() => {
|
|
1464
|
+
if (accountScope === null) return;
|
|
1200
1465
|
let cancelled = false;
|
|
1201
1466
|
setLoading(true);
|
|
1202
|
-
|
|
1203
|
-
|
|
1467
|
+
if (showingDrafts) {
|
|
1468
|
+
const targets = isAll ? accounts : account === null ? [] : [account];
|
|
1469
|
+
Promise.all(
|
|
1470
|
+
targets.map(async (target) => {
|
|
1471
|
+
try {
|
|
1472
|
+
const result = await api.drafts(target.id);
|
|
1473
|
+
return result.items.map((draft) => ({ ...draft, accountId: target.id }));
|
|
1474
|
+
} catch {
|
|
1475
|
+
return [];
|
|
1476
|
+
}
|
|
1477
|
+
})
|
|
1478
|
+
).then((groups) => {
|
|
1479
|
+
if (cancelled) return;
|
|
1480
|
+
setDrafts(groups.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
|
|
1481
|
+
setPage(EMPTY_PAGE);
|
|
1482
|
+
}).finally(() => {
|
|
1483
|
+
if (!cancelled) setLoading(false);
|
|
1484
|
+
});
|
|
1485
|
+
return () => {
|
|
1486
|
+
cancelled = true;
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
api.messages({ accountId: accountScope, source, mailbox, query: search, limit }).then((result) => {
|
|
1490
|
+
if (!cancelled) {
|
|
1491
|
+
setPage(result);
|
|
1492
|
+
setDrafts([]);
|
|
1493
|
+
}
|
|
1204
1494
|
}).catch((caught) => {
|
|
1205
|
-
if (!cancelled) setError(
|
|
1495
|
+
if (!cancelled) setError(errorText4(caught));
|
|
1206
1496
|
}).finally(() => {
|
|
1207
1497
|
if (!cancelled) setLoading(false);
|
|
1208
1498
|
});
|
|
1209
1499
|
return () => {
|
|
1210
1500
|
cancelled = true;
|
|
1211
1501
|
};
|
|
1212
|
-
}, [
|
|
1502
|
+
}, [accountScope, source, mailbox, search, limit, tick]);
|
|
1213
1503
|
(0, import_react7.useEffect)(() => {
|
|
1214
1504
|
if (typeof window.matchMedia !== "function") return;
|
|
1215
1505
|
const media = window.matchMedia("(max-width: 900px)");
|
|
@@ -1220,11 +1510,11 @@ function MailPanel() {
|
|
|
1220
1510
|
};
|
|
1221
1511
|
}, []);
|
|
1222
1512
|
(0, import_react7.useEffect)(() => {
|
|
1223
|
-
if (!showBackup ||
|
|
1513
|
+
if (!showBackup || accountScope === null || isAll) return;
|
|
1224
1514
|
let cancelled = false;
|
|
1225
1515
|
const poll = async () => {
|
|
1226
1516
|
try {
|
|
1227
|
-
const status = await api.backupStatus(
|
|
1517
|
+
const status = await api.backupStatus(accountScope);
|
|
1228
1518
|
if (!cancelled) setBackup(status);
|
|
1229
1519
|
} catch {
|
|
1230
1520
|
}
|
|
@@ -1237,16 +1527,17 @@ function MailPanel() {
|
|
|
1237
1527
|
cancelled = true;
|
|
1238
1528
|
window.clearInterval(timer);
|
|
1239
1529
|
};
|
|
1240
|
-
}, [showBackup,
|
|
1530
|
+
}, [showBackup, accountScope, isAll]);
|
|
1241
1531
|
const openMessage = async (item) => {
|
|
1242
|
-
|
|
1532
|
+
const owner = item.accountId;
|
|
1533
|
+
const ownerSource = source;
|
|
1243
1534
|
setError("");
|
|
1244
1535
|
try {
|
|
1245
|
-
const detail = await api.message({ id: item.id, accountId:
|
|
1536
|
+
const detail = await api.message({ id: item.id, accountId: owner, source: ownerSource, mailbox });
|
|
1246
1537
|
setSelected(detail);
|
|
1247
1538
|
if (detail.unread && state?.settings.read.markOnOpen === true) {
|
|
1248
1539
|
try {
|
|
1249
|
-
await api.setRead({ id: detail.id, accountId:
|
|
1540
|
+
await api.setRead({ id: detail.id, accountId: owner, source: ownerSource, mailbox, read: true });
|
|
1250
1541
|
setSelected({ ...detail, unread: false });
|
|
1251
1542
|
setPage((current) => ({
|
|
1252
1543
|
...current,
|
|
@@ -1256,14 +1547,15 @@ function MailPanel() {
|
|
|
1256
1547
|
}
|
|
1257
1548
|
}
|
|
1258
1549
|
} catch (caught) {
|
|
1259
|
-
setError(
|
|
1550
|
+
setError(errorText4(caught));
|
|
1260
1551
|
}
|
|
1261
1552
|
};
|
|
1262
1553
|
const startCompose = (mode) => {
|
|
1263
|
-
|
|
1554
|
+
const owner = account ?? accounts.find((item) => item.id === state?.activeAccountId) ?? accounts[0];
|
|
1555
|
+
if (owner === void 0) return;
|
|
1264
1556
|
const detail = selected;
|
|
1265
1557
|
if (mode === "new" || detail === null) {
|
|
1266
|
-
setCompose({ mode: "new", to: "", cc: "", bcc: "", subject: "", html: "" });
|
|
1558
|
+
setCompose({ mode: "new", accountId: owner.id, to: "", cc: "", bcc: "", subject: "", html: "" });
|
|
1267
1559
|
return;
|
|
1268
1560
|
}
|
|
1269
1561
|
const subjectPrefix = mode === "forward" ? "Fwd: " : "Re: ";
|
|
@@ -1271,6 +1563,7 @@ function MailPanel() {
|
|
|
1271
1563
|
if (mode === "forward") {
|
|
1272
1564
|
setCompose({
|
|
1273
1565
|
mode,
|
|
1566
|
+
accountId: detail.accountId,
|
|
1274
1567
|
to: "",
|
|
1275
1568
|
cc: "",
|
|
1276
1569
|
bcc: "",
|
|
@@ -1281,9 +1574,10 @@ function MailPanel() {
|
|
|
1281
1574
|
return;
|
|
1282
1575
|
}
|
|
1283
1576
|
const to = detail.from.address;
|
|
1284
|
-
const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !==
|
|
1577
|
+
const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !== owner.email && address !== to).join(", ") : "";
|
|
1285
1578
|
setCompose({
|
|
1286
1579
|
mode,
|
|
1580
|
+
accountId: detail.accountId,
|
|
1287
1581
|
to,
|
|
1288
1582
|
cc,
|
|
1289
1583
|
bcc: "",
|
|
@@ -1292,32 +1586,58 @@ function MailPanel() {
|
|
|
1292
1586
|
...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
|
|
1293
1587
|
});
|
|
1294
1588
|
};
|
|
1589
|
+
const openDraft = (draft) => {
|
|
1590
|
+
setCompose({
|
|
1591
|
+
mode: "new",
|
|
1592
|
+
accountId: draft.accountId,
|
|
1593
|
+
to: draft.to,
|
|
1594
|
+
cc: draft.cc,
|
|
1595
|
+
bcc: draft.bcc,
|
|
1596
|
+
subject: draft.subject,
|
|
1597
|
+
html: draft.html,
|
|
1598
|
+
attachments: draft.attachments,
|
|
1599
|
+
draftId: draft.id
|
|
1600
|
+
});
|
|
1601
|
+
};
|
|
1602
|
+
const removeDraft = async (id, accountId) => {
|
|
1603
|
+
try {
|
|
1604
|
+
await api.deleteDraft(id, accountId);
|
|
1605
|
+
setDrafts((current) => current.filter((draft) => draft.id !== id));
|
|
1606
|
+
notice("\u8349\u7A3F\u5DF2\u5220\u9664");
|
|
1607
|
+
refresh();
|
|
1608
|
+
} catch (caught) {
|
|
1609
|
+
setError(errorText4(caught));
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1295
1612
|
const switchAccount = async (id) => {
|
|
1296
1613
|
try {
|
|
1297
|
-
await api.activateAccount(id);
|
|
1614
|
+
if (id !== "all") await api.activateAccount(id);
|
|
1615
|
+
setAccountScope(id);
|
|
1298
1616
|
setSelected(null);
|
|
1617
|
+
if (id === "all") setSource("local");
|
|
1299
1618
|
refresh();
|
|
1300
1619
|
} catch (caught) {
|
|
1301
|
-
setError(
|
|
1620
|
+
setError(errorText4(caught));
|
|
1302
1621
|
}
|
|
1303
1622
|
};
|
|
1304
1623
|
const startBackup = async () => {
|
|
1305
|
-
|
|
1624
|
+
const owner = accountScope === null || isAll ? accounts[0] : accounts.find((item) => item.id === accountScope);
|
|
1625
|
+
if (owner === void 0) return;
|
|
1306
1626
|
try {
|
|
1307
|
-
const result = await api.startBackup(
|
|
1627
|
+
const result = await api.startBackup(owner.id);
|
|
1308
1628
|
setBackup(result.status);
|
|
1309
1629
|
setShowBackup(true);
|
|
1310
1630
|
} catch (caught) {
|
|
1311
|
-
setError(
|
|
1631
|
+
setError(errorText4(caught));
|
|
1312
1632
|
}
|
|
1313
1633
|
};
|
|
1314
1634
|
const stopBackup = async () => {
|
|
1315
|
-
if (
|
|
1635
|
+
if (accountScope === null || isAll) return;
|
|
1316
1636
|
try {
|
|
1317
|
-
const result = await api.stopBackup(
|
|
1637
|
+
const result = await api.stopBackup(accountScope);
|
|
1318
1638
|
setBackup(result.status);
|
|
1319
1639
|
} catch (caught) {
|
|
1320
|
-
setError(
|
|
1640
|
+
setError(errorText4(caught));
|
|
1321
1641
|
}
|
|
1322
1642
|
};
|
|
1323
1643
|
const folderOptions = (0, import_react7.useMemo)(
|
|
@@ -1334,7 +1654,7 @@ function MailPanel() {
|
|
|
1334
1654
|
(0, import_react7.createElement)("div", { className: "mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error)
|
|
1335
1655
|
);
|
|
1336
1656
|
}
|
|
1337
|
-
if (
|
|
1657
|
+
if (accounts.length === 0) {
|
|
1338
1658
|
return (0, import_react7.createElement)(
|
|
1339
1659
|
"div",
|
|
1340
1660
|
{ className: "mailharbor" },
|
|
@@ -1351,13 +1671,16 @@ function MailPanel() {
|
|
|
1351
1671
|
(0, import_react7.createElement)(AddAccountForm, {
|
|
1352
1672
|
onDone: (next) => {
|
|
1353
1673
|
setState(next);
|
|
1674
|
+
setAccountScope(next.activeAccountId);
|
|
1354
1675
|
setSource("local");
|
|
1355
1676
|
}
|
|
1356
1677
|
})
|
|
1357
1678
|
)
|
|
1358
1679
|
);
|
|
1359
1680
|
}
|
|
1360
|
-
const
|
|
1681
|
+
const currentAccount = account ?? accounts[0] ?? null;
|
|
1682
|
+
const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
|
|
1683
|
+
const needsLogin = source === "server" && !isAll && state.loggedIn === false;
|
|
1361
1684
|
const items = page.items;
|
|
1362
1685
|
return (0, import_react7.createElement)(
|
|
1363
1686
|
"div",
|
|
@@ -1368,17 +1691,19 @@ function MailPanel() {
|
|
|
1368
1691
|
(0, import_react7.createElement)(
|
|
1369
1692
|
"div",
|
|
1370
1693
|
{ className: "mailharbor-account" },
|
|
1371
|
-
(0, import_react7.createElement)(Avatar, { email:
|
|
1372
|
-
|
|
1694
|
+
(0, import_react7.createElement)(Avatar, { email: isAll ? "all" : currentAccount?.email ?? "?", size: 26 }),
|
|
1695
|
+
accounts.length > 1 ? (0, import_react7.createElement)(
|
|
1373
1696
|
"select",
|
|
1374
1697
|
{
|
|
1375
|
-
value:
|
|
1698
|
+
value: accountScope ?? "",
|
|
1376
1699
|
onChange: (event) => void switchAccount(event.target.value),
|
|
1377
1700
|
"aria-label": "\u8D26\u53F7"
|
|
1378
1701
|
},
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1702
|
+
(0, import_react7.createElement)("option", { key: "all", value: "all" }, "\u5168\u90E8\u8D26\u53F7"),
|
|
1703
|
+
accounts.map((item) => (0, import_react7.createElement)("option", { key: item.id, value: item.id }, item.label))
|
|
1704
|
+
) : (0, import_react7.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
|
|
1705
|
+
isAll ? (0, import_react7.createElement)("span", { className: "badge" }, `${String(accounts.length)} \u4E2A\u8D26\u53F7`) : null,
|
|
1706
|
+
!isAll && state.loggedIn === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null
|
|
1382
1707
|
),
|
|
1383
1708
|
(0, import_react7.createElement)(
|
|
1384
1709
|
"div",
|
|
@@ -1390,7 +1715,18 @@ function MailPanel() {
|
|
|
1390
1715
|
),
|
|
1391
1716
|
(0, import_react7.createElement)(
|
|
1392
1717
|
"button",
|
|
1393
|
-
{
|
|
1718
|
+
{
|
|
1719
|
+
className: source === "server" ? "active" : "",
|
|
1720
|
+
onClick: () => {
|
|
1721
|
+
if (isAll) {
|
|
1722
|
+
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");
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
setSource("server");
|
|
1726
|
+
},
|
|
1727
|
+
role: "tab",
|
|
1728
|
+
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"
|
|
1729
|
+
},
|
|
1394
1730
|
"\u670D\u52A1\u5668\u5B9E\u65F6"
|
|
1395
1731
|
)
|
|
1396
1732
|
),
|
|
@@ -1412,7 +1748,11 @@ function MailPanel() {
|
|
|
1412
1748
|
),
|
|
1413
1749
|
(0, import_react7.createElement)(
|
|
1414
1750
|
"button",
|
|
1415
|
-
{
|
|
1751
|
+
{
|
|
1752
|
+
className: "ghost",
|
|
1753
|
+
onClick: () => void startBackup(),
|
|
1754
|
+
title: isAll ? "\u5907\u4EFD\u5F53\u524D\u6D3B\u52A8\u8D26\u53F7" : "\u5907\u4EFD\u8FD9\u4E2A\u8D26\u53F7"
|
|
1755
|
+
},
|
|
1416
1756
|
(0, import_react7.createElement)(Icon, { name: "archive" }),
|
|
1417
1757
|
"\u5907\u4EFD"
|
|
1418
1758
|
),
|
|
@@ -1425,7 +1765,11 @@ function MailPanel() {
|
|
|
1425
1765
|
),
|
|
1426
1766
|
toast === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { padding: "6px 14px", color: "var(--m-ok)" } }, toast),
|
|
1427
1767
|
error === "" ? null : (0, import_react7.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
|
|
1428
|
-
needsLogin ? (0, import_react7.createElement)(
|
|
1768
|
+
needsLogin ? (0, import_react7.createElement)(
|
|
1769
|
+
"div",
|
|
1770
|
+
{ className: "mailharbor-settings" },
|
|
1771
|
+
(0, import_react7.createElement)(LoginForm, { account: currentAccount, onDone: refresh })
|
|
1772
|
+
) : (0, import_react7.createElement)(
|
|
1429
1773
|
"div",
|
|
1430
1774
|
{ className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
|
|
1431
1775
|
(0, import_react7.createElement)(
|
|
@@ -1450,7 +1794,7 @@ function MailPanel() {
|
|
|
1450
1794
|
),
|
|
1451
1795
|
(0, import_react7.createElement)("input", {
|
|
1452
1796
|
type: "search",
|
|
1453
|
-
placeholder: "\u641C\u7D22\u90AE\u4EF6",
|
|
1797
|
+
placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
|
|
1454
1798
|
"aria-label": "\u641C\u7D22\u90AE\u4EF6",
|
|
1455
1799
|
value: query2,
|
|
1456
1800
|
onChange: (event) => setQuery(event.target.value),
|
|
@@ -1478,7 +1822,73 @@ function MailPanel() {
|
|
|
1478
1822
|
(0, import_react7.createElement)(
|
|
1479
1823
|
"div",
|
|
1480
1824
|
{ className: "mailharbor-rows" },
|
|
1481
|
-
loading && items.length === 0 ? (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") :
|
|
1825
|
+
loading && items.length === 0 && drafts.length === 0 ? (0, import_react7.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : showingDrafts ? drafts.length === 0 ? (0, import_react7.createElement)(
|
|
1826
|
+
"div",
|
|
1827
|
+
{ className: "mailharbor-empty" },
|
|
1828
|
+
(0, import_react7.createElement)(Icon, { name: "compose", size: 28 }),
|
|
1829
|
+
(0, import_react7.createElement)("div", { className: "title" }, "\u6CA1\u6709\u8349\u7A3F"),
|
|
1830
|
+
(0, import_react7.createElement)("div", { className: "small" }, "\u5199\u4FE1\u65F6\u70B9\u300C\u5B58\u8349\u7A3F\u300D\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002")
|
|
1831
|
+
) : drafts.filter(
|
|
1832
|
+
(draft) => search === "" ? true : `${draft.subject} ${draft.to} ${draft.cc}`.toLowerCase().includes(search.toLowerCase())
|
|
1833
|
+
).map(
|
|
1834
|
+
(draft) => (0, import_react7.createElement)(
|
|
1835
|
+
"div",
|
|
1836
|
+
{
|
|
1837
|
+
key: draft.id,
|
|
1838
|
+
className: "mailharbor-row",
|
|
1839
|
+
role: "button",
|
|
1840
|
+
tabIndex: 0,
|
|
1841
|
+
onClick: () => openDraft(draft),
|
|
1842
|
+
onKeyDown: (event) => {
|
|
1843
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1844
|
+
event.preventDefault();
|
|
1845
|
+
openDraft(draft);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
},
|
|
1849
|
+
(0, import_react7.createElement)(
|
|
1850
|
+
"span",
|
|
1851
|
+
{ className: "line-1" },
|
|
1852
|
+
(0, import_react7.createElement)(
|
|
1853
|
+
"span",
|
|
1854
|
+
{ className: "from" },
|
|
1855
|
+
`\u8349\u7A3F${draft.to === "" ? "" : ` \xB7 \u6536\u4EF6\u4EBA ${draft.to}`}`
|
|
1856
|
+
),
|
|
1857
|
+
isAll ? (0, import_react7.createElement)(
|
|
1858
|
+
"span",
|
|
1859
|
+
{ className: "badge" },
|
|
1860
|
+
accounts.find((item) => item.id === draft.accountId)?.label ?? ""
|
|
1861
|
+
) : null,
|
|
1862
|
+
(0, import_react7.createElement)("span", { className: "date" }, shortDate(draft.updatedAt)),
|
|
1863
|
+
(0, import_react7.createElement)(
|
|
1864
|
+
"button",
|
|
1865
|
+
{
|
|
1866
|
+
className: "subtle",
|
|
1867
|
+
title: "\u5220\u9664\u8349\u7A3F",
|
|
1868
|
+
onClick: (event) => {
|
|
1869
|
+
event.stopPropagation();
|
|
1870
|
+
void removeDraft(draft.id, draft.accountId);
|
|
1871
|
+
}
|
|
1872
|
+
},
|
|
1873
|
+
(0, import_react7.createElement)(Icon, { name: "close", size: 13 })
|
|
1874
|
+
)
|
|
1875
|
+
),
|
|
1876
|
+
(0, import_react7.createElement)(
|
|
1877
|
+
"span",
|
|
1878
|
+
{ className: "line-2" },
|
|
1879
|
+
(0, import_react7.createElement)(
|
|
1880
|
+
"span",
|
|
1881
|
+
{ className: "subject" },
|
|
1882
|
+
draft.subject === "" ? "(\u65E0\u4E3B\u9898)" : draft.subject
|
|
1883
|
+
)
|
|
1884
|
+
),
|
|
1885
|
+
draft.html === "" ? null : (0, import_react7.createElement)(
|
|
1886
|
+
"span",
|
|
1887
|
+
{ className: "snippet" },
|
|
1888
|
+
draft.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120)
|
|
1889
|
+
)
|
|
1890
|
+
)
|
|
1891
|
+
) : items.length === 0 ? (0, import_react7.createElement)(
|
|
1482
1892
|
"div",
|
|
1483
1893
|
{ className: "mailharbor-empty" },
|
|
1484
1894
|
(0, import_react7.createElement)(Icon, { name: "mail", size: 28 }),
|
|
@@ -1496,8 +1906,8 @@ function MailPanel() {
|
|
|
1496
1906
|
(item) => (0, import_react7.createElement)(
|
|
1497
1907
|
"div",
|
|
1498
1908
|
{
|
|
1499
|
-
key: item.id
|
|
1500
|
-
className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id ? " selected" : ""}`,
|
|
1909
|
+
key: `${item.accountId}:${item.id}`,
|
|
1910
|
+
className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id && selected?.accountId === item.accountId ? " selected" : ""}`,
|
|
1501
1911
|
role: "button",
|
|
1502
1912
|
tabIndex: 0,
|
|
1503
1913
|
onClick: () => void openMessage(item),
|
|
@@ -1518,15 +1928,24 @@ function MailPanel() {
|
|
|
1518
1928
|
item.from.name !== "" ? item.from.name : item.from.address
|
|
1519
1929
|
),
|
|
1520
1930
|
item.hasAttachments ? (0, import_react7.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
|
|
1931
|
+
isAll ? (0, import_react7.createElement)(
|
|
1932
|
+
"span",
|
|
1933
|
+
{ className: "badge" },
|
|
1934
|
+
accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
|
|
1935
|
+
) : null,
|
|
1521
1936
|
item.backedUp === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
|
|
1522
1937
|
(0, import_react7.createElement)("span", { className: "date" }, shortDate(item.date))
|
|
1523
1938
|
),
|
|
1524
|
-
(0, import_react7.createElement)(
|
|
1939
|
+
(0, import_react7.createElement)(
|
|
1940
|
+
"span",
|
|
1941
|
+
{ className: "line-2" },
|
|
1942
|
+
(0, import_react7.createElement)("span", { className: "subject" }, item.subject)
|
|
1943
|
+
),
|
|
1525
1944
|
item.snippet === "" ? null : (0, import_react7.createElement)("span", { className: "snippet" }, item.snippet)
|
|
1526
1945
|
)
|
|
1527
1946
|
)
|
|
1528
1947
|
),
|
|
1529
|
-
items.length > 0 ? (0, import_react7.createElement)(
|
|
1948
|
+
!showingDrafts && items.length > 0 ? (0, import_react7.createElement)(
|
|
1530
1949
|
"div",
|
|
1531
1950
|
{ className: "mailharbor-list-foot" },
|
|
1532
1951
|
(0, import_react7.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
|
|
@@ -1536,6 +1955,10 @@ function MailPanel() {
|
|
|
1536
1955
|
{ className: "subtle", onClick: () => setLimit((current) => current + 40) },
|
|
1537
1956
|
"\u52A0\u8F7D\u66F4\u591A"
|
|
1538
1957
|
) : null
|
|
1958
|
+
) : showingDrafts && drafts.length > 0 ? (0, import_react7.createElement)(
|
|
1959
|
+
"div",
|
|
1960
|
+
{ className: "mailharbor-list-foot" },
|
|
1961
|
+
(0, import_react7.createElement)("span", null, `${String(drafts.length)} \u5C01\u8349\u7A3F`)
|
|
1539
1962
|
) : null
|
|
1540
1963
|
),
|
|
1541
1964
|
(0, import_react7.createElement)(
|
|
@@ -1549,15 +1972,25 @@ function MailPanel() {
|
|
|
1549
1972
|
(0, import_react7.createElement)(
|
|
1550
1973
|
"div",
|
|
1551
1974
|
{ className: "small" },
|
|
1552
|
-
|
|
1975
|
+
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
1976
|
)
|
|
1554
1977
|
) : (0, import_react7.createElement)(Reader, {
|
|
1555
1978
|
detail: selected,
|
|
1556
|
-
account,
|
|
1557
1979
|
source,
|
|
1558
1980
|
mailbox,
|
|
1559
1981
|
onChanged: (patch) => setSelected({ ...selected, ...patch }),
|
|
1560
1982
|
onReply: (mode) => startCompose(mode),
|
|
1983
|
+
onAiReply: (init) => setCompose({
|
|
1984
|
+
mode: "reply",
|
|
1985
|
+
accountId: selected.accountId,
|
|
1986
|
+
to: init.to,
|
|
1987
|
+
cc: init.cc,
|
|
1988
|
+
bcc: "",
|
|
1989
|
+
subject: init.subject,
|
|
1990
|
+
html: init.html,
|
|
1991
|
+
inReplyTo: init.inReplyTo,
|
|
1992
|
+
references: init.references
|
|
1993
|
+
}),
|
|
1561
1994
|
onClose: narrow ? () => setSelected(null) : void 0
|
|
1562
1995
|
})
|
|
1563
1996
|
)
|
|
@@ -1594,8 +2027,8 @@ function MailPanel() {
|
|
|
1594
2027
|
backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
|
|
1595
2028
|
)
|
|
1596
2029
|
) : null,
|
|
1597
|
-
compose !== null ? (0, import_react7.createElement)(Composer, {
|
|
1598
|
-
account,
|
|
2030
|
+
compose !== null && composeAccount !== null ? (0, import_react7.createElement)(Composer, {
|
|
2031
|
+
account: composeAccount,
|
|
1599
2032
|
initial: compose,
|
|
1600
2033
|
onClose: () => setCompose(null),
|
|
1601
2034
|
onSent: (message) => notice(message)
|
|
@@ -1604,6 +2037,7 @@ function MailPanel() {
|
|
|
1604
2037
|
onClose: () => setShowAdd(false),
|
|
1605
2038
|
onDone: (next) => {
|
|
1606
2039
|
setState(next);
|
|
2040
|
+
setAccountScope(next.activeAccountId);
|
|
1607
2041
|
refresh();
|
|
1608
2042
|
}
|
|
1609
2043
|
}) : null
|
|
@@ -1612,7 +2046,7 @@ function MailPanel() {
|
|
|
1612
2046
|
|
|
1613
2047
|
// src/client/settings.tsx
|
|
1614
2048
|
var import_react8 = require("react");
|
|
1615
|
-
function
|
|
2049
|
+
function errorText5(error) {
|
|
1616
2050
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
1617
2051
|
}
|
|
1618
2052
|
function MailSettings() {
|
|
@@ -1627,6 +2061,11 @@ function MailSettings() {
|
|
|
1627
2061
|
const [aiEnabled, setAiEnabled] = (0, import_react8.useState)(true);
|
|
1628
2062
|
const [aiProvider, setAiProvider] = (0, import_react8.useState)("deepseek-account");
|
|
1629
2063
|
const [aiModel, setAiModel] = (0, import_react8.useState)("deepseek-flash");
|
|
2064
|
+
const [aiModels, setAiModels] = (0, import_react8.useState)({ providers: [] });
|
|
2065
|
+
const [aiStatus, setAiStatus] = (0, import_react8.useState)(null);
|
|
2066
|
+
const [styleText, setStyleText] = (0, import_react8.useState)("");
|
|
2067
|
+
const [styleMeta, setStyleMeta] = (0, import_react8.useState)({ updatedAt: "", sampleCount: 0 });
|
|
2068
|
+
const [styleBusy, setStyleBusy] = (0, import_react8.useState)(null);
|
|
1630
2069
|
const [editing, setEditing] = (0, import_react8.useState)(null);
|
|
1631
2070
|
const [checkResult, setCheckResult] = (0, import_react8.useState)("");
|
|
1632
2071
|
const [adding, setAdding] = (0, import_react8.useState)(false);
|
|
@@ -1645,12 +2084,37 @@ function MailSettings() {
|
|
|
1645
2084
|
setAiProvider(view.ai.provider);
|
|
1646
2085
|
setAiModel(view.ai.model);
|
|
1647
2086
|
}).catch((caught) => {
|
|
1648
|
-
if (!cancelled) setError(
|
|
2087
|
+
if (!cancelled) setError(errorText5(caught));
|
|
1649
2088
|
});
|
|
1650
2089
|
return () => {
|
|
1651
2090
|
cancelled = true;
|
|
1652
2091
|
};
|
|
1653
2092
|
}, [tick]);
|
|
2093
|
+
(0, import_react8.useEffect)(() => {
|
|
2094
|
+
let cancelled = false;
|
|
2095
|
+
const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
|
|
2096
|
+
if (accountId === null) return;
|
|
2097
|
+
Promise.all([api.aiStatus(accountId), api.aiModels(), api.aiStyle(accountId)]).then(([status, models, style]) => {
|
|
2098
|
+
if (cancelled) return;
|
|
2099
|
+
setAiStatus(status);
|
|
2100
|
+
setAiModels(models);
|
|
2101
|
+
setStyleText(style.style?.profile ?? "");
|
|
2102
|
+
setStyleMeta({
|
|
2103
|
+
updatedAt: style.style?.updatedAt ?? "",
|
|
2104
|
+
sampleCount: style.style?.sampleCount ?? 0
|
|
2105
|
+
});
|
|
2106
|
+
const preferred = models.providers.find((item) => item.provider === status.provider) ?? models.providers[0];
|
|
2107
|
+
if (preferred !== void 0 && status.provider === "") {
|
|
2108
|
+
setAiProvider(preferred.provider);
|
|
2109
|
+
const firstModel = preferred.models[0];
|
|
2110
|
+
if (firstModel !== void 0) setAiModel(firstModel.id);
|
|
2111
|
+
}
|
|
2112
|
+
}).catch(() => {
|
|
2113
|
+
});
|
|
2114
|
+
return () => {
|
|
2115
|
+
cancelled = true;
|
|
2116
|
+
};
|
|
2117
|
+
}, [tick, settings?.activeAccountId]);
|
|
1654
2118
|
const reload = () => setTick((value) => value + 1);
|
|
1655
2119
|
const notify = (message) => {
|
|
1656
2120
|
setToast(message);
|
|
@@ -1664,7 +2128,7 @@ function MailSettings() {
|
|
|
1664
2128
|
setSettings(result.settings);
|
|
1665
2129
|
notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
|
|
1666
2130
|
} catch (caught) {
|
|
1667
|
-
setError(
|
|
2131
|
+
setError(errorText5(caught));
|
|
1668
2132
|
}
|
|
1669
2133
|
};
|
|
1670
2134
|
const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
|
|
@@ -1716,7 +2180,7 @@ function MailSettings() {
|
|
|
1716
2180
|
`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
2181
|
);
|
|
1718
2182
|
} catch (caught) {
|
|
1719
|
-
setCheckResult(`\u5931\u8D25: ${
|
|
2183
|
+
setCheckResult(`\u5931\u8D25: ${errorText5(caught)}`);
|
|
1720
2184
|
}
|
|
1721
2185
|
},
|
|
1722
2186
|
onSave: async (patch) => {
|
|
@@ -1803,34 +2267,135 @@ function MailSettings() {
|
|
|
1803
2267
|
setAiEnabled(value);
|
|
1804
2268
|
void saveGeneral({ ai: { enabled: value } });
|
|
1805
2269
|
},
|
|
1806
|
-
label: "\u542F\u7528 AI\uFF08\u6458\u8981\
|
|
2270
|
+
label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001AI \u56DE\u590D\u3001\u7FFB\u8BD1\u3001\u5199\u4F5C\u98CE\u683C\uFF09"
|
|
1807
2271
|
}),
|
|
2272
|
+
aiStatus === null ? null : (0, import_react8.createElement)(
|
|
2273
|
+
"div",
|
|
2274
|
+
{ className: "small", style: { color: aiStatus.available ? "var(--m-ok)" : "var(--m-warn)" } },
|
|
2275
|
+
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"
|
|
2276
|
+
),
|
|
1808
2277
|
(0, import_react8.createElement)(
|
|
1809
2278
|
"div",
|
|
1810
2279
|
{ className: "mailharbor-grid" },
|
|
1811
2280
|
(0, import_react8.createElement)(
|
|
1812
2281
|
Field,
|
|
1813
2282
|
{ label: "Provider" },
|
|
1814
|
-
(0, import_react8.createElement)("input", {
|
|
2283
|
+
aiModels.providers.length === 0 ? (0, import_react8.createElement)("input", {
|
|
1815
2284
|
value: aiProvider,
|
|
1816
2285
|
onChange: (event) => setAiProvider(event.target.value),
|
|
1817
2286
|
onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
|
|
1818
|
-
})
|
|
2287
|
+
}) : (0, import_react8.createElement)(
|
|
2288
|
+
"select",
|
|
2289
|
+
{
|
|
2290
|
+
value: aiProvider,
|
|
2291
|
+
onChange: (event) => {
|
|
2292
|
+
setAiProvider(event.target.value);
|
|
2293
|
+
const provider = aiModels.providers.find((item) => item.provider === event.target.value);
|
|
2294
|
+
const first = provider?.models[0];
|
|
2295
|
+
if (first !== void 0) setAiModel(first.id);
|
|
2296
|
+
void saveGeneral({
|
|
2297
|
+
ai: {
|
|
2298
|
+
provider: event.target.value,
|
|
2299
|
+
...first === void 0 ? {} : { model: first.id }
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
},
|
|
2304
|
+
aiModels.providers.map(
|
|
2305
|
+
(item) => (0, import_react8.createElement)("option", { key: item.provider, value: item.provider }, item.name)
|
|
2306
|
+
)
|
|
2307
|
+
)
|
|
1819
2308
|
),
|
|
1820
2309
|
(0, import_react8.createElement)(
|
|
1821
2310
|
Field,
|
|
1822
2311
|
{ label: "\u6A21\u578B" },
|
|
1823
|
-
(
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2312
|
+
(() => {
|
|
2313
|
+
const provider = aiModels.providers.find((item) => item.provider === aiProvider);
|
|
2314
|
+
return provider === void 0 || provider.models.length === 0 ? (0, import_react8.createElement)("input", {
|
|
2315
|
+
value: aiModel,
|
|
2316
|
+
onChange: (event) => setAiModel(event.target.value),
|
|
2317
|
+
onBlur: () => void saveGeneral({ ai: { model: aiModel } })
|
|
2318
|
+
}) : (0, import_react8.createElement)(
|
|
2319
|
+
"select",
|
|
2320
|
+
{
|
|
2321
|
+
value: aiModel,
|
|
2322
|
+
onChange: (event) => {
|
|
2323
|
+
setAiModel(event.target.value);
|
|
2324
|
+
void saveGeneral({ ai: { model: event.target.value } });
|
|
2325
|
+
}
|
|
2326
|
+
},
|
|
2327
|
+
provider.models.map((item) => (0, import_react8.createElement)("option", { key: item.id, value: item.id }, item.name))
|
|
2328
|
+
);
|
|
2329
|
+
})()
|
|
2330
|
+
)
|
|
2331
|
+
),
|
|
2332
|
+
(0, import_react8.createElement)("hr", null),
|
|
2333
|
+
(0, import_react8.createElement)(
|
|
2334
|
+
"div",
|
|
2335
|
+
{ style: { display: "flex", alignItems: "center", gap: "8px" } },
|
|
2336
|
+
(0, import_react8.createElement)("strong", { style: { fontSize: "12.5px" } }, "\u5199\u4F5C\u98CE\u683C"),
|
|
2337
|
+
(0, import_react8.createElement)(
|
|
2338
|
+
"span",
|
|
2339
|
+
{ className: "small muted" },
|
|
2340
|
+
styleMeta.updatedAt === "" ? "\u8FD8\u6CA1\u6709\u751F\u6210" : `${styleMeta.sampleCount} \u5C01\u5DF2\u53D1\u9001\u90AE\u4EF6 \xB7 ${new Date(styleMeta.updatedAt).toLocaleString()}`
|
|
2341
|
+
)
|
|
2342
|
+
),
|
|
2343
|
+
(0, import_react8.createElement)(
|
|
2344
|
+
"p",
|
|
2345
|
+
{ className: "small muted" },
|
|
2346
|
+
"\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"
|
|
2347
|
+
),
|
|
2348
|
+
(0, import_react8.createElement)("textarea", {
|
|
2349
|
+
rows: 6,
|
|
2350
|
+
value: styleText,
|
|
2351
|
+
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",
|
|
2352
|
+
onChange: (event) => setStyleText(event.target.value)
|
|
2353
|
+
}),
|
|
2354
|
+
(0, import_react8.createElement)(
|
|
2355
|
+
"div",
|
|
2356
|
+
{ style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
|
|
2357
|
+
(0, import_react8.createElement)(
|
|
2358
|
+
"button",
|
|
2359
|
+
{
|
|
2360
|
+
className: "ghost",
|
|
2361
|
+
disabled: styleBusy !== null,
|
|
2362
|
+
onClick: () => {
|
|
2363
|
+
const accountId = settings.activeAccountId;
|
|
2364
|
+
if (accountId === null) return;
|
|
2365
|
+
setStyleBusy("learn");
|
|
2366
|
+
setError("");
|
|
2367
|
+
api.aiLearnStyle(accountId).then((result) => {
|
|
2368
|
+
setStyleText(result.style.profile);
|
|
2369
|
+
setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
|
|
2370
|
+
notify("\u5DF2\u6839\u636E\u5DF2\u53D1\u9001\u90AE\u4EF6\u751F\u6210\u5199\u4F5C\u98CE\u683C");
|
|
2371
|
+
}).catch((caught) => setError(errorText5(caught))).finally(() => setStyleBusy(null));
|
|
2372
|
+
}
|
|
2373
|
+
},
|
|
2374
|
+
styleBusy === "learn" ? "\u5B66\u4E60\u4E2D\u2026" : "\u4ECE\u5DF2\u53D1\u9001\u90AE\u4EF6\u5B66\u4E60"
|
|
2375
|
+
),
|
|
2376
|
+
(0, import_react8.createElement)(
|
|
2377
|
+
"button",
|
|
2378
|
+
{
|
|
2379
|
+
className: "primary",
|
|
2380
|
+
disabled: styleBusy !== null || styleText.trim() === "",
|
|
2381
|
+
onClick: () => {
|
|
2382
|
+
const accountId = settings.activeAccountId;
|
|
2383
|
+
if (accountId === null) return;
|
|
2384
|
+
setStyleBusy("save");
|
|
2385
|
+
setError("");
|
|
2386
|
+
api.aiSaveStyle(accountId, styleText).then((result) => {
|
|
2387
|
+
setStyleMeta({ updatedAt: result.style.updatedAt, sampleCount: result.style.sampleCount });
|
|
2388
|
+
notify("\u5199\u4F5C\u98CE\u683C\u5DF2\u4FDD\u5B58");
|
|
2389
|
+
}).catch((caught) => setError(errorText5(caught))).finally(() => setStyleBusy(null));
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
"\u4FDD\u5B58\u98CE\u683C"
|
|
1828
2393
|
)
|
|
1829
2394
|
),
|
|
1830
2395
|
(0, import_react8.createElement)(
|
|
1831
2396
|
"p",
|
|
1832
2397
|
{ className: "small muted" },
|
|
1833
|
-
"AI \u8C03\u7528\u8D70 DSH \u81EA\u5DF1\u7684\u6A21\u578B\u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4FDD\u5B58\u5BC6\u94A5\
|
|
2398
|
+
"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
2399
|
)
|
|
1835
2400
|
),
|
|
1836
2401
|
(0, import_react8.createElement)(
|
|
@@ -1845,7 +2410,11 @@ function MailSettings() {
|
|
|
1845
2410
|
(0, import_react8.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
|
|
1846
2411
|
(0, import_react8.createElement)("dd", null, settings.configFile),
|
|
1847
2412
|
(0, import_react8.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
|
|
1848
|
-
(0, import_react8.createElement)(
|
|
2413
|
+
(0, import_react8.createElement)(
|
|
2414
|
+
"dd",
|
|
2415
|
+
null,
|
|
2416
|
+
account === null ? "\u2014" : `${account.dataDir.startsWith("/") ? account.dataDir : `${settings.dataDir}/${account.dataDir}`}/mail.db`
|
|
2417
|
+
),
|
|
1849
2418
|
(0, import_react8.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
|
|
1850
2419
|
(0, import_react8.createElement)("dd", null, state.version)
|
|
1851
2420
|
),
|
|
@@ -1862,7 +2431,7 @@ function MailSettings() {
|
|
|
1862
2431
|
notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
|
|
1863
2432
|
reload();
|
|
1864
2433
|
} catch (caught) {
|
|
1865
|
-
setError(
|
|
2434
|
+
setError(errorText5(caught));
|
|
1866
2435
|
}
|
|
1867
2436
|
}
|
|
1868
2437
|
},
|
|
@@ -2073,7 +2642,7 @@ function AccountRow({
|
|
|
2073
2642
|
}
|
|
2074
2643
|
|
|
2075
2644
|
// src/client/index.tsx
|
|
2076
|
-
var VERSION = "0.
|
|
2645
|
+
var VERSION = "0.3.0";
|
|
2077
2646
|
var inject = ["slots"];
|
|
2078
2647
|
var PANEL_ID = "mailharbor";
|
|
2079
2648
|
var STYLE_ID = "mailharbor-style";
|