@zaclinley/dsh-mailharbor 0.5.1 → 0.7.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 +32 -0
- package/README.md +22 -19
- package/README.zh.md +22 -19
- package/lib/client.js +605 -528
- package/lib/client.js.map +3 -3
- package/lib/index.js +315 -472
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -35,7 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
37
|
// src/client/styles.css
|
|
38
|
-
var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > input,\n.mailharbor-field > select,\n.mailharbor-field > textarea,\n.mailharbor-card > textarea {\n width: 100%;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 10px;\n min-width: 0;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: minmax(0, 2fr) minmax(72px, 90px) auto;\n align-items: end;\n min-width: 0;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- selection / extraction ---------- */\n\n.mailharbor-selectbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n margin: 0 12px 8px;\n border: 1px solid var(--m-accent-soft);\n border-radius: 9px;\n background: var(--m-accent-soft);\n}\n\n.mailharbor-selectbar select {\n height: 28px;\n max-width: 200px;\n}\n\n.mailharbor-row.selectable input[type='checkbox'] {\n width: 15px;\n height: 15px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 9px 10px;\n border: 1px solid var(--m-line-soft);\n border-radius: 9px;\n background: var(--m-bg-1);\n cursor: pointer;\n}\n\n.mailharbor-extract-row input {\n margin-top: 2px;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row > span {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 6px 12px;\n margin: 0;\n font-size: 12.5px;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n/* ---------- tasks panel ---------- */\n\n.mailharbor .chip {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n.tasks-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 18px;\n padding: 18px 20px 48px;\n width: 100%;\n max-width: 940px;\n margin: 0 auto;\n}\n\n.tasks-overview {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));\n gap: 12px;\n}\n\n.tasks-progress,\n.tasks-project {\n display: flex;\n flex-direction: column;\n gap: 7px;\n padding: 12px 14px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n}\n\n.tasks-progress-head,\n.tasks-project-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.tasks-project-head .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 500;\n}\n\n.tasks-bar {\n height: 6px;\n border-radius: 999px;\n background: var(--m-bg-3);\n overflow: hidden;\n}\n\n.tasks-bar span {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: var(--m-accent);\n transition: width 200ms ease;\n}\n\n.tasks-bar.small {\n height: 4px;\n}\n\n.tasks-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.tasks-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tasks-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 9px 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n}\n\n.tasks-row input[type='checkbox'] {\n width: 16px;\n height: 16px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.tasks-row .tasks-title {\n flex: 1;\n min-width: 0;\n height: auto;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n text-align: left;\n border: 0;\n background: transparent;\n}\n\n.tasks-row .tasks-title:hover {\n background: transparent;\n}\n\n.tasks-row .tasks-title > span:first-child {\n font-size: 13px;\n color: var(--m-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.tasks-row.done .tasks-title > span:first-child {\n color: var(--m-text-3);\n text-decoration: line-through;\n}\n\n.tasks-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-meta .due.overdue {\n color: var(--m-danger);\n}\n\n.tasks-list-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- markdown ---------- */\n\n.mailharbor-markdown {\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.68;\n overflow-wrap: anywhere;\n}\n\n.mailharbor-markdown > *:first-child {\n margin-top: 0;\n}\n\n.mailharbor-markdown > *:last-child {\n margin-bottom: 0;\n}\n\n.mailharbor-markdown p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.mailharbor-markdown h1,\n.mailharbor-markdown h2,\n.mailharbor-markdown h3,\n.mailharbor-markdown h4,\n.mailharbor-markdown h5,\n.mailharbor-markdown h6 {\n margin: 4px 0 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.mailharbor-markdown ul,\n.mailharbor-markdown ol {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.mailharbor-markdown li::marker {\n color: var(--m-text-3);\n}\n\n.mailharbor-markdown a {\n color: var(--m-accent);\n text-decoration: none;\n}\n\n.mailharbor-markdown a:hover {\n text-decoration: underline;\n}\n\n.mailharbor-markdown code {\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--m-bg-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11.5px;\n}\n\n.mailharbor-markdown pre {\n margin: 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n overflow: auto;\n}\n\n.mailharbor-markdown pre code {\n padding: 0;\n background: transparent;\n}\n\n.mailharbor-markdown blockquote {\n margin: 0;\n padding-left: 10px;\n border-left: 2px solid var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor-markdown hr {\n width: 100%;\n margin: 4px 0;\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor.mailharbor-settings-page {\n height: auto;\n min-height: 100%;\n overflow: visible;\n}\n\n.mailharbor-settings {\n flex: 0 0 auto;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n padding: 18px 20px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n overflow-x: hidden;\n}\n\n.mailharbor-settings > * {\n min-width: 0;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n overflow: hidden;\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n";
|
|
38
|
+
var styles_default = "/* MailHarbor panel + settings.\n Tokens come from the DSH shell (dark by default); fallbacks match the\n default dark theme so the panel also renders in a bare host page.\n Component rules are all scoped under `.mailharbor` and the generic element\n reset lives in `:where()` so it cannot outrank a component class. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #151517);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #232324);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #2c2c2e);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #353638);\n --m-line: var(--dsw-alias-border-l2, #ffffff1f);\n --m-line-soft: var(--dsw-alias-border-l1, #ffffff0f);\n --m-line-strong: var(--dsw-alias-border-l3, #ffffff29);\n --m-text: var(--dsw-alias-label-primary, #f9fafb);\n --m-text-2: var(--dsw-alias-label-secondary, #cfd3d6);\n --m-text-3: var(--dsw-alias-label-tertiary, #adb2b8);\n --m-caption: var(--dsw-alias-label-caption, #81858c);\n --m-accent: #5686fe;\n --m-accent-soft: rgba(86, 134, 254, 0.14);\n --m-primary-bg: var(--dsw-alias-brand-primary, #f9fafb);\n --m-primary-fg: var(--dsw-alias-label-primary-foreground, #0f1115);\n --m-hover: var(--dsw-alias-interactive-bg-hover, #ffffff14);\n --m-danger: #f25a5a;\n --m-ok: #3fbf7f;\n --m-warn: var(--dsw-alias-state-warn-label, #dd8629);\n --m-radius: 8px;\n --m-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--m-bg);\n color: var(--m-text);\n font-size: 13px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- base controls ---------- */\n\n.mailharbor :where(button) {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n margin: 0;\n cursor: pointer;\n}\n\n.mailharbor button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n border-radius: var(--m-radius);\n border: 1px solid transparent;\n color: var(--m-text-2);\n font-size: 12.5px;\n font-weight: 500;\n white-space: nowrap;\n transition:\n background 120ms ease,\n color 120ms ease,\n border-color 120ms ease;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n color: var(--m-text);\n}\n\n.mailharbor button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-primary-bg);\n color: var(--m-primary-fg);\n border-color: transparent;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n background: var(--m-primary-bg);\n filter: brightness(0.94);\n color: var(--m-primary-fg);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor button.ghost:hover:not(:disabled) {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-3);\n padding: 0 8px;\n}\n\n.mailharbor button.icon {\n width: 30px;\n padding: 0;\n}\n\n.mailharbor button.danger {\n color: var(--m-danger);\n}\n\n.mailharbor input,\n.mailharbor select,\n.mailharbor textarea {\n font: inherit;\n color: var(--m-text);\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n padding: 0 10px;\n height: 32px;\n min-width: 0;\n}\n\n.mailharbor textarea {\n height: auto;\n padding: 8px 10px;\n resize: vertical;\n line-height: 1.6;\n}\n\n.mailharbor select {\n padding-right: 26px;\n appearance: none;\n background-image:\n linear-gradient(45deg, transparent 50%, var(--m-text-3) 50%),\n linear-gradient(135deg, var(--m-text-3) 50%, transparent 50%);\n background-position:\n calc(100% - 15px) 14px,\n calc(100% - 10px) 14px;\n background-size:\n 5px 5px,\n 5px 5px;\n background-repeat: no-repeat;\n}\n\n.mailharbor ::placeholder {\n color: var(--m-caption);\n}\n\n.mailharbor input:focus-visible,\n.mailharbor select:focus-visible,\n.mailharbor textarea:focus-visible,\n.mailharbor button:focus-visible,\n.mailharbor [contenteditable='true']:focus-visible {\n outline: 2px solid rgba(86, 134, 254, 0.55);\n outline-offset: -1px;\n}\n\n.mailharbor .spacer {\n flex: 1;\n}\n\n.mailharbor .muted {\n color: var(--m-text-3);\n}\n\n.mailharbor .small {\n font-size: 12px;\n}\n\n.mailharbor .badge {\n display: inline-flex;\n align-items: center;\n height: 18px;\n padding: 0 6px;\n border-radius: 999px;\n font-size: 10.5px;\n font-weight: 500;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: rgba(63, 191, 127, 0.16);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: rgba(221, 134, 41, 0.16);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.1);\n border-bottom: 1px solid rgba(242, 90, 90, 0.2);\n font-size: 12.5px;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n flex: none;\n}\n\n.mailharbor-account {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n max-width: 220px;\n padding: 4px 8px 4px 4px;\n border-radius: 999px;\n border: 1px solid transparent;\n cursor: default;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-account .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.mailharbor-account select {\n height: 26px;\n background: transparent;\n border-color: transparent;\n font-weight: 500;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.mailharbor button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 360px;\n flex: none;\n border-right: 1px solid var(--m-line-soft);\n min-height: 0;\n}\n\n.mailharbor-reader {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n@media (max-width: 1000px) {\n .mailharbor-list {\n width: 300px;\n }\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='reader'] .mailharbor-list {\n display: none;\n}\n\n.mailharbor-narrow .mailharbor-body[data-mode='list'] .mailharbor-reader {\n display: none;\n}\n\n/* ---------- segmented control ---------- */\n\n.mailharbor-segment {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n border-radius: 9px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-segment button {\n height: 26px;\n padding: 0 12px;\n border-radius: 7px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-segment button:hover:not(.active) {\n color: var(--m-text-2);\n background: transparent;\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters select {\n width: 128px;\n flex: none;\n font-size: 12px;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n height: 30px;\n font-size: 12.5px;\n}\n\n.mailharbor-rows {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n.mailharbor-row {\n display: flex;\n flex-direction: column;\n gap: 3px;\n width: 100%;\n padding: 10px 14px;\n border: 0;\n border-bottom: 1px solid var(--m-line-soft);\n border-left: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n user-select: none;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-accent-soft);\n border-left-color: var(--m-accent);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n width: 100%;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\n font-size: 12.5px;\n color: var(--m-text-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-row .date {\n margin-left: auto;\n color: var(--m-caption);\n font-size: 11px;\n flex: none;\n}\n\n.mailharbor-row .subject {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-caption);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: 100%;\n}\n\n.mailharbor-row.unread .from,\n.mailharbor-row.unread .subject {\n color: var(--m-text);\n}\n\n.mailharbor-row .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-accent);\n flex: none;\n}\n\n.mailharbor-row .paperclip {\n color: var(--m-caption);\n flex: none;\n display: inline-flex;\n}\n\n.mailharbor-list-foot {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--m-line-soft);\n color: var(--m-text-3);\n font-size: 11.5px;\n flex: none;\n}\n\n.mailharbor-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 48px 24px;\n color: var(--m-text-3);\n text-align: center;\n}\n\n.mailharbor-empty svg {\n opacity: 0.5;\n}\n\n.mailharbor-empty .title {\n color: var(--m-text-2);\n font-size: 13px;\n font-weight: 500;\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 16px 20px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.mailharbor-reader-title {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n}\n\n.mailharbor-reader-title h2 {\n margin: 0;\n font-size: 17px;\n line-height: 1.35;\n font-weight: 600;\n letter-spacing: -0.01em;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 10px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor-reader-meta .who {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: var(--m-text-2);\n}\n\n.mailharbor-reader-meta .who strong {\n color: var(--m-text);\n font-weight: 600;\n}\n\n.mailharbor-reader-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.mailharbor-reader-body {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 14px 18px 18px;\n background: var(--m-bg-2);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 20px 22px;\n white-space: pre-wrap;\n word-break: break-word;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n color: #171a21;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12.5px;\n line-height: 1.7;\n}\n\n.mailharbor-attachments {\n padding: 10px 18px;\n border-top: 1px solid var(--m-line-soft);\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex: none;\n background: var(--m-bg);\n}\n\n.mailharbor-attachment {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 30px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: var(--m-radius);\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text-2);\n max-width: 260px;\n background: var(--m-bg-1);\n}\n\n.mailharbor-attachment:hover {\n border-color: var(--m-line-strong);\n color: var(--m-text);\n}\n\n.mailharbor-attachment .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- AI rail ---------- */\n\n.mailharbor-ai {\n display: flex;\n flex-direction: column;\n width: 340px;\n flex: none;\n min-height: 0;\n border-left: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-ai-head {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 46px;\n padding: 0 8px 0 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-head strong {\n font-size: 13px;\n font-weight: 600;\n}\n\n.mailharbor-ai-confirm {\n display: inline-flex;\n gap: 2px;\n}\n\n.mailharbor-ai-context {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 9px 14px 10px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-context strong {\n font-size: 12.5px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-ai-warn {\n padding: 8px 14px;\n color: var(--m-warn);\n background: rgba(221, 134, 41, 0.1);\n font-size: 12px;\n flex: none;\n}\n\n.mailharbor-ai-quick {\n display: flex;\n gap: 6px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-quick button {\n flex: 1;\n padding: 0 6px;\n}\n\n.mailharbor-ai-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.mailharbor-ai-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 30px 12px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-empty .title {\n color: var(--m-text-2);\n font-weight: 500;\n font-size: 12.5px;\n}\n\n.mailharbor-ai-card {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n overflow: hidden;\n}\n\n.mailharbor-ai-card.generating {\n opacity: 0.7;\n}\n\n.mailharbor-ai-card-head {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 8px 7px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-ai-card-head > span:first-child {\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-ai-summary {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 12px;\n}\n\n.mailharbor-ai-summary section h4 {\n margin: 0 0 3px;\n font-size: 11px;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.mailharbor-ai-summary section p {\n margin: 0;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-text {\n padding: 10px 12px;\n max-height: 320px;\n overflow: auto;\n white-space: pre-wrap;\n font-size: 12.5px;\n line-height: 1.65;\n color: var(--m-text);\n}\n\n.mailharbor-ai-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n padding: 6px 8px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-ai-card-actions button {\n height: 26px;\n padding: 0 8px;\n font-size: 11.5px;\n}\n\n.mailharbor-ai-ask {\n display: flex;\n gap: 6px;\n padding: 10px 12px;\n border-top: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-ai-ask input {\n flex: 1;\n height: 32px;\n}\n\n.mailharbor-ai-error {\n color: var(--m-danger);\n font-size: 12px;\n padding: 0 2px;\n}\n\n@media (max-width: 1280px) {\n .mailharbor-ai {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(420px, 94%);\n z-index: 6;\n border-left: 1px solid var(--m-line);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- AI summary ---------- */\n\n.mailharbor-divider {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--m-line);\n}\n\n.mailharbor-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.mailharbor-summary section h4 {\n margin: 0 0 6px;\n font-size: 11.5px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-summary section p {\n margin: 0;\n white-space: pre-wrap;\n line-height: 1.75;\n color: var(--m-text);\n}\n\n/* ---------- modal / forms ---------- */\n\n.mailharbor-portal {\n position: fixed;\n inset: 0;\n z-index: 80;\n height: auto;\n background: transparent;\n pointer-events: none;\n}\n\n.mailharbor-portal .mailharbor-modal-mask {\n pointer-events: auto;\n}\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 60;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.55);\n backdrop-filter: blur(2px);\n}\n\n.mailharbor-modal {\n display: flex;\n flex-direction: column;\n width: min(720px, 100%);\n max-height: 86vh;\n overflow: hidden;\n border: 1px solid var(--m-line);\n border-radius: 14px;\n background: var(--m-bg-1);\n color: var(--m-text);\n box-shadow: var(--m-shadow);\n}\n\n.mailharbor-modal > header {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 52px;\n padding: 0 14px 0 18px;\n border-bottom: 1px solid var(--m-line-soft);\n font-size: 14px;\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 18px;\n overflow: auto;\n}\n\n.mailharbor-modal > footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 18px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.mailharbor-field > input,\n.mailharbor-field > select,\n.mailharbor-field > textarea,\n.mailharbor-card > textarea {\n width: 100%;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 10px;\n min-width: 0;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: minmax(0, 2fr) minmax(72px, 90px) auto;\n align-items: end;\n min-width: 0;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: var(--m-text-2);\n font-size: 12.5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.mailharbor-toggle input[type='checkbox'] {\n width: 14px;\n height: 14px;\n accent-color: var(--m-accent);\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-toolbar {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n padding: 6px;\n border: 1px solid var(--m-line-soft);\n border-bottom: 0;\n border-radius: 10px 10px 0 0;\n background: var(--m-bg-2);\n}\n\n.mailharbor-toolbar button {\n height: 26px;\n padding: 0 9px;\n border-radius: 6px;\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-toolbar button:hover {\n background: var(--m-bg-3);\n color: var(--m-text);\n}\n\n.mailharbor-editor {\n min-height: 240px;\n max-height: 44vh;\n overflow: auto;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 0 0 10px 10px;\n background: #fff;\n color: #171a21;\n outline: none;\n font-size: 13.5px;\n line-height: 1.7;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: #9aa0ab;\n}\n\n/* ---------- inline confirm ---------- */\n\n.mailharbor-confirm {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--m-line);\n background: var(--m-bg-2);\n color: var(--m-text-2);\n font-size: 12.5px;\n}\n\n/* ---------- selection / extraction ---------- */\n\n.mailharbor-selectbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n margin: 0 12px 8px;\n border: 1px solid var(--m-accent-soft);\n border-radius: 9px;\n background: var(--m-accent-soft);\n}\n\n.mailharbor-selectbar select {\n height: 28px;\n max-width: 200px;\n}\n\n.mailharbor-row.selectable input[type='checkbox'] {\n width: 15px;\n height: 15px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 9px 10px;\n border: 1px solid var(--m-line-soft);\n border-radius: 9px;\n background: var(--m-bg-1);\n cursor: pointer;\n}\n\n.mailharbor-extract-row input {\n margin-top: 2px;\n accent-color: var(--m-accent);\n}\n\n.mailharbor-extract-row > span {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n gap: 6px 12px;\n margin: 0;\n font-size: 12.5px;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n/* ---------- tasks panel ---------- */\n\n.mailharbor .chip {\n height: 26px;\n padding: 0 10px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 12px;\n}\n\n.mailharbor .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n.tasks-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 18px;\n padding: 18px 20px 48px;\n width: 100%;\n max-width: 940px;\n margin: 0 auto;\n}\n\n.tasks-overview {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));\n gap: 12px;\n}\n\n.tasks-progress,\n.tasks-project {\n display: flex;\n flex-direction: column;\n gap: 7px;\n padding: 12px 14px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n}\n\n.tasks-progress-head,\n.tasks-project-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.tasks-project-head .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n font-weight: 500;\n}\n\n.tasks-bar {\n height: 6px;\n border-radius: 999px;\n background: var(--m-bg-3);\n overflow: hidden;\n}\n\n.tasks-bar span {\n display: block;\n height: 100%;\n border-radius: 999px;\n background: var(--m-accent);\n transition: width 200ms ease;\n}\n\n.tasks-bar.small {\n height: 4px;\n}\n\n.tasks-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.tasks-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.tasks-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 9px 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n}\n\n.tasks-row input[type='checkbox'] {\n width: 16px;\n height: 16px;\n flex: none;\n accent-color: var(--m-accent);\n}\n\n.tasks-row .tasks-title {\n flex: 1;\n min-width: 0;\n height: auto;\n padding: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n text-align: left;\n border: 0;\n background: transparent;\n}\n\n.tasks-row .tasks-title:hover {\n background: transparent;\n}\n\n.tasks-row .tasks-title > span:first-child {\n font-size: 13px;\n color: var(--m-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 100%;\n}\n\n.tasks-row.done .tasks-title > span:first-child {\n color: var(--m-text-3);\n text-decoration: line-through;\n}\n\n.tasks-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-meta .due.overdue {\n color: var(--m-danger);\n}\n\n.tasks-list-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* ---------- markdown ---------- */\n\n.mailharbor-markdown {\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 12.5px;\n line-height: 1.68;\n overflow-wrap: anywhere;\n}\n\n.mailharbor-markdown > *:first-child {\n margin-top: 0;\n}\n\n.mailharbor-markdown > *:last-child {\n margin-bottom: 0;\n}\n\n.mailharbor-markdown p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.mailharbor-markdown h1,\n.mailharbor-markdown h2,\n.mailharbor-markdown h3,\n.mailharbor-markdown h4,\n.mailharbor-markdown h5,\n.mailharbor-markdown h6 {\n margin: 4px 0 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.mailharbor-markdown ul,\n.mailharbor-markdown ol {\n margin: 0;\n padding-left: 20px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.mailharbor-markdown li::marker {\n color: var(--m-text-3);\n}\n\n.mailharbor-markdown a {\n color: var(--m-accent);\n text-decoration: none;\n}\n\n.mailharbor-markdown a:hover {\n text-decoration: underline;\n}\n\n.mailharbor-markdown code {\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--m-bg-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11.5px;\n}\n\n.mailharbor-markdown pre {\n margin: 0;\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n overflow: auto;\n}\n\n.mailharbor-markdown pre code {\n padding: 0;\n background: transparent;\n}\n\n.mailharbor-markdown blockquote {\n margin: 0;\n padding-left: 10px;\n border-left: 2px solid var(--m-line);\n color: var(--m-text-2);\n}\n\n.mailharbor-markdown hr {\n width: 100%;\n margin: 4px 0;\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n}\n\n/* ---------- composer AI ---------- */\n\n.mailharbor-toolbar button.ai {\n color: var(--m-accent);\n}\n\n.mailharbor-compose-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n flex-wrap: wrap;\n}\n\n.mailharbor-versions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n padding-top: 8px;\n}\n\n.mailharbor-versions .chip {\n height: 22px;\n padding: 0 8px;\n border: 1px solid var(--m-line);\n border-radius: 999px;\n color: var(--m-text-3);\n font-size: 11px;\n}\n\n.mailharbor-versions .chip.active {\n background: var(--m-accent-soft);\n border-color: transparent;\n color: var(--m-text);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor.mailharbor-settings-page {\n height: auto;\n min-height: 100%;\n overflow: visible;\n}\n\n.mailharbor-settings {\n flex: 0 0 auto;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n padding: 18px 20px 48px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n overflow-x: hidden;\n}\n\n.mailharbor-settings > * {\n min-width: 0;\n}\n\n.mailharbor-settings h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n}\n\n.mailharbor-settings h3 {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--m-text-3);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n.mailharbor-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n min-width: 0;\n overflow: hidden;\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .meta {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.mailharbor-account-row .meta strong {\n font-weight: 600;\n}\n\n.mailharbor-account-row .meta span {\n color: var(--m-text-3);\n font-size: 11.5px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor .avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 50%;\n background: var(--m-accent-soft);\n color: var(--m-accent);\n font-weight: 600;\n}\n\n.mailharbor-log {\n padding: 10px 12px;\n border-radius: 8px;\n background: var(--m-bg);\n border: 1px solid var(--m-line-soft);\n color: var(--m-text-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.6;\n max-height: 220px;\n overflow: auto;\n white-space: pre-wrap;\n}\n\n.mailharbor-kv {\n display: grid;\n grid-template-columns: minmax(120px, 170px) 1fr;\n gap: 6px 14px;\n font-size: 12px;\n margin: 0;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n color: var(--m-text-2);\n word-break: break-all;\n}\n\n.mailharbor hr {\n border: 0;\n border-top: 1px solid var(--m-line-soft);\n margin: 2px 0;\n}\n\n/* ---------- conversation rows ---------- */\n\n.mailharbor-thread {\n display: flex;\n flex-direction: column;\n}\n\n.mailharbor-row.compact {\n padding-top: 6px;\n padding-bottom: 6px;\n padding-left: 34px;\n background: var(--m-bg);\n}\n\n.mailharbor-row.compact .line-1 {\n opacity: 0.85;\n}\n\n.mailharbor-row.compact .snippet {\n -webkit-line-clamp: 1;\n}\n\n.mailharbor .to-label {\n font-size: 10.5px;\n color: var(--m-text-3);\n flex: none;\n}\n\n.mailharbor .thread-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 999px;\n background: var(--m-bg-3);\n color: var(--m-text-2);\n font-size: 10.5px;\n flex: none;\n}\n\n.mailharbor .thread-count:hover {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-account {\n height: auto;\n padding: 3px 8px 3px 3px;\n border-radius: 8px;\n gap: 8px;\n}\n\n.mailharbor-account:hover {\n background: var(--m-hover);\n}\n\n/* ---------- mailbox sidebar ---------- */\n\n.mailharbor-side {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 214px;\n flex: none;\n padding: 10px 8px;\n overflow: auto;\n border-right: 1px solid var(--m-line-soft);\n background: var(--m-bg);\n}\n\n.mailharbor-side-section {\n padding: 12px 8px 4px;\n font-size: 10.5px;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--m-text-3);\n}\n\n.mailharbor-side-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: 32px;\n padding: 0 8px;\n border-radius: 8px;\n color: var(--m-text-2);\n font-size: 12.5px;\n text-align: left;\n flex: none;\n}\n\n.mailharbor-side-item:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-side-item.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.mailharbor-side-item .name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-side-item .sub {\n display: block;\n font-size: 10.5px;\n color: var(--m-text-3);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mailharbor-side-item .count {\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.mailharbor-side-item svg {\n flex: none;\n color: var(--m-text-3);\n}\n\n.mailharbor-side-item.active svg {\n color: var(--m-accent);\n}\n\n@media (max-width: 900px) {\n .mailharbor-side {\n display: none;\n }\n}\n\n/* ---------- task board ---------- */\n\n.tasks-board-wrap {\n flex: 1;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px 18px 40px;\n}\n\n.tasks-summary {\n display: flex;\n align-items: center;\n gap: 20px;\n padding: 14px 16px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.tasks-summary-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 7px;\n}\n\n.tasks-summary-labels {\n display: flex;\n align-items: baseline;\n gap: 10px;\n}\n\n.tasks-stats {\n display: flex;\n gap: 18px;\n}\n\n.tasks-stat {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 42px;\n}\n\n.tasks-stat strong {\n font-size: 16px;\n font-weight: 600;\n color: var(--m-text);\n}\n\n.tasks-stat span {\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.tasks-stat.danger strong {\n color: var(--m-danger);\n}\n\n.tasks-toolbar {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.tasks-board {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n gap: 14px;\n align-items: start;\n}\n\n.task-column {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-height: 140px;\n padding: 8px;\n border: 1px solid var(--m-line-soft);\n border-radius: 12px;\n background: var(--m-bg-1);\n}\n\n.task-column-head {\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 2px 4px 6px;\n}\n\n.task-column-head strong {\n font-size: 12.5px;\n font-weight: 600;\n}\n\n.task-column-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 999px;\n background: var(--m-bg-3);\n color: var(--m-text-3);\n font-size: 10.5px;\n}\n\n.task-dot {\n width: 7px;\n height: 7px;\n border-radius: 999px;\n background: var(--m-text-3);\n}\n\n.task-dot.NEEDS-ACTION {\n background: #8a8f98;\n}\n\n.task-dot.IN-PROCESS {\n background: var(--m-accent);\n}\n\n.task-dot.COMPLETED {\n background: var(--m-ok);\n}\n\n.task-dot.CANCELLED {\n background: var(--m-danger);\n opacity: 0.7;\n}\n\n.task-column-body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-height: 60px;\n}\n\n.task-column-empty {\n padding: 18px 8px;\n text-align: center;\n color: var(--m-text-3);\n font-size: 11.5px;\n border: 1px dashed var(--m-line-soft);\n border-radius: 9px;\n}\n\n.task-card {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 11px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg);\n cursor: grab;\n transition:\n border-color 120ms ease,\n box-shadow 120ms ease;\n}\n\n.task-card:hover {\n border-color: var(--m-line);\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16);\n}\n\n.task-card.dragging {\n opacity: 0.5;\n}\n\n.task-card-top {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n}\n\n.task-card-title {\n flex: 1;\n min-width: 0;\n font-size: 12.5px;\n line-height: 1.45;\n color: var(--m-text);\n overflow-wrap: anywhere;\n}\n\n.task-priority {\n width: 3px;\n height: 14px;\n margin-top: 2px;\n border-radius: 999px;\n background: transparent;\n flex: none;\n}\n\n.task-priority.p5 {\n background: #d9a441;\n}\n\n.task-priority.p9 {\n background: var(--m-danger);\n}\n\n.task-card-delete {\n width: 18px;\n height: 18px;\n flex: none;\n border-radius: 5px;\n color: var(--m-text-3);\n opacity: 0;\n}\n\n.task-card:hover .task-card-delete {\n opacity: 1;\n}\n\n.task-card-notes {\n margin: 0;\n font-size: 11.5px;\n line-height: 1.55;\n color: var(--m-text-3);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.task-card-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 10.5px;\n color: var(--m-text-3);\n}\n\n.task-due,\n.task-owner,\n.task-project,\n.task-priority-label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 1px 6px;\n border-radius: 5px;\n background: var(--m-bg-2);\n}\n\n.task-due.overdue {\n color: var(--m-danger);\n background: rgba(242, 90, 90, 0.12);\n}\n\n.task-project {\n color: var(--m-accent);\n background: var(--m-accent-soft);\n}\n\n.task-source {\n align-self: flex-start;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 22px;\n padding: 0 6px;\n border-radius: 5px;\n color: var(--m-text-3);\n font-size: 10.5px;\n}\n\n.task-source:hover {\n background: var(--m-hover);\n color: var(--m-text-2);\n}\n\n@media (max-width: 1180px) {\n .tasks-board {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n}\n\n@media (max-width: 720px) {\n .tasks-board {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n/* ---------- AI search ---------- */\n\n.mailharbor-ai-search {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 12px 8px;\n padding: 7px 10px;\n border-radius: 8px;\n background: var(--m-accent-soft);\n color: var(--m-text-2);\n font-size: 12px;\n}\n\n.mailharbor-ai-search.searching {\n background: var(--m-bg-2);\n color: var(--m-text-3);\n}\n\n/* ---------- task editor ---------- */\n\n.mailharbor-tasks .mailharbor-modal {\n max-width: 760px;\n}\n\n.task-editor {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 230px;\n gap: 18px;\n}\n\n.task-editor-main {\n display: flex;\n flex-direction: column;\n gap: 14px;\n min-width: 0;\n}\n\n.task-editor-title input {\n width: 100%;\n height: 38px;\n font-size: 15px;\n font-weight: 500;\n}\n\n.task-editor-section {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.task-editor-section label,\n.task-editor-field label {\n font-size: 11px;\n color: var(--m-text-3);\n}\n\n.task-editor-section textarea {\n width: 100%;\n resize: vertical;\n}\n\n.task-editor-side {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 12px;\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n background: var(--m-bg-1);\n align-self: start;\n}\n\n.task-editor-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n min-width: 0;\n}\n\n.task-editor-field input,\n.task-editor-field select {\n width: 100%;\n}\n\n.task-priority-picker {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 4px;\n padding: 3px;\n border: 1px solid var(--m-line);\n border-radius: 8px;\n background: var(--m-bg);\n}\n\n.task-priority-picker button {\n height: 26px;\n border-radius: 6px;\n font-size: 11.5px;\n color: var(--m-text-3);\n}\n\n.task-priority-picker button.active {\n background: var(--m-accent-soft);\n color: var(--m-text);\n}\n\n.task-editor-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n}\n";
|
|
39
39
|
|
|
40
40
|
// src/client/icon.tsx
|
|
41
41
|
var import_react = require("react");
|
|
@@ -126,6 +126,10 @@ var api = {
|
|
|
126
126
|
check: (id) => request(`accounts/${encodeURIComponent(id)}/check`, { method: "POST" }),
|
|
127
127
|
aiStatus: (accountId) => request(`ai/status${query({ accountId: accountId ?? null })}`),
|
|
128
128
|
aiModels: () => request("ai/models"),
|
|
129
|
+
aiSearch: (payload) => request("ai/search", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: JSON.stringify(payload)
|
|
132
|
+
}),
|
|
129
133
|
aiThread: (accountId, key) => request(`ai/thread${query({ accountId: accountId ?? null, key })}`),
|
|
130
134
|
aiAddCard: (payload) => request("ai/cards", {
|
|
131
135
|
method: "POST",
|
|
@@ -182,6 +186,7 @@ var api = {
|
|
|
182
186
|
}),
|
|
183
187
|
syncTasks: (accountId) => request("tasks/sync", { method: "POST", body: JSON.stringify({ accountId }) }),
|
|
184
188
|
projects: () => request("projects"),
|
|
189
|
+
pickDirectory: () => request("projects/pick-directory", { method: "POST" }),
|
|
185
190
|
projectFor: (accountId, key) => request(`projects/for${query({ accountId: accountId ?? null, key })}`),
|
|
186
191
|
createProject: (payload) => request("projects", {
|
|
187
192
|
method: "POST",
|
|
@@ -499,6 +504,12 @@ var PATHS = {
|
|
|
499
504
|
folder: (0, import_react3.createElement)("path", {
|
|
500
505
|
d: "M3.5 6.5A1.5 1.5 0 0 1 5 5h4l2 2.5h8A1.5 1.5 0 0 1 20.5 9v8.5A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5z"
|
|
501
506
|
}),
|
|
507
|
+
calendar: (0, import_react3.createElement)(
|
|
508
|
+
"g",
|
|
509
|
+
null,
|
|
510
|
+
(0, import_react3.createElement)("rect", { x: 3.5, y: 5, width: 17, height: 15.5, rx: 2 }),
|
|
511
|
+
(0, import_react3.createElement)("path", { d: "M3.5 9.5h17M8 3.5v3M16 3.5v3" })
|
|
512
|
+
),
|
|
502
513
|
checklist: (0, import_react3.createElement)(
|
|
503
514
|
"g",
|
|
504
515
|
null,
|
|
@@ -1096,10 +1107,28 @@ ${card.output}`
|
|
|
1096
1107
|
"div",
|
|
1097
1108
|
{ className: "mailharbor-field" },
|
|
1098
1109
|
(0, import_react5.createElement)("label", null, "\u9879\u76EE\u76EE\u5F55\uFF08\u9ED8\u8BA4\u53EF\u5728\u8BBE\u7F6E\u91CC\u6539\uFF09"),
|
|
1099
|
-
(0, import_react5.createElement)(
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1110
|
+
(0, import_react5.createElement)(
|
|
1111
|
+
"div",
|
|
1112
|
+
{ style: { display: "flex", gap: "6px" } },
|
|
1113
|
+
(0, import_react5.createElement)("input", {
|
|
1114
|
+
value: projectRoot,
|
|
1115
|
+
onChange: (event) => setProjectRoot(event.target.value)
|
|
1116
|
+
}),
|
|
1117
|
+
(0, import_react5.createElement)(
|
|
1118
|
+
"button",
|
|
1119
|
+
{
|
|
1120
|
+
className: "ghost",
|
|
1121
|
+
title: "\u4ECE\u6587\u4EF6\u7BA1\u7406\u5668\u9009\u62E9",
|
|
1122
|
+
onClick: () => {
|
|
1123
|
+
void api.pickDirectory().then((result) => {
|
|
1124
|
+
if (result.path !== null) setProjectRoot(result.path);
|
|
1125
|
+
}).catch((caught) => setProjectWarnings([errorText(caught)]));
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
(0, import_react5.createElement)(Icon, { name: "folder", size: 13 }),
|
|
1129
|
+
"\u6D4F\u89C8\u2026"
|
|
1130
|
+
)
|
|
1131
|
+
)
|
|
1103
1132
|
),
|
|
1104
1133
|
(0, import_react5.createElement)(
|
|
1105
1134
|
"p",
|
|
@@ -2080,6 +2109,43 @@ function consumeOpenMail() {
|
|
|
2080
2109
|
// src/client/panel.tsx
|
|
2081
2110
|
var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
|
|
2082
2111
|
var DRAFTS = "__drafts";
|
|
2112
|
+
var folderCache = /* @__PURE__ */ new Map();
|
|
2113
|
+
var pageCache = /* @__PURE__ */ new Map();
|
|
2114
|
+
var FOLDER_LABELS = {
|
|
2115
|
+
INBOX: "\u6536\u4EF6\u7BB1",
|
|
2116
|
+
"Sent Items": "\u5DF2\u53D1\u9001",
|
|
2117
|
+
Sent: "\u5DF2\u53D1\u9001",
|
|
2118
|
+
Drafts: "\u8349\u7A3F",
|
|
2119
|
+
"Junk E-mail": "\u5783\u573E\u90AE\u4EF6",
|
|
2120
|
+
Junk: "\u5783\u573E\u90AE\u4EF6",
|
|
2121
|
+
Trash: "\u5DF2\u5220\u9664",
|
|
2122
|
+
Deleted: "\u5DF2\u5220\u9664",
|
|
2123
|
+
Archive: "\u5B58\u6863"
|
|
2124
|
+
};
|
|
2125
|
+
function folderName(name) {
|
|
2126
|
+
return FOLDER_LABELS[name] ?? name;
|
|
2127
|
+
}
|
|
2128
|
+
function isSentFolder(mailbox) {
|
|
2129
|
+
return /(^|[/.])(sent|sent items|sent mail|已发送|发件箱)/i.test(mailbox);
|
|
2130
|
+
}
|
|
2131
|
+
function normalizeSubject(subject) {
|
|
2132
|
+
return subject.replace(/^(\s*(re|fw|fwd|回复|转发)\s*[::]\s*)+/gi, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
2133
|
+
}
|
|
2134
|
+
function groupMessages(items) {
|
|
2135
|
+
const map = /* @__PURE__ */ new Map();
|
|
2136
|
+
const order = [];
|
|
2137
|
+
for (const item of items) {
|
|
2138
|
+
const key = normalizeSubject(item.subject) === "" ? `__${item.accountId}:${item.id}` : normalizeSubject(item.subject);
|
|
2139
|
+
const existing = map.get(key);
|
|
2140
|
+
if (existing === void 0) {
|
|
2141
|
+
map.set(key, [item]);
|
|
2142
|
+
order.push(key);
|
|
2143
|
+
} else {
|
|
2144
|
+
existing.push(item);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
return order.map((key) => ({ key, items: map.get(key) ?? [] }));
|
|
2148
|
+
}
|
|
2083
2149
|
function errorText5(error) {
|
|
2084
2150
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
2085
2151
|
}
|
|
@@ -2094,7 +2160,7 @@ function MailPanel() {
|
|
|
2094
2160
|
const [state, setState] = (0, import_react9.useState)(null);
|
|
2095
2161
|
const [error, setError] = (0, import_react9.useState)("");
|
|
2096
2162
|
const [accountScope, setAccountScope] = (0, import_react9.useState)(null);
|
|
2097
|
-
const [source, setSource] = (0, import_react9.useState)("
|
|
2163
|
+
const [source, setSource] = (0, import_react9.useState)("server");
|
|
2098
2164
|
const [mailbox, setMailbox] = (0, import_react9.useState)("all");
|
|
2099
2165
|
const [folders, setFolders] = (0, import_react9.useState)([]);
|
|
2100
2166
|
const [query2, setQuery] = (0, import_react9.useState)("");
|
|
@@ -2112,11 +2178,21 @@ function MailPanel() {
|
|
|
2112
2178
|
const [narrow, setNarrow] = (0, import_react9.useState)(() => matchesNarrow());
|
|
2113
2179
|
const [railOpen, setRailOpen] = (0, import_react9.useState)(false);
|
|
2114
2180
|
const [selectMode, setSelectMode] = (0, import_react9.useState)(false);
|
|
2181
|
+
const [sideOpen, setSideOpen] = (0, import_react9.useState)(() => {
|
|
2182
|
+
try {
|
|
2183
|
+
return window.localStorage.getItem("mailharbor.side") !== "0";
|
|
2184
|
+
} catch {
|
|
2185
|
+
return true;
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
const [expandedGroups, setExpandedGroups] = (0, import_react9.useState)([]);
|
|
2115
2189
|
const [selectedKeys, setSelectedKeys] = (0, import_react9.useState)([]);
|
|
2116
2190
|
const [projectOptions, setProjectOptions] = (0, import_react9.useState)([]);
|
|
2117
2191
|
const [targetProject, setTargetProject] = (0, import_react9.useState)("");
|
|
2118
2192
|
const [extraction, setExtraction] = (0, import_react9.useState)(null);
|
|
2119
2193
|
const [extractBusy, setExtractBusy] = (0, import_react9.useState)(false);
|
|
2194
|
+
const [aiInfo, setAiInfo] = (0, import_react9.useState)(null);
|
|
2195
|
+
const [aiSearching, setAiSearching] = (0, import_react9.useState)(false);
|
|
2120
2196
|
const [aiRequest, setAiRequest] = (0, import_react9.useState)(null);
|
|
2121
2197
|
const [tick, setTick] = (0, import_react9.useState)(0);
|
|
2122
2198
|
const accounts = state?.accounts ?? [];
|
|
@@ -2161,6 +2237,44 @@ function MailPanel() {
|
|
|
2161
2237
|
setError(errorText5(caught));
|
|
2162
2238
|
}
|
|
2163
2239
|
};
|
|
2240
|
+
const runAiSearch = async () => {
|
|
2241
|
+
const text = query2.trim();
|
|
2242
|
+
if (text === "" || accountScope === null) return;
|
|
2243
|
+
setAiSearching(true);
|
|
2244
|
+
setError("");
|
|
2245
|
+
try {
|
|
2246
|
+
const result = await Promise.race([
|
|
2247
|
+
api.aiSearch({ accountId: accountScope, source, mailbox, query: text }),
|
|
2248
|
+
new Promise((_resolve, reject) => {
|
|
2249
|
+
window.setTimeout(() => reject(new Error("AI \u641C\u7D22\u8D85\u65F6\uFF0C\u5DF2\u6539\u7528\u666E\u901A\u641C\u7D22")), 45e3);
|
|
2250
|
+
})
|
|
2251
|
+
]);
|
|
2252
|
+
setPage({ items: result.items, total: result.total, offset: 0, limit: result.limit });
|
|
2253
|
+
setDrafts([]);
|
|
2254
|
+
setAiInfo(result.ai);
|
|
2255
|
+
setSearch("");
|
|
2256
|
+
} catch (caught) {
|
|
2257
|
+
setError(errorText5(caught));
|
|
2258
|
+
setAiInfo(null);
|
|
2259
|
+
setSearch(text);
|
|
2260
|
+
} finally {
|
|
2261
|
+
setAiSearching(false);
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2264
|
+
const toggleSide = () => {
|
|
2265
|
+
setSideOpen((open) => {
|
|
2266
|
+
try {
|
|
2267
|
+
window.localStorage.setItem("mailharbor.side", open ? "0" : "1");
|
|
2268
|
+
} catch {
|
|
2269
|
+
}
|
|
2270
|
+
return !open;
|
|
2271
|
+
});
|
|
2272
|
+
};
|
|
2273
|
+
const toggleGroup = (key) => {
|
|
2274
|
+
setExpandedGroups(
|
|
2275
|
+
(current) => current.includes(key) ? current.filter((entry) => entry !== key) : [...current, key]
|
|
2276
|
+
);
|
|
2277
|
+
};
|
|
2164
2278
|
const toggleSelect = (item) => {
|
|
2165
2279
|
const key = `${item.accountId}:${item.id}`;
|
|
2166
2280
|
setSelectedKeys(
|
|
@@ -2263,6 +2377,9 @@ function MailPanel() {
|
|
|
2263
2377
|
(0, import_react9.useEffect)(() => {
|
|
2264
2378
|
if (accountScope === null) return;
|
|
2265
2379
|
let cancelled = false;
|
|
2380
|
+
const cacheKey = `${accountScope}:${source}`;
|
|
2381
|
+
const cached = folderCache.get(cacheKey);
|
|
2382
|
+
if (cached !== void 0) setFolders(cached);
|
|
2266
2383
|
api.folders({ accountId: accountScope, source }).then((result) => {
|
|
2267
2384
|
if (cancelled) return;
|
|
2268
2385
|
const items2 = source === "local" ? [
|
|
@@ -2277,8 +2394,9 @@ function MailPanel() {
|
|
|
2277
2394
|
}))
|
|
2278
2395
|
] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
|
|
2279
2396
|
setFolders(items2);
|
|
2397
|
+
folderCache.set(cacheKey, items2);
|
|
2280
2398
|
}).catch(() => {
|
|
2281
|
-
if (!cancelled) setFolders([]);
|
|
2399
|
+
if (!cancelled && cached === void 0) setFolders([]);
|
|
2282
2400
|
});
|
|
2283
2401
|
return () => {
|
|
2284
2402
|
cancelled = true;
|
|
@@ -2287,11 +2405,15 @@ function MailPanel() {
|
|
|
2287
2405
|
(0, import_react9.useEffect)(() => {
|
|
2288
2406
|
setMailbox(source === "local" ? "all" : "INBOX");
|
|
2289
2407
|
setSelected(null);
|
|
2408
|
+
setAiInfo(null);
|
|
2409
|
+
setSearch("");
|
|
2290
2410
|
setLimit(state?.settings.render.listLimit ?? 40);
|
|
2291
2411
|
}, [source]);
|
|
2292
2412
|
(0, import_react9.useEffect)(() => {
|
|
2293
2413
|
if (accountScope === "all" && source === "server") setSource("local");
|
|
2294
2414
|
setSelected(null);
|
|
2415
|
+
setAiInfo(null);
|
|
2416
|
+
setSearch("");
|
|
2295
2417
|
}, [accountScope, source]);
|
|
2296
2418
|
(0, import_react9.useEffect)(() => {
|
|
2297
2419
|
if (accountScope === null) return;
|
|
@@ -2308,9 +2430,9 @@ function MailPanel() {
|
|
|
2308
2430
|
return [];
|
|
2309
2431
|
}
|
|
2310
2432
|
})
|
|
2311
|
-
).then((
|
|
2433
|
+
).then((groups2) => {
|
|
2312
2434
|
if (cancelled) return;
|
|
2313
|
-
setDrafts(
|
|
2435
|
+
setDrafts(groups2.flat().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
|
|
2314
2436
|
setPage(EMPTY_PAGE);
|
|
2315
2437
|
}).finally(() => {
|
|
2316
2438
|
if (!cancelled) setLoading(false);
|
|
@@ -2319,10 +2441,28 @@ function MailPanel() {
|
|
|
2319
2441
|
cancelled = true;
|
|
2320
2442
|
};
|
|
2321
2443
|
}
|
|
2444
|
+
const pageKey = `${accountScope}:${source}:${mailbox}:${search}:${String(limit)}`;
|
|
2445
|
+
const cachedPage = pageCache.get(pageKey);
|
|
2446
|
+
if (cachedPage !== void 0) {
|
|
2447
|
+
setPage(cachedPage);
|
|
2448
|
+
setLoading(false);
|
|
2449
|
+
}
|
|
2322
2450
|
api.messages({ accountId: accountScope, source, mailbox, query: search, limit }).then((result) => {
|
|
2323
2451
|
if (!cancelled) {
|
|
2324
2452
|
setPage(result);
|
|
2325
2453
|
setDrafts([]);
|
|
2454
|
+
if (source === "server" && mailbox !== "all") {
|
|
2455
|
+
const update = (list) => list.map((folder) => folder.name === mailbox ? { ...folder, count: result.total } : folder);
|
|
2456
|
+
setFolders(update);
|
|
2457
|
+
const key = `${accountScope}:server`;
|
|
2458
|
+
const cachedFolders = folderCache.get(key);
|
|
2459
|
+
if (cachedFolders !== void 0) folderCache.set(key, update(cachedFolders));
|
|
2460
|
+
}
|
|
2461
|
+
pageCache.set(pageKey, result);
|
|
2462
|
+
if (pageCache.size > 12) {
|
|
2463
|
+
const oldest = pageCache.keys().next().value;
|
|
2464
|
+
if (oldest !== void 0) pageCache.delete(oldest);
|
|
2465
|
+
}
|
|
2326
2466
|
}
|
|
2327
2467
|
}).catch((caught) => {
|
|
2328
2468
|
if (!cancelled) setError(errorText5(caught));
|
|
@@ -2510,18 +2650,11 @@ function MailPanel() {
|
|
|
2510
2650
|
setError(errorText5(caught));
|
|
2511
2651
|
}
|
|
2512
2652
|
};
|
|
2513
|
-
const folderOptions = (0, import_react9.useMemo)(
|
|
2514
|
-
() => folders.map((folder) => ({
|
|
2515
|
-
value: folder.name,
|
|
2516
|
-
label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
|
|
2517
|
-
})),
|
|
2518
|
-
[folders]
|
|
2519
|
-
);
|
|
2520
2653
|
if (state === null) {
|
|
2521
2654
|
return (0, import_react9.createElement)(
|
|
2522
2655
|
"div",
|
|
2523
2656
|
{ className: "mailharbor" },
|
|
2524
|
-
(0, import_react9.createElement)("div", { className: "mailharbor-
|
|
2657
|
+
(0, import_react9.createElement)("div", { className: "mailharbor-settings" }, "\u6B63\u5728\u8BFB\u53D6\u90AE\u7BB1\u2026")
|
|
2525
2658
|
);
|
|
2526
2659
|
}
|
|
2527
2660
|
if (accounts.length === 0) {
|
|
@@ -2552,6 +2685,65 @@ function MailPanel() {
|
|
|
2552
2685
|
const loggedIn = isAll ? accounts.some((item) => item.id === state.activeAccountId) : state.loggedIn;
|
|
2553
2686
|
const needsLogin = source === "server" && !isAll && state.loggedIn === false;
|
|
2554
2687
|
const items = page.items;
|
|
2688
|
+
const isSent = isSentFolder(mailbox);
|
|
2689
|
+
const groups = groupMessages(items);
|
|
2690
|
+
const renderMessage = (item, meta = {}) => {
|
|
2691
|
+
const sent = isSent && item.to.length > 0;
|
|
2692
|
+
const first = sent ? item.to[0] : item.from;
|
|
2693
|
+
const who = first === void 0 || first === null ? "" : first.name !== "" ? first.name : first.address;
|
|
2694
|
+
const extra = sent && item.to.length > 1 ? ` +${String(item.to.length - 1)}` : "";
|
|
2695
|
+
const isSelected = selected?.id === item.id && selected?.accountId === item.accountId;
|
|
2696
|
+
return (0, import_react9.createElement)(
|
|
2697
|
+
"div",
|
|
2698
|
+
{
|
|
2699
|
+
key: `${item.accountId}:${item.id}`,
|
|
2700
|
+
className: `mailharbor-row${item.unread ? " unread" : ""}${isSelected ? " selected" : ""}${selectMode ? " selectable" : ""}${meta.compact === true ? " compact" : ""}`,
|
|
2701
|
+
role: "button",
|
|
2702
|
+
tabIndex: 0,
|
|
2703
|
+
onClick: () => {
|
|
2704
|
+
if (selectMode) toggleSelect(item);
|
|
2705
|
+
else void openMessage(item);
|
|
2706
|
+
},
|
|
2707
|
+
onKeyDown: (event) => {
|
|
2708
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2709
|
+
event.preventDefault();
|
|
2710
|
+
if (selectMode) toggleSelect(item);
|
|
2711
|
+
else void openMessage(item);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2715
|
+
(0, import_react9.createElement)(
|
|
2716
|
+
"span",
|
|
2717
|
+
{ className: "line-1" },
|
|
2718
|
+
selectMode ? (0, import_react9.createElement)("input", {
|
|
2719
|
+
type: "checkbox",
|
|
2720
|
+
checked: selectedKeys.includes(`${item.accountId}:${item.id}`),
|
|
2721
|
+
"aria-label": "\u9009\u62E9\u90AE\u4EF6",
|
|
2722
|
+
onClick: (event) => event.stopPropagation(),
|
|
2723
|
+
onChange: () => toggleSelect(item)
|
|
2724
|
+
}) : item.unread ? (0, import_react9.createElement)("span", { className: "dot" }) : null,
|
|
2725
|
+
sent ? (0, import_react9.createElement)("span", { className: "to-label" }, "\u6536\u4EF6\u4EBA ") : null,
|
|
2726
|
+
(0, import_react9.createElement)("span", { className: "from" }, `${who}${extra}`),
|
|
2727
|
+
item.hasAttachments ? (0, import_react9.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
|
|
2728
|
+
source === "local" && item.backedUp === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
|
|
2729
|
+
meta.groupCount !== void 0 && meta.groupCount > 1 ? (0, import_react9.createElement)(
|
|
2730
|
+
"button",
|
|
2731
|
+
{
|
|
2732
|
+
className: "thread-count",
|
|
2733
|
+
title: meta.expanded === true ? "\u6536\u8D77\u4F1A\u8BDD" : `\u5C55\u5F00\u4F1A\u8BDD\uFF08${String(meta.groupCount)} \u5C01\uFF09`,
|
|
2734
|
+
onClick: (event) => {
|
|
2735
|
+
event.stopPropagation();
|
|
2736
|
+
toggleGroup(meta.groupKey ?? "");
|
|
2737
|
+
}
|
|
2738
|
+
},
|
|
2739
|
+
meta.expanded === true ? "\u2212" : String(meta.groupCount)
|
|
2740
|
+
) : null,
|
|
2741
|
+
(0, import_react9.createElement)("span", { className: "date" }, shortDate(item.date))
|
|
2742
|
+
),
|
|
2743
|
+
meta.compact === true ? null : (0, import_react9.createElement)("span", { className: "line-2" }, (0, import_react9.createElement)("span", { className: "subject" }, item.subject)),
|
|
2744
|
+
item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, item.snippet)
|
|
2745
|
+
);
|
|
2746
|
+
};
|
|
2555
2747
|
return (0, import_react9.createElement)(
|
|
2556
2748
|
"div",
|
|
2557
2749
|
{ className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
|
|
@@ -2559,21 +2751,21 @@ function MailPanel() {
|
|
|
2559
2751
|
"div",
|
|
2560
2752
|
{ className: "mailharbor-topbar" },
|
|
2561
2753
|
(0, import_react9.createElement)(
|
|
2562
|
-
"
|
|
2563
|
-
{
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
"
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
},
|
|
2572
|
-
(0, import_react9.createElement)("
|
|
2573
|
-
|
|
2574
|
-
)
|
|
2575
|
-
|
|
2576
|
-
|
|
2754
|
+
"button",
|
|
2755
|
+
{
|
|
2756
|
+
className: "mailharbor-account",
|
|
2757
|
+
onClick: toggleSide,
|
|
2758
|
+
title: sideOpen ? "\u6536\u8D77\u8D26\u53F7\u4E0E\u6587\u4EF6\u5939" : "\u5C55\u5F00\u8D26\u53F7\u4E0E\u6587\u4EF6\u5939"
|
|
2759
|
+
},
|
|
2760
|
+
(0, import_react9.createElement)(Avatar, { email: currentAccount?.email ?? "?", size: 26 }),
|
|
2761
|
+
(0, import_react9.createElement)(
|
|
2762
|
+
"div",
|
|
2763
|
+
{ style: { display: "flex", flexDirection: "column", minWidth: 0 } },
|
|
2764
|
+
(0, import_react9.createElement)("span", { className: "name" }, currentAccount?.label ?? ""),
|
|
2765
|
+
(0, import_react9.createElement)("span", { className: "small muted" }, currentAccount?.email ?? "")
|
|
2766
|
+
),
|
|
2767
|
+
state.loggedIn === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
|
|
2768
|
+
(0, import_react9.createElement)(Icon, { name: sideOpen ? "chevronLeft" : "chevronRight", size: 14 })
|
|
2577
2769
|
),
|
|
2578
2770
|
(0, import_react9.createElement)(
|
|
2579
2771
|
"div",
|
|
@@ -2601,16 +2793,6 @@ function MailPanel() {
|
|
|
2601
2793
|
)
|
|
2602
2794
|
),
|
|
2603
2795
|
(0, import_react9.createElement)("span", { className: "spacer" }),
|
|
2604
|
-
(0, import_react9.createElement)(
|
|
2605
|
-
"button",
|
|
2606
|
-
{
|
|
2607
|
-
className: "ghost icon",
|
|
2608
|
-
title: "\u6DFB\u52A0\u8D26\u53F7",
|
|
2609
|
-
"aria-label": "\u6DFB\u52A0\u8D26\u53F7",
|
|
2610
|
-
onClick: () => setShowAdd(true)
|
|
2611
|
-
},
|
|
2612
|
-
(0, import_react9.createElement)(Icon, { name: "plus" })
|
|
2613
|
-
),
|
|
2614
2796
|
(0, import_react9.createElement)(
|
|
2615
2797
|
"button",
|
|
2616
2798
|
{ className: "ghost icon", title: "\u5237\u65B0\u5217\u8868", "aria-label": "\u5237\u65B0\u5217\u8868", onClick: refresh },
|
|
@@ -2652,37 +2834,75 @@ function MailPanel() {
|
|
|
2652
2834
|
) : (0, import_react9.createElement)(
|
|
2653
2835
|
"div",
|
|
2654
2836
|
{ className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
|
|
2837
|
+
sideOpen ? (0, import_react9.createElement)(
|
|
2838
|
+
"aside",
|
|
2839
|
+
{ className: "mailharbor-side" },
|
|
2840
|
+
(0, import_react9.createElement)("div", { className: "mailharbor-side-section" }, "\u8D26\u53F7"),
|
|
2841
|
+
accounts.map(
|
|
2842
|
+
(item) => (0, import_react9.createElement)(
|
|
2843
|
+
"button",
|
|
2844
|
+
{
|
|
2845
|
+
key: item.id,
|
|
2846
|
+
className: `mailharbor-side-item${!isAll && item.id === accountScope ? " active" : ""}`,
|
|
2847
|
+
onClick: () => {
|
|
2848
|
+
void switchAccount(item.id);
|
|
2849
|
+
setSource("server");
|
|
2850
|
+
setMailbox("INBOX");
|
|
2851
|
+
}
|
|
2852
|
+
},
|
|
2853
|
+
(0, import_react9.createElement)(Avatar, { email: item.email, size: 18 }),
|
|
2854
|
+
(0, import_react9.createElement)(
|
|
2855
|
+
"span",
|
|
2856
|
+
{ className: "name" },
|
|
2857
|
+
(0, import_react9.createElement)("span", null, item.label),
|
|
2858
|
+
(0, import_react9.createElement)("span", { className: "sub" }, item.email)
|
|
2859
|
+
),
|
|
2860
|
+
item.enabled === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u505C\u7528") : null
|
|
2861
|
+
)
|
|
2862
|
+
),
|
|
2863
|
+
(0, import_react9.createElement)(
|
|
2864
|
+
"div",
|
|
2865
|
+
{ className: "mailharbor-side-section" },
|
|
2866
|
+
source === "server" ? "\u670D\u52A1\u5668\u6587\u4EF6\u5939" : "\u672C\u5730\u6587\u4EF6\u5939"
|
|
2867
|
+
),
|
|
2868
|
+
folders.map(
|
|
2869
|
+
(folder) => (0, import_react9.createElement)(
|
|
2870
|
+
"button",
|
|
2871
|
+
{
|
|
2872
|
+
key: folder.name,
|
|
2873
|
+
className: `mailharbor-side-item${mailbox === folder.name ? " active" : ""}`,
|
|
2874
|
+
onClick: () => {
|
|
2875
|
+
setMailbox(folder.name);
|
|
2876
|
+
setSelected(null);
|
|
2877
|
+
}
|
|
2878
|
+
},
|
|
2879
|
+
(0, import_react9.createElement)(Icon, { name: folder.name === DRAFTS ? "compose" : "folder", size: 14 }),
|
|
2880
|
+
(0, import_react9.createElement)("span", { className: "name" }, folderName(folder.label ?? folder.name)),
|
|
2881
|
+
folder.count > 0 ? (0, import_react9.createElement)("span", { className: "count" }, String(folder.count)) : null
|
|
2882
|
+
)
|
|
2883
|
+
)
|
|
2884
|
+
) : null,
|
|
2655
2885
|
(0, import_react9.createElement)(
|
|
2656
2886
|
"div",
|
|
2657
2887
|
{ className: "mailharbor-list" },
|
|
2658
2888
|
(0, import_react9.createElement)(
|
|
2659
2889
|
"div",
|
|
2660
2890
|
{ className: "mailharbor-filters" },
|
|
2661
|
-
(0, import_react9.createElement)(
|
|
2662
|
-
"select",
|
|
2663
|
-
{
|
|
2664
|
-
value: mailbox,
|
|
2665
|
-
onChange: (event) => {
|
|
2666
|
-
setMailbox(event.target.value);
|
|
2667
|
-
setSelected(null);
|
|
2668
|
-
},
|
|
2669
|
-
"aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
|
|
2670
|
-
},
|
|
2671
|
-
folderOptions.map(
|
|
2672
|
-
(option) => (0, import_react9.createElement)("option", { key: option.value, value: option.value }, option.label)
|
|
2673
|
-
)
|
|
2674
|
-
),
|
|
2675
2891
|
(0, import_react9.createElement)("input", {
|
|
2676
2892
|
type: "search",
|
|
2677
|
-
placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : "\u641C\u7D22\u90AE\u4EF6",
|
|
2893
|
+
placeholder: showingDrafts ? "\u641C\u7D22\u8349\u7A3F" : aiSearching ? "AI \u6B63\u5728\u7406\u89E3\u4F60\u7684\u641C\u7D22\u2026" : "\u641C\u7D22\u90AE\u4EF6\uFF0C\u6216\u63CF\u8FF0\u4F60\u8981\u627E\u4EC0\u4E48\uFF08AI\uFF09",
|
|
2678
2894
|
"aria-label": "\u641C\u7D22\u90AE\u4EF6",
|
|
2679
2895
|
value: query2,
|
|
2680
2896
|
onChange: (event) => setQuery(event.target.value),
|
|
2681
2897
|
onKeyDown: (event) => {
|
|
2682
|
-
if (event.key === "Enter")
|
|
2898
|
+
if (event.key === "Enter") {
|
|
2899
|
+
if (showingDrafts) setSearch(query2);
|
|
2900
|
+
else void runAiSearch();
|
|
2901
|
+
}
|
|
2683
2902
|
if (event.key === "Escape") {
|
|
2684
2903
|
setQuery("");
|
|
2685
2904
|
setSearch("");
|
|
2905
|
+
setAiInfo(null);
|
|
2686
2906
|
}
|
|
2687
2907
|
}
|
|
2688
2908
|
}),
|
|
@@ -2711,11 +2931,35 @@ function MailPanel() {
|
|
|
2711
2931
|
onClick: () => {
|
|
2712
2932
|
setQuery("");
|
|
2713
2933
|
setSearch("");
|
|
2934
|
+
setAiInfo(null);
|
|
2714
2935
|
}
|
|
2715
2936
|
},
|
|
2716
2937
|
(0, import_react9.createElement)(Icon, { name: "close", size: 14 })
|
|
2717
2938
|
) : null
|
|
2718
2939
|
),
|
|
2940
|
+
aiInfo === null ? null : (0, import_react9.createElement)(
|
|
2941
|
+
"div",
|
|
2942
|
+
{ className: "mailharbor-ai-search" },
|
|
2943
|
+
(0, import_react9.createElement)(Icon, { name: "sparkles", size: 13 }),
|
|
2944
|
+
(0, import_react9.createElement)(
|
|
2945
|
+
"span",
|
|
2946
|
+
null,
|
|
2947
|
+
aiInfo.explanation !== "" ? aiInfo.explanation : `\u5173\u952E\u8BCD\uFF1A${aiInfo.keywords.join("\u3001")}`
|
|
2948
|
+
),
|
|
2949
|
+
(0, import_react9.createElement)("span", { className: "spacer" }),
|
|
2950
|
+
(0, import_react9.createElement)(
|
|
2951
|
+
"button",
|
|
2952
|
+
{
|
|
2953
|
+
className: "subtle",
|
|
2954
|
+
onClick: () => {
|
|
2955
|
+
setAiInfo(null);
|
|
2956
|
+
setTick((value) => value + 1);
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
"\u6E05\u9664"
|
|
2960
|
+
)
|
|
2961
|
+
),
|
|
2962
|
+
aiSearching ? (0, import_react9.createElement)("div", { className: "mailharbor-ai-search searching" }, "AI \u6B63\u5728\u7406\u89E3\u5E76\u641C\u7D22\u2026") : null,
|
|
2719
2963
|
selectMode ? (0, import_react9.createElement)(
|
|
2720
2964
|
"div",
|
|
2721
2965
|
{ className: "mailharbor-selectbar" },
|
|
@@ -2826,76 +3070,39 @@ function MailPanel() {
|
|
|
2826
3070
|
) : items.length === 0 ? (0, import_react9.createElement)(
|
|
2827
3071
|
"div",
|
|
2828
3072
|
{ className: "mailharbor-empty" },
|
|
2829
|
-
(0, import_react9.createElement)(Icon, { name: "mail", size: 28 }),
|
|
3073
|
+
(0, import_react9.createElement)(Icon, { name: aiInfo === null ? "mail" : "sparkles", size: 28 }),
|
|
2830
3074
|
(0, import_react9.createElement)(
|
|
2831
3075
|
"div",
|
|
2832
3076
|
{ className: "title" },
|
|
2833
|
-
source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6"
|
|
3077
|
+
aiInfo === null ? source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6" : "AI \u641C\u7D22\u6CA1\u6709\u627E\u5230\u5339\u914D\u90AE\u4EF6"
|
|
2834
3078
|
),
|
|
2835
3079
|
(0, import_react9.createElement)(
|
|
2836
3080
|
"div",
|
|
2837
3081
|
{ className: "small" },
|
|
2838
|
-
source === "local" ? "\u70B9\u9876\u90E8\u300C\u5907\u4EFD\u300D\u628A\u670D\u52A1\u5668\u90AE\u4EF6\u540C\u6B65\u4E0B\u6765\u3002" : "\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\u6216\u7A0D\u540E\u5237\u65B0\u3002"
|
|
3082
|
+
aiInfo === null ? 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" : "\u6362\u4E2A\u8BF4\u6CD5\uFF0C\u6216\u76F4\u63A5\u8F93\u5165\u5173\u952E\u8BCD\u540E\u6309\u56DE\u8F66\u3002"
|
|
2839
3083
|
)
|
|
2840
|
-
) : items.map(
|
|
2841
|
-
|
|
3084
|
+
) : selectMode ? items.map((item) => renderMessage(item)) : groups.map((group) => {
|
|
3085
|
+
const latest = group.items[0];
|
|
3086
|
+
if (latest === void 0) return null;
|
|
3087
|
+
const expanded = expandedGroups.includes(group.key);
|
|
3088
|
+
return (0, import_react9.createElement)(
|
|
2842
3089
|
"div",
|
|
2843
|
-
{
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
onKeyDown: (event) => {
|
|
2853
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
2854
|
-
event.preventDefault();
|
|
2855
|
-
if (selectMode) toggleSelect(item);
|
|
2856
|
-
else void openMessage(item);
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
},
|
|
2860
|
-
(0, import_react9.createElement)(
|
|
2861
|
-
"span",
|
|
2862
|
-
{ className: "line-1" },
|
|
2863
|
-
selectMode ? (0, import_react9.createElement)("input", {
|
|
2864
|
-
type: "checkbox",
|
|
2865
|
-
checked: selectedKeys.includes(`${item.accountId}:${item.id}`),
|
|
2866
|
-
"aria-label": "\u9009\u62E9\u90AE\u4EF6",
|
|
2867
|
-
onClick: (event) => event.stopPropagation(),
|
|
2868
|
-
onChange: () => toggleSelect(item)
|
|
2869
|
-
}) : item.unread ? (0, import_react9.createElement)("span", { className: "dot" }) : null,
|
|
2870
|
-
(0, import_react9.createElement)(
|
|
2871
|
-
"span",
|
|
2872
|
-
{ className: "from" },
|
|
2873
|
-
item.from.name !== "" ? item.from.name : item.from.address
|
|
2874
|
-
),
|
|
2875
|
-
item.hasAttachments ? (0, import_react9.createElement)(Icon, { name: "paperclip", size: 13 }) : null,
|
|
2876
|
-
isAll ? (0, import_react9.createElement)(
|
|
2877
|
-
"span",
|
|
2878
|
-
{ className: "badge" },
|
|
2879
|
-
accounts.find((entry) => entry.id === item.accountId)?.label ?? ""
|
|
2880
|
-
) : null,
|
|
2881
|
-
item.backedUp === false ? (0, import_react9.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
|
|
2882
|
-
(0, import_react9.createElement)("span", { className: "date" }, shortDate(item.date))
|
|
2883
|
-
),
|
|
2884
|
-
(0, import_react9.createElement)(
|
|
2885
|
-
"span",
|
|
2886
|
-
{ className: "line-2" },
|
|
2887
|
-
(0, import_react9.createElement)("span", { className: "subject" }, item.subject)
|
|
2888
|
-
),
|
|
2889
|
-
item.snippet === "" ? null : (0, import_react9.createElement)("span", { className: "snippet" }, item.snippet)
|
|
2890
|
-
)
|
|
2891
|
-
)
|
|
3090
|
+
{ className: "mailharbor-thread", key: group.key },
|
|
3091
|
+
renderMessage(latest, {
|
|
3092
|
+
groupCount: group.items.length,
|
|
3093
|
+
groupKey: group.key,
|
|
3094
|
+
expanded
|
|
3095
|
+
}),
|
|
3096
|
+
expanded ? group.items.slice(1).map((item) => renderMessage(item, { compact: true })) : null
|
|
3097
|
+
);
|
|
3098
|
+
})
|
|
2892
3099
|
),
|
|
2893
3100
|
!showingDrafts && items.length > 0 ? (0, import_react9.createElement)(
|
|
2894
3101
|
"div",
|
|
2895
3102
|
{ className: "mailharbor-list-foot" },
|
|
2896
3103
|
(0, import_react9.createElement)("span", null, `${String(items.length)} / ${String(page.total)} \u5C01`),
|
|
2897
3104
|
(0, import_react9.createElement)("span", { className: "spacer" }),
|
|
2898
|
-
page.total > items.length ? (0, import_react9.createElement)(
|
|
3105
|
+
page.total > items.length && aiInfo === null ? (0, import_react9.createElement)(
|
|
2899
3106
|
"button",
|
|
2900
3107
|
{ className: "subtle", onClick: () => setLimit((current) => current + 40) },
|
|
2901
3108
|
"\u52A0\u8F7D\u66F4\u591A"
|
|
@@ -3062,15 +3269,6 @@ function MailSettings() {
|
|
|
3062
3269
|
const [pollSeconds, setPollSeconds] = (0, import_react10.useState)(60);
|
|
3063
3270
|
const [listLimit, setListLimit] = (0, import_react10.useState)(40);
|
|
3064
3271
|
const [projectRoot, setProjectRoot] = (0, import_react10.useState)("");
|
|
3065
|
-
const [caldavAccountId, setCaldavAccountId] = (0, import_react10.useState)("");
|
|
3066
|
-
const [caldavUrl, setCaldavUrl] = (0, import_react10.useState)("");
|
|
3067
|
-
const [caldavUser, setCaldavUser] = (0, import_react10.useState)("");
|
|
3068
|
-
const [caldavPassword, setCaldavPassword] = (0, import_react10.useState)("");
|
|
3069
|
-
const [caldavHref, setCaldavHref] = (0, import_react10.useState)("");
|
|
3070
|
-
const [caldavHasPassword, setCaldavHasPassword] = (0, import_react10.useState)(false);
|
|
3071
|
-
const [caldavCalendar, setCaldavCalendar] = (0, import_react10.useState)(null);
|
|
3072
|
-
const [caldavMessage, setCaldavMessage] = (0, import_react10.useState)("");
|
|
3073
|
-
const [caldavBusy, setCaldavBusy] = (0, import_react10.useState)(false);
|
|
3074
3272
|
const [aiEnabled, setAiEnabled] = (0, import_react10.useState)(true);
|
|
3075
3273
|
const [aiProvider, setAiProvider] = (0, import_react10.useState)("deepseek-account");
|
|
3076
3274
|
const [aiModel, setAiModel] = (0, import_react10.useState)("deepseek-flash");
|
|
@@ -3094,9 +3292,6 @@ function MailSettings() {
|
|
|
3094
3292
|
setPollSeconds(view.read.pollSeconds);
|
|
3095
3293
|
setListLimit(view.render.listLimit);
|
|
3096
3294
|
setProjectRoot(view.projects.root);
|
|
3097
|
-
setCaldavAccountId(
|
|
3098
|
-
(current) => current || view.activeAccountId || appState.settings.activeAccountId || ""
|
|
3099
|
-
);
|
|
3100
3295
|
setAiEnabled(view.ai.enabled);
|
|
3101
3296
|
setAiProvider(view.ai.provider);
|
|
3102
3297
|
setAiModel(view.ai.model);
|
|
@@ -3107,23 +3302,6 @@ function MailSettings() {
|
|
|
3107
3302
|
cancelled = true;
|
|
3108
3303
|
};
|
|
3109
3304
|
}, [tick]);
|
|
3110
|
-
(0, import_react10.useEffect)(() => {
|
|
3111
|
-
let cancelled = false;
|
|
3112
|
-
if (caldavAccountId === "") return;
|
|
3113
|
-
api.taskCaldav(caldavAccountId).then((info) => {
|
|
3114
|
-
if (cancelled) return;
|
|
3115
|
-
setCaldavUrl(info.caldav?.url ?? "");
|
|
3116
|
-
setCaldavUser(info.caldav?.username ?? "");
|
|
3117
|
-
setCaldavHref(info.caldav?.calendarHref ?? "");
|
|
3118
|
-
setCaldavHasPassword(info.hasPassword);
|
|
3119
|
-
setCaldavCalendar(info.calendar);
|
|
3120
|
-
}).catch((caught) => {
|
|
3121
|
-
if (!cancelled) setCaldavMessage(errorText6(caught));
|
|
3122
|
-
});
|
|
3123
|
-
return () => {
|
|
3124
|
-
cancelled = true;
|
|
3125
|
-
};
|
|
3126
|
-
}, [caldavAccountId, tick]);
|
|
3127
3305
|
(0, import_react10.useEffect)(() => {
|
|
3128
3306
|
let cancelled = false;
|
|
3129
3307
|
const accountId = settings?.activeAccountId ?? state?.activeAccountId ?? null;
|
|
@@ -3309,147 +3487,29 @@ function MailSettings() {
|
|
|
3309
3487
|
})
|
|
3310
3488
|
),
|
|
3311
3489
|
(0, import_react10.createElement)(
|
|
3312
|
-
"
|
|
3313
|
-
{
|
|
3314
|
-
className: "ghost small",
|
|
3315
|
-
style: { alignSelf: "end" },
|
|
3316
|
-
onClick: () => void saveGeneral({ projects: { root: projectRoot } })
|
|
3317
|
-
},
|
|
3318
|
-
"\u4FDD\u5B58\u76EE\u5F55"
|
|
3319
|
-
)
|
|
3320
|
-
)
|
|
3321
|
-
),
|
|
3322
|
-
(0, import_react10.createElement)(
|
|
3323
|
-
"div",
|
|
3324
|
-
{ className: "mailharbor-card" },
|
|
3325
|
-
(0, import_react10.createElement)("h3", null, "\u4EFB\u52A1\u4E0E CalDAV"),
|
|
3326
|
-
(0, import_react10.createElement)(
|
|
3327
|
-
"p",
|
|
3328
|
-
{ className: "small muted" },
|
|
3329
|
-
"\u4EFB\u52A1\u9762\u677F\u7684\u300C\u540C\u6B65 CalDAV\u300D\u4F7F\u7528\u8FD9\u91CC\u7684\u914D\u7F6E\u3002\u5BC6\u7801\u53EA\u5B58\u7CFB\u7EDF Keychain\uFF0C\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\u3002"
|
|
3330
|
-
),
|
|
3331
|
-
(0, import_react10.createElement)(
|
|
3332
|
-
"div",
|
|
3333
|
-
{ className: "mailharbor-grid" },
|
|
3334
|
-
(0, import_react10.createElement)(
|
|
3335
|
-
Field,
|
|
3336
|
-
{ label: "\u8D26\u53F7" },
|
|
3490
|
+
"div",
|
|
3491
|
+
{ style: { display: "flex", gap: "6px", alignSelf: "end" } },
|
|
3337
3492
|
(0, import_react10.createElement)(
|
|
3338
|
-
"
|
|
3493
|
+
"button",
|
|
3339
3494
|
{
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3495
|
+
className: "ghost small",
|
|
3496
|
+
onClick: () => {
|
|
3497
|
+
void api.pickDirectory().then((result) => {
|
|
3498
|
+
if (result.path !== null) setProjectRoot(result.path);
|
|
3499
|
+
}).catch((caught) => setError(errorText6(caught)));
|
|
3344
3500
|
}
|
|
3345
3501
|
},
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
)
|
|
3349
|
-
)
|
|
3350
|
-
),
|
|
3351
|
-
(0, import_react10.createElement)(
|
|
3352
|
-
Field,
|
|
3353
|
-
{ label: "\u72B6\u6001" },
|
|
3502
|
+
"\u6D4F\u89C8\u2026"
|
|
3503
|
+
),
|
|
3354
3504
|
(0, import_react10.createElement)(
|
|
3355
|
-
"
|
|
3356
|
-
{
|
|
3357
|
-
|
|
3358
|
-
|
|
3505
|
+
"button",
|
|
3506
|
+
{
|
|
3507
|
+
className: "ghost small",
|
|
3508
|
+
onClick: () => void saveGeneral({ projects: { root: projectRoot } })
|
|
3509
|
+
},
|
|
3510
|
+
"\u4FDD\u5B58\u76EE\u5F55"
|
|
3359
3511
|
)
|
|
3360
3512
|
)
|
|
3361
|
-
),
|
|
3362
|
-
(0, import_react10.createElement)(
|
|
3363
|
-
"div",
|
|
3364
|
-
{ className: "mailharbor-grid" },
|
|
3365
|
-
(0, import_react10.createElement)(
|
|
3366
|
-
Field,
|
|
3367
|
-
{ label: "CalDAV \u5730\u5740" },
|
|
3368
|
-
(0, import_react10.createElement)("input", {
|
|
3369
|
-
value: caldavUrl,
|
|
3370
|
-
placeholder: "https://caldav.example.com/",
|
|
3371
|
-
onChange: (event) => setCaldavUrl(event.target.value)
|
|
3372
|
-
})
|
|
3373
|
-
),
|
|
3374
|
-
(0, import_react10.createElement)(
|
|
3375
|
-
Field,
|
|
3376
|
-
{ label: "\u7528\u6237\u540D" },
|
|
3377
|
-
(0, import_react10.createElement)("input", {
|
|
3378
|
-
value: caldavUser,
|
|
3379
|
-
placeholder: "\u901A\u5E38\u662F\u90AE\u7BB1\u5730\u5740",
|
|
3380
|
-
onChange: (event) => setCaldavUser(event.target.value)
|
|
3381
|
-
})
|
|
3382
|
-
)
|
|
3383
|
-
),
|
|
3384
|
-
(0, import_react10.createElement)(
|
|
3385
|
-
"div",
|
|
3386
|
-
{ className: "mailharbor-grid" },
|
|
3387
|
-
(0, import_react10.createElement)(
|
|
3388
|
-
Field,
|
|
3389
|
-
{ label: caldavHasPassword ? "\u5E94\u7528\u5BC6\u7801\uFF08\u7559\u7A7A\u8868\u793A\u4E0D\u4FEE\u6539\uFF09" : "\u5E94\u7528\u5BC6\u7801" },
|
|
3390
|
-
(0, import_react10.createElement)("input", {
|
|
3391
|
-
type: "password",
|
|
3392
|
-
value: caldavPassword,
|
|
3393
|
-
placeholder: caldavHasPassword ? "\u5DF2\u4FDD\u5B58" : "iCloud / Fastmail \u7B49\u5E94\u7528\u4E13\u7528\u5BC6\u7801",
|
|
3394
|
-
onChange: (event) => setCaldavPassword(event.target.value)
|
|
3395
|
-
})
|
|
3396
|
-
),
|
|
3397
|
-
(0, import_react10.createElement)(
|
|
3398
|
-
Field,
|
|
3399
|
-
{ label: "\u4EFB\u52A1\u65E5\u5386\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09" },
|
|
3400
|
-
(0, import_react10.createElement)("input", {
|
|
3401
|
-
value: caldavHref,
|
|
3402
|
-
placeholder: "\u7559\u7A7A\u81EA\u52A8\u53D1\u73B0",
|
|
3403
|
-
onChange: (event) => setCaldavHref(event.target.value)
|
|
3404
|
-
})
|
|
3405
|
-
)
|
|
3406
|
-
),
|
|
3407
|
-
(0, import_react10.createElement)(
|
|
3408
|
-
"div",
|
|
3409
|
-
{ style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
|
|
3410
|
-
(0, import_react10.createElement)(
|
|
3411
|
-
"button",
|
|
3412
|
-
{
|
|
3413
|
-
className: "primary small",
|
|
3414
|
-
disabled: caldavBusy,
|
|
3415
|
-
onClick: () => {
|
|
3416
|
-
setCaldavBusy(true);
|
|
3417
|
-
setCaldavMessage("\u4FDD\u5B58\u4E2D\u2026");
|
|
3418
|
-
void api.saveTaskCaldav({
|
|
3419
|
-
accountId: caldavAccountId,
|
|
3420
|
-
url: caldavUrl,
|
|
3421
|
-
username: caldavUser,
|
|
3422
|
-
...caldavPassword === "" ? {} : { password: caldavPassword },
|
|
3423
|
-
calendarHref: caldavHref
|
|
3424
|
-
}).then((info) => {
|
|
3425
|
-
setCaldavHasPassword(info.hasPassword);
|
|
3426
|
-
setCaldavPassword("");
|
|
3427
|
-
setCaldavMessage(info.hasPassword ? "\u5DF2\u4FDD\u5B58" : "\u5DF2\u4FDD\u5B58\u5730\u5740\uFF0C\u4F46\u8FD8\u6CA1\u6709\u5BC6\u7801");
|
|
3428
|
-
setTick((value) => value + 1);
|
|
3429
|
-
}).catch((caught) => setCaldavMessage(errorText6(caught))).finally(() => setCaldavBusy(false));
|
|
3430
|
-
}
|
|
3431
|
-
},
|
|
3432
|
-
caldavBusy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58 CalDAV"
|
|
3433
|
-
),
|
|
3434
|
-
(0, import_react10.createElement)(
|
|
3435
|
-
"button",
|
|
3436
|
-
{
|
|
3437
|
-
className: "ghost small",
|
|
3438
|
-
disabled: caldavBusy,
|
|
3439
|
-
onClick: () => {
|
|
3440
|
-
setCaldavBusy(true);
|
|
3441
|
-
setCaldavMessage("\u540C\u6B65\u4E2D\u2026");
|
|
3442
|
-
void api.syncTasks(caldavAccountId).then((result) => {
|
|
3443
|
-
setCaldavMessage(
|
|
3444
|
-
`\u540C\u6B65\u5B8C\u6210\uFF1A\u62C9\u53D6 ${String(result.result.pulled)}\uFF0C\u63A8\u9001 ${String(result.result.pushed)}`
|
|
3445
|
-
);
|
|
3446
|
-
setTick((value) => value + 1);
|
|
3447
|
-
}).catch((caught) => setCaldavMessage(errorText6(caught))).finally(() => setCaldavBusy(false));
|
|
3448
|
-
}
|
|
3449
|
-
},
|
|
3450
|
-
"\u7ACB\u5373\u540C\u6B65"
|
|
3451
|
-
),
|
|
3452
|
-
(0, import_react10.createElement)("span", { className: "small muted" }, caldavMessage)
|
|
3453
3513
|
)
|
|
3454
3514
|
),
|
|
3455
3515
|
(0, import_react10.createElement)(
|
|
@@ -3858,19 +3918,18 @@ function AccountRow({
|
|
|
3858
3918
|
// src/client/tasks-panel.tsx
|
|
3859
3919
|
var import_react11 = require("react");
|
|
3860
3920
|
var FILTERS = [
|
|
3861
|
-
{ id: "
|
|
3921
|
+
{ id: "all", label: "\u5168\u90E8" },
|
|
3862
3922
|
{ id: "today", label: "\u4ECA\u5929" },
|
|
3863
3923
|
{ id: "week", label: "\u672C\u5468" },
|
|
3864
|
-
{ id: "overdue", label: "\u903E\u671F" }
|
|
3865
|
-
{ id: "done", label: "\u5DF2\u5B8C\u6210" },
|
|
3866
|
-
{ id: "all", label: "\u5168\u90E8" }
|
|
3924
|
+
{ id: "overdue", label: "\u903E\u671F" }
|
|
3867
3925
|
];
|
|
3868
|
-
var
|
|
3869
|
-
"NEEDS-ACTION": "\u5F85\u5904\u7406",
|
|
3870
|
-
"IN-PROCESS": "\u8FDB\u884C\u4E2D",
|
|
3871
|
-
COMPLETED: "\u5DF2\u5B8C\u6210",
|
|
3872
|
-
CANCELLED: "\
|
|
3873
|
-
|
|
3926
|
+
var COLUMNS = [
|
|
3927
|
+
{ status: "NEEDS-ACTION", label: "\u5F85\u5904\u7406", hint: "\u8FD8\u6CA1\u5F00\u59CB" },
|
|
3928
|
+
{ status: "IN-PROCESS", label: "\u8FDB\u884C\u4E2D", hint: "\u6B63\u5728\u505A" },
|
|
3929
|
+
{ status: "COMPLETED", label: "\u5DF2\u5B8C\u6210", hint: "\u6536\u5C3E" },
|
|
3930
|
+
{ status: "CANCELLED", label: "\u7EC8\u6B62", hint: "\u4E0D\u505A\u4E86" }
|
|
3931
|
+
];
|
|
3932
|
+
var PRIORITY_LABEL = { 0: "\u65E0", 5: "\u4E2D", 9: "\u9AD8" };
|
|
3874
3933
|
function errorText7(error) {
|
|
3875
3934
|
return error instanceof ApiError || error instanceof Error ? error.message : String(error);
|
|
3876
3935
|
}
|
|
@@ -3886,11 +3945,13 @@ function weekEnd() {
|
|
|
3886
3945
|
}
|
|
3887
3946
|
function dueText(due) {
|
|
3888
3947
|
if (due === "") return "";
|
|
3948
|
+
const value = due.slice(0, 10);
|
|
3889
3949
|
const today = dateKey(/* @__PURE__ */ new Date());
|
|
3890
|
-
if (
|
|
3891
|
-
|
|
3950
|
+
if (value < today) return `\u903E\u671F ${value}`;
|
|
3951
|
+
if (value === today) return "\u4ECA\u5929";
|
|
3952
|
+
return value;
|
|
3892
3953
|
}
|
|
3893
|
-
var
|
|
3954
|
+
var EMPTY = {
|
|
3894
3955
|
title: "",
|
|
3895
3956
|
notes: "",
|
|
3896
3957
|
due: "",
|
|
@@ -3971,13 +4032,14 @@ function TasksPanel() {
|
|
|
3971
4032
|
const [state, setState] = (0, import_react11.useState)(null);
|
|
3972
4033
|
const [tasks, setTasks] = (0, import_react11.useState)([]);
|
|
3973
4034
|
const [projects, setProjects] = (0, import_react11.useState)([]);
|
|
3974
|
-
const [
|
|
3975
|
-
const [
|
|
4035
|
+
const [filter, setFilter] = (0, import_react11.useState)("all");
|
|
4036
|
+
const [projectFilter, setProjectFilter] = (0, import_react11.useState)("");
|
|
3976
4037
|
const [error, setError] = (0, import_react11.useState)("");
|
|
3977
4038
|
const [toast, setToast] = (0, import_react11.useState)("");
|
|
3978
4039
|
const [busy, setBusy] = (0, import_react11.useState)(false);
|
|
3979
4040
|
const [editing, setEditing] = (0, import_react11.useState)(null);
|
|
3980
|
-
const [creating, setCreating] = (0, import_react11.useState)(
|
|
4041
|
+
const [creating, setCreating] = (0, import_react11.useState)(null);
|
|
4042
|
+
const [dragging, setDragging] = (0, import_react11.useState)(null);
|
|
3981
4043
|
const [tick, setTick] = (0, import_react11.useState)(0);
|
|
3982
4044
|
const accountId = state?.settings.activeAccountId ?? null;
|
|
3983
4045
|
(0, import_react11.useEffect)(() => {
|
|
@@ -3989,7 +4051,6 @@ function TasksPanel() {
|
|
|
3989
4051
|
Promise.all([api.tasks(accountId), api.projects()]).then(([list, projectList]) => {
|
|
3990
4052
|
if (cancelled) return;
|
|
3991
4053
|
setTasks(list.items);
|
|
3992
|
-
setCalendar(list.calendar);
|
|
3993
4054
|
setProjects(projectList.items);
|
|
3994
4055
|
}).catch((caught) => {
|
|
3995
4056
|
if (!cancelled) setError(errorText7(caught));
|
|
@@ -4007,38 +4068,17 @@ function TasksPanel() {
|
|
|
4007
4068
|
setToast("");
|
|
4008
4069
|
}, 3e3);
|
|
4009
4070
|
};
|
|
4010
|
-
const
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
const done2 = task.status === "COMPLETED" || task.status === "CANCELLED";
|
|
4015
|
-
if (filter === "done") return done2;
|
|
4016
|
-
if (done2) return false;
|
|
4017
|
-
if (filter === "all") return true;
|
|
4071
|
+
const matchesFilter = (0, import_react11.useCallback)(
|
|
4072
|
+
(task) => {
|
|
4073
|
+
const today = dateKey(/* @__PURE__ */ new Date());
|
|
4074
|
+
const end = weekEnd();
|
|
4018
4075
|
if (filter === "overdue") return task.due !== "" && task.due.slice(0, 10) < today;
|
|
4019
4076
|
if (filter === "today") return task.due.slice(0, 10) === today;
|
|
4020
4077
|
if (filter === "week") return task.due !== "" && task.due.slice(0, 10) <= end;
|
|
4021
4078
|
return true;
|
|
4022
|
-
}
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
const done = tasks.length - open.length;
|
|
4026
|
-
const progress = tasks.length === 0 ? 0 : Math.round(done / tasks.length * 100);
|
|
4027
|
-
const byProject = (0, import_react11.useMemo)(() => {
|
|
4028
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4029
|
-
for (const task of tasks) {
|
|
4030
|
-
const key = task.projectId ?? "";
|
|
4031
|
-
const group = groups.get(key) ?? {
|
|
4032
|
-
project: projects.find((project) => project.id === key) ?? null,
|
|
4033
|
-
total: 0,
|
|
4034
|
-
done: 0
|
|
4035
|
-
};
|
|
4036
|
-
group.total += 1;
|
|
4037
|
-
if (task.status === "COMPLETED" || task.status === "CANCELLED") group.done += 1;
|
|
4038
|
-
groups.set(key, group);
|
|
4039
|
-
}
|
|
4040
|
-
return [...groups.values()].filter((group) => group.project !== null);
|
|
4041
|
-
}, [tasks, projects]);
|
|
4079
|
+
},
|
|
4080
|
+
[filter]
|
|
4081
|
+
);
|
|
4042
4082
|
const switchAccount = async (id) => {
|
|
4043
4083
|
try {
|
|
4044
4084
|
await api.activateAccount(id);
|
|
@@ -4047,10 +4087,10 @@ function TasksPanel() {
|
|
|
4047
4087
|
setError(errorText7(caught));
|
|
4048
4088
|
}
|
|
4049
4089
|
};
|
|
4050
|
-
const
|
|
4090
|
+
const move = async (task, status) => {
|
|
4091
|
+
if (task.status === status) return;
|
|
4051
4092
|
try {
|
|
4052
|
-
const
|
|
4053
|
-
const { task: updated } = await api.updateTask(task.id, { status: next });
|
|
4093
|
+
const { task: updated } = await api.updateTask(task.id, { status });
|
|
4054
4094
|
setTasks((current) => current.map((item) => item.id === task.id ? updated : item));
|
|
4055
4095
|
} catch (caught) {
|
|
4056
4096
|
setError(errorText7(caught));
|
|
@@ -4060,6 +4100,7 @@ function TasksPanel() {
|
|
|
4060
4100
|
try {
|
|
4061
4101
|
await api.deleteTask(task.id);
|
|
4062
4102
|
setTasks((current) => current.filter((item) => item.id !== task.id));
|
|
4103
|
+
notify("\u4EFB\u52A1\u5DF2\u5220\u9664");
|
|
4063
4104
|
} catch (caught) {
|
|
4064
4105
|
setError(errorText7(caught));
|
|
4065
4106
|
}
|
|
@@ -4074,21 +4115,8 @@ function TasksPanel() {
|
|
|
4074
4115
|
setError(errorText7(caught));
|
|
4075
4116
|
}
|
|
4076
4117
|
};
|
|
4077
|
-
const sync = async () => {
|
|
4078
|
-
if (accountId === null) return;
|
|
4079
|
-
setBusy(true);
|
|
4080
|
-
setError("");
|
|
4081
|
-
try {
|
|
4082
|
-
const result = await api.syncTasks(accountId);
|
|
4083
|
-
notify(`\u5DF2\u540C\u6B65\uFF1A\u62C9\u53D6 ${String(result.result.pulled)}\uFF0C\u63A8\u9001 ${String(result.result.pushed)}`);
|
|
4084
|
-
refresh();
|
|
4085
|
-
} catch (caught) {
|
|
4086
|
-
setError(errorText7(caught));
|
|
4087
|
-
} finally {
|
|
4088
|
-
setBusy(false);
|
|
4089
|
-
}
|
|
4090
|
-
};
|
|
4091
4118
|
const save = async (draft) => {
|
|
4119
|
+
if (accountId === null) return;
|
|
4092
4120
|
setBusy(true);
|
|
4093
4121
|
setError("");
|
|
4094
4122
|
try {
|
|
@@ -4115,7 +4143,8 @@ function TasksPanel() {
|
|
|
4115
4143
|
});
|
|
4116
4144
|
}
|
|
4117
4145
|
setEditing(null);
|
|
4118
|
-
setCreating(
|
|
4146
|
+
setCreating(null);
|
|
4147
|
+
notify("\u4EFB\u52A1\u5DF2\u4FDD\u5B58");
|
|
4119
4148
|
refresh();
|
|
4120
4149
|
} catch (caught) {
|
|
4121
4150
|
setError(errorText7(caught));
|
|
@@ -4125,7 +4154,7 @@ function TasksPanel() {
|
|
|
4125
4154
|
};
|
|
4126
4155
|
return (0, import_react11.createElement)(
|
|
4127
4156
|
"div",
|
|
4128
|
-
{ className: "mailharbor" },
|
|
4157
|
+
{ className: "mailharbor mailharbor-tasks" },
|
|
4129
4158
|
(0, import_react11.createElement)(
|
|
4130
4159
|
"div",
|
|
4131
4160
|
{ className: "mailharbor-topbar" },
|
|
@@ -4143,72 +4172,21 @@ function TasksPanel() {
|
|
|
4143
4172
|
)
|
|
4144
4173
|
),
|
|
4145
4174
|
(0, import_react11.createElement)("span", { className: "spacer" }),
|
|
4146
|
-
calendar === null ? null : (0, import_react11.createElement)(
|
|
4147
|
-
"span",
|
|
4148
|
-
{ className: "small muted" },
|
|
4149
|
-
calendar.lastSync === "" ? "\u672A\u540C\u6B65" : `\u540C\u6B65\u4E8E ${new Date(calendar.lastSync).toLocaleString()}`
|
|
4150
|
-
),
|
|
4151
|
-
(0, import_react11.createElement)(
|
|
4152
|
-
"button",
|
|
4153
|
-
{ className: "ghost", onClick: () => void sync(), disabled: busy },
|
|
4154
|
-
busy ? "\u540C\u6B65\u4E2D\u2026" : "\u540C\u6B65 CalDAV"
|
|
4155
|
-
),
|
|
4156
4175
|
(0, import_react11.createElement)(
|
|
4157
4176
|
"button",
|
|
4158
|
-
{ className: "primary", onClick: () => setCreating(
|
|
4177
|
+
{ className: "primary", onClick: () => setCreating("NEEDS-ACTION") },
|
|
4159
4178
|
(0, import_react11.createElement)(Icon, { name: "plus" }),
|
|
4160
4179
|
"\u65B0\u5EFA\u4EFB\u52A1"
|
|
4161
4180
|
)
|
|
4162
4181
|
),
|
|
4163
|
-
toast === "" ? null : (0, import_react11.createElement)("div", { className: "small", style: { padding: "6px
|
|
4182
|
+
toast === "" ? null : (0, import_react11.createElement)("div", { className: "small", style: { padding: "6px 18px", color: "var(--m-ok)" } }, toast),
|
|
4164
4183
|
error === "" ? null : (0, import_react11.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
|
|
4165
4184
|
(0, import_react11.createElement)(
|
|
4166
4185
|
"div",
|
|
4167
|
-
{ className: "tasks-
|
|
4186
|
+
{ className: "tasks-board-wrap" },
|
|
4168
4187
|
(0, import_react11.createElement)(
|
|
4169
4188
|
"div",
|
|
4170
|
-
{ className: "tasks-
|
|
4171
|
-
(0, import_react11.createElement)(
|
|
4172
|
-
"div",
|
|
4173
|
-
{ className: "tasks-progress" },
|
|
4174
|
-
(0, import_react11.createElement)(
|
|
4175
|
-
"div",
|
|
4176
|
-
{ className: "tasks-progress-head" },
|
|
4177
|
-
(0, import_react11.createElement)("strong", null, "\u603B\u4F53\u8FDB\u5EA6"),
|
|
4178
|
-
(0, import_react11.createElement)(
|
|
4179
|
-
"span",
|
|
4180
|
-
{ className: "muted small" },
|
|
4181
|
-
`${String(done)} / ${String(tasks.length)} \xB7 ${String(progress)}%`
|
|
4182
|
-
)
|
|
4183
|
-
),
|
|
4184
|
-
(0, import_react11.createElement)("div", { className: "tasks-bar" }, (0, import_react11.createElement)("span", { style: { width: `${String(progress)}%` } })),
|
|
4185
|
-
(0, import_react11.createElement)("div", { className: "small muted" }, `\u5F85\u529E ${String(open.length)} \u6761`)
|
|
4186
|
-
),
|
|
4187
|
-
...byProject.map(
|
|
4188
|
-
(group) => (0, import_react11.createElement)(
|
|
4189
|
-
"div",
|
|
4190
|
-
{ key: group.project?.id ?? "none", className: "tasks-project" },
|
|
4191
|
-
(0, import_react11.createElement)(
|
|
4192
|
-
"div",
|
|
4193
|
-
{ className: "tasks-project-head" },
|
|
4194
|
-
(0, import_react11.createElement)("span", { className: "name" }, group.project?.name ?? ""),
|
|
4195
|
-
(0, import_react11.createElement)("span", { className: "muted small" }, `${String(group.done)}/${String(group.total)}`)
|
|
4196
|
-
),
|
|
4197
|
-
(0, import_react11.createElement)(
|
|
4198
|
-
"div",
|
|
4199
|
-
{ className: "tasks-bar small" },
|
|
4200
|
-
(0, import_react11.createElement)("span", {
|
|
4201
|
-
style: {
|
|
4202
|
-
width: `${String(group.total === 0 ? 0 : Math.round(group.done / group.total * 100))}%`
|
|
4203
|
-
}
|
|
4204
|
-
})
|
|
4205
|
-
)
|
|
4206
|
-
)
|
|
4207
|
-
)
|
|
4208
|
-
),
|
|
4209
|
-
(0, import_react11.createElement)(
|
|
4210
|
-
"div",
|
|
4211
|
-
{ className: "tasks-list-wrap" },
|
|
4189
|
+
{ className: "tasks-toolbar" },
|
|
4212
4190
|
(0, import_react11.createElement)(
|
|
4213
4191
|
"div",
|
|
4214
4192
|
{ className: "tasks-filters" },
|
|
@@ -4224,73 +4202,142 @@ function TasksPanel() {
|
|
|
4224
4202
|
)
|
|
4225
4203
|
)
|
|
4226
4204
|
),
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
{
|
|
4236
|
-
|
|
4237
|
-
|
|
4205
|
+
(0, import_react11.createElement)("span", { className: "spacer" }),
|
|
4206
|
+
(0, import_react11.createElement)(
|
|
4207
|
+
"select",
|
|
4208
|
+
{
|
|
4209
|
+
value: projectFilter,
|
|
4210
|
+
onChange: (event) => setProjectFilter(event.target.value),
|
|
4211
|
+
"aria-label": "\u6309\u9879\u76EE\u7B5B\u9009"
|
|
4212
|
+
},
|
|
4213
|
+
(0, import_react11.createElement)("option", { value: "" }, "\u5168\u90E8\u9879\u76EE"),
|
|
4214
|
+
projects.map((project) => (0, import_react11.createElement)("option", { key: project.id, value: project.id }, project.name))
|
|
4215
|
+
),
|
|
4216
|
+
(0, import_react11.createElement)("span", { className: "small muted" }, "\u62D6\u52A8\u5361\u7247\u53EF\u6539\u53D8\u72B6\u6001")
|
|
4217
|
+
),
|
|
4218
|
+
(0, import_react11.createElement)(
|
|
4219
|
+
"div",
|
|
4220
|
+
{ className: "tasks-board" },
|
|
4221
|
+
COLUMNS.map((column) => {
|
|
4222
|
+
const cards = tasks.filter((task) => task.status === column.status).filter(matchesFilter).filter((task) => projectFilter === "" || task.projectId === projectFilter).sort((left, right) => {
|
|
4223
|
+
const leftDue = left.due === "" ? "9999" : left.due.slice(0, 10);
|
|
4224
|
+
const rightDue = right.due === "" ? "9999" : right.due.slice(0, 10);
|
|
4225
|
+
return leftDue.localeCompare(rightDue);
|
|
4226
|
+
});
|
|
4227
|
+
return (0, import_react11.createElement)(
|
|
4228
|
+
"div",
|
|
4229
|
+
{
|
|
4230
|
+
key: column.status,
|
|
4231
|
+
className: `task-column task-column-${column.status}`,
|
|
4232
|
+
onDragOver: (event) => {
|
|
4233
|
+
event.preventDefault();
|
|
4234
|
+
},
|
|
4235
|
+
onDrop: (event) => {
|
|
4236
|
+
event.preventDefault();
|
|
4237
|
+
const task = tasks.find((item) => item.id === dragging);
|
|
4238
|
+
if (task !== void 0) void move(task, column.status);
|
|
4239
|
+
setDragging(null);
|
|
4240
|
+
}
|
|
4241
|
+
},
|
|
4242
|
+
(0, import_react11.createElement)(
|
|
4238
4243
|
"div",
|
|
4239
|
-
{
|
|
4240
|
-
(0, import_react11.createElement)("
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
onChange: () => void toggle(task)
|
|
4245
|
-
}),
|
|
4244
|
+
{ className: "task-column-head" },
|
|
4245
|
+
(0, import_react11.createElement)("span", { className: `task-dot ${column.status}` }),
|
|
4246
|
+
(0, import_react11.createElement)("strong", null, column.label),
|
|
4247
|
+
(0, import_react11.createElement)("span", { className: "task-column-count" }, String(cards.length)),
|
|
4248
|
+
(0, import_react11.createElement)("span", { className: "spacer" }),
|
|
4246
4249
|
(0, import_react11.createElement)(
|
|
4247
4250
|
"button",
|
|
4248
|
-
{
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4251
|
+
{
|
|
4252
|
+
className: "subtle icon",
|
|
4253
|
+
title: `\u5728\u300C${column.label}\u300D\u4E2D\u65B0\u5EFA`,
|
|
4254
|
+
onClick: () => setCreating(column.status)
|
|
4255
|
+
},
|
|
4256
|
+
(0, import_react11.createElement)(Icon, { name: "plus", size: 14 })
|
|
4257
|
+
)
|
|
4258
|
+
),
|
|
4259
|
+
(0, import_react11.createElement)(
|
|
4260
|
+
"div",
|
|
4261
|
+
{ className: "task-column-body" },
|
|
4262
|
+
cards.length === 0 ? (0, import_react11.createElement)("div", { className: "task-column-empty" }, column.hint) : cards.map(
|
|
4263
|
+
(task) => (0, import_react11.createElement)(
|
|
4264
|
+
"article",
|
|
4265
|
+
{
|
|
4266
|
+
key: task.id,
|
|
4267
|
+
className: `task-card${dragging === task.id ? " dragging" : ""}`,
|
|
4268
|
+
draggable: true,
|
|
4269
|
+
onDragStart: () => setDragging(task.id),
|
|
4270
|
+
onDragEnd: () => setDragging(null),
|
|
4271
|
+
onClick: () => setEditing(task)
|
|
4272
|
+
},
|
|
4273
|
+
(0, import_react11.createElement)(
|
|
4274
|
+
"div",
|
|
4275
|
+
{ className: "task-card-top" },
|
|
4276
|
+
(0, import_react11.createElement)("span", { className: `task-priority p${String(task.priority)}` }),
|
|
4277
|
+
(0, import_react11.createElement)("span", { className: "task-card-title" }, task.title),
|
|
4278
|
+
(0, import_react11.createElement)(
|
|
4279
|
+
"button",
|
|
4280
|
+
{
|
|
4281
|
+
className: "task-card-delete",
|
|
4282
|
+
title: "\u5220\u9664\u4EFB\u52A1",
|
|
4283
|
+
onClick: (event) => {
|
|
4284
|
+
event.stopPropagation();
|
|
4285
|
+
void remove(task);
|
|
4286
|
+
}
|
|
4287
|
+
},
|
|
4288
|
+
(0, import_react11.createElement)(Icon, { name: "close", size: 12 })
|
|
4289
|
+
)
|
|
4290
|
+
),
|
|
4291
|
+
task.notes === "" ? null : (0, import_react11.createElement)("p", { className: "task-card-notes" }, task.notes),
|
|
4292
|
+
(0, import_react11.createElement)(
|
|
4293
|
+
"div",
|
|
4294
|
+
{ className: "task-card-meta" },
|
|
4295
|
+
task.due === "" ? null : (0, import_react11.createElement)(
|
|
4296
|
+
"span",
|
|
4297
|
+
{
|
|
4298
|
+
className: task.due.slice(0, 10) < dateKey(/* @__PURE__ */ new Date()) && task.status !== "COMPLETED" ? "task-due overdue" : "task-due"
|
|
4299
|
+
},
|
|
4300
|
+
(0, import_react11.createElement)(Icon, { name: "calendar", size: 12 }),
|
|
4301
|
+
dueText(task.due)
|
|
4302
|
+
),
|
|
4303
|
+
task.owner === "" ? null : (0, import_react11.createElement)("span", { className: "task-owner" }, task.owner),
|
|
4304
|
+
task.projectId === null ? null : (0, import_react11.createElement)(
|
|
4305
|
+
"span",
|
|
4306
|
+
{ className: "task-project" },
|
|
4307
|
+
projects.find((project) => project.id === task.projectId)?.name ?? "\u9879\u76EE"
|
|
4308
|
+
),
|
|
4309
|
+
task.priority > 0 ? (0, import_react11.createElement)(
|
|
4310
|
+
"span",
|
|
4311
|
+
{ className: "task-priority-label" },
|
|
4312
|
+
`\u4F18\u5148\u7EA7 ${PRIORITY_LABEL[task.priority] ?? String(task.priority)}`
|
|
4313
|
+
) : null
|
|
4314
|
+
),
|
|
4315
|
+
task.messageKey === "" ? null : (0, import_react11.createElement)(
|
|
4316
|
+
"button",
|
|
4255
4317
|
{
|
|
4256
|
-
className: task
|
|
4318
|
+
className: "task-source",
|
|
4319
|
+
onClick: (event) => {
|
|
4320
|
+
event.stopPropagation();
|
|
4321
|
+
void openSource(task);
|
|
4322
|
+
}
|
|
4257
4323
|
},
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
task.owner === "" ? null : (0, import_react11.createElement)("span", null, task.owner),
|
|
4261
|
-
(0, import_react11.createElement)("span", { className: "badge" }, STATUS_LABEL[task.status] ?? task.status),
|
|
4262
|
-
task.projectId === null ? null : (0, import_react11.createElement)(
|
|
4263
|
-
"span",
|
|
4264
|
-
{ className: "badge" },
|
|
4265
|
-
projects.find((project) => project.id === task.projectId)?.name ?? "\u9879\u76EE"
|
|
4324
|
+
(0, import_react11.createElement)(Icon, { name: "mail", size: 12 }),
|
|
4325
|
+
"\u6765\u6E90\u90AE\u4EF6"
|
|
4266
4326
|
)
|
|
4267
4327
|
)
|
|
4268
|
-
),
|
|
4269
|
-
task.messageKey === "" ? null : (0, import_react11.createElement)(
|
|
4270
|
-
"button",
|
|
4271
|
-
{
|
|
4272
|
-
className: "subtle small",
|
|
4273
|
-
title: "\u6253\u5F00\u6765\u6E90\u90AE\u4EF6",
|
|
4274
|
-
onClick: () => void openSource(task)
|
|
4275
|
-
},
|
|
4276
|
-
"\u6765\u6E90"
|
|
4277
|
-
),
|
|
4278
|
-
(0, import_react11.createElement)(
|
|
4279
|
-
"button",
|
|
4280
|
-
{ className: "subtle icon", title: "\u5220\u9664", onClick: () => void remove(task) },
|
|
4281
|
-
(0, import_react11.createElement)(Icon, { name: "trash", size: 13 })
|
|
4282
4328
|
)
|
|
4283
4329
|
)
|
|
4284
|
-
)
|
|
4285
|
-
)
|
|
4330
|
+
);
|
|
4331
|
+
})
|
|
4286
4332
|
)
|
|
4287
4333
|
),
|
|
4288
|
-
creating || editing !== null ? (0, import_react11.createElement)(TaskEditor, {
|
|
4334
|
+
creating !== null || editing !== null ? (0, import_react11.createElement)(TaskEditor, {
|
|
4289
4335
|
task: editing,
|
|
4336
|
+
status: creating ?? "NEEDS-ACTION",
|
|
4290
4337
|
projects,
|
|
4291
4338
|
busy,
|
|
4292
4339
|
onClose: () => {
|
|
4293
|
-
setCreating(
|
|
4340
|
+
setCreating(null);
|
|
4294
4341
|
setEditing(null);
|
|
4295
4342
|
},
|
|
4296
4343
|
onSave: (draft) => void save(draft)
|
|
@@ -4299,13 +4346,15 @@ function TasksPanel() {
|
|
|
4299
4346
|
}
|
|
4300
4347
|
function TaskEditor({
|
|
4301
4348
|
task,
|
|
4349
|
+
status,
|
|
4302
4350
|
projects,
|
|
4303
4351
|
busy,
|
|
4304
4352
|
onClose,
|
|
4305
4353
|
onSave
|
|
4306
4354
|
}) {
|
|
4307
4355
|
const [draft, setDraft] = (0, import_react11.useState)({
|
|
4308
|
-
...
|
|
4356
|
+
...EMPTY,
|
|
4357
|
+
status,
|
|
4309
4358
|
...task === null ? {} : {
|
|
4310
4359
|
id: task.id,
|
|
4311
4360
|
title: task.title,
|
|
@@ -4323,111 +4372,139 @@ function TaskEditor({
|
|
|
4323
4372
|
{
|
|
4324
4373
|
title: task === null ? "\u65B0\u5EFA\u4EFB\u52A1" : "\u7F16\u8F91\u4EFB\u52A1",
|
|
4325
4374
|
onClose,
|
|
4375
|
+
wide: true,
|
|
4326
4376
|
footer: (0, import_react11.createElement)(
|
|
4327
4377
|
"div",
|
|
4328
|
-
{
|
|
4378
|
+
{ className: "task-editor-foot" },
|
|
4379
|
+
(0, import_react11.createElement)("span", { className: "small muted" }, "\u63D0\u793A\uFF1A\u4EFB\u52A1\u4F1A\u4FDD\u5B58\u5728\u672C\u5730\uFF0C\u53EF\u5728\u770B\u677F\u4E2D\u62D6\u52A8\u6539\u53D8\u72B6\u6001\u3002"),
|
|
4380
|
+
(0, import_react11.createElement)("span", { className: "spacer" }),
|
|
4329
4381
|
(0, import_react11.createElement)("button", { className: "ghost", onClick: onClose }, "\u53D6\u6D88"),
|
|
4330
4382
|
(0, import_react11.createElement)(
|
|
4331
4383
|
"button",
|
|
4332
|
-
{ className: "primary", disabled: busy || draft.title.trim() === "", onClick: () => onSave(draft) },
|
|
4333
|
-
busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58"
|
|
4334
|
-
)
|
|
4335
|
-
)
|
|
4336
|
-
},
|
|
4337
|
-
(0, import_react11.createElement)(
|
|
4338
|
-
"div",
|
|
4339
|
-
{ className: "mailharbor-field" },
|
|
4340
|
-
(0, import_react11.createElement)("label", null, "\u6807\u9898"),
|
|
4341
|
-
(0, import_react11.createElement)("input", {
|
|
4342
|
-
value: draft.title,
|
|
4343
|
-
autoFocus: true,
|
|
4344
|
-
onChange: (event) => set({ title: event.target.value })
|
|
4345
|
-
})
|
|
4346
|
-
),
|
|
4347
|
-
(0, import_react11.createElement)(
|
|
4348
|
-
"div",
|
|
4349
|
-
{ className: "mailharbor-field" },
|
|
4350
|
-
(0, import_react11.createElement)("label", null, "\u5907\u6CE8"),
|
|
4351
|
-
(0, import_react11.createElement)("textarea", {
|
|
4352
|
-
rows: 3,
|
|
4353
|
-
value: draft.notes,
|
|
4354
|
-
onChange: (event) => set({ notes: event.target.value })
|
|
4355
|
-
})
|
|
4356
|
-
),
|
|
4357
|
-
(0, import_react11.createElement)(
|
|
4358
|
-
"div",
|
|
4359
|
-
{ className: "mailharbor-grid" },
|
|
4360
|
-
(0, import_react11.createElement)(
|
|
4361
|
-
"div",
|
|
4362
|
-
{ className: "mailharbor-field" },
|
|
4363
|
-
(0, import_react11.createElement)("label", null, "\u622A\u6B62\u65E5\u671F"),
|
|
4364
|
-
(0, import_react11.createElement)("input", {
|
|
4365
|
-
type: "date",
|
|
4366
|
-
value: draft.due,
|
|
4367
|
-
onChange: (event) => set({ due: event.target.value })
|
|
4368
|
-
})
|
|
4369
|
-
),
|
|
4370
|
-
(0, import_react11.createElement)(
|
|
4371
|
-
"div",
|
|
4372
|
-
{ className: "mailharbor-field" },
|
|
4373
|
-
(0, import_react11.createElement)("label", null, "\u4F18\u5148\u7EA7"),
|
|
4374
|
-
(0, import_react11.createElement)(
|
|
4375
|
-
"select",
|
|
4376
4384
|
{
|
|
4377
|
-
|
|
4378
|
-
|
|
4385
|
+
className: "primary",
|
|
4386
|
+
disabled: busy || draft.title.trim() === "",
|
|
4387
|
+
onClick: () => onSave(draft)
|
|
4379
4388
|
},
|
|
4380
|
-
|
|
4381
|
-
(0, import_react11.createElement)("option", { value: "5" }, "\u4E2D"),
|
|
4382
|
-
(0, import_react11.createElement)("option", { value: "9" }, "\u9AD8")
|
|
4389
|
+
busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u4EFB\u52A1"
|
|
4383
4390
|
)
|
|
4384
4391
|
)
|
|
4385
|
-
|
|
4392
|
+
},
|
|
4386
4393
|
(0, import_react11.createElement)(
|
|
4387
4394
|
"div",
|
|
4388
|
-
{ className: "
|
|
4395
|
+
{ className: "task-editor" },
|
|
4389
4396
|
(0, import_react11.createElement)(
|
|
4390
4397
|
"div",
|
|
4391
|
-
{ className: "
|
|
4392
|
-
(0, import_react11.createElement)("label", null, "\u72B6\u6001"),
|
|
4398
|
+
{ className: "task-editor-main" },
|
|
4393
4399
|
(0, import_react11.createElement)(
|
|
4394
|
-
"
|
|
4395
|
-
{
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
+
"div",
|
|
4401
|
+
{ className: "task-editor-title" },
|
|
4402
|
+
(0, import_react11.createElement)("input", {
|
|
4403
|
+
value: draft.title,
|
|
4404
|
+
autoFocus: true,
|
|
4405
|
+
placeholder: "\u8981\u505A\u4EC0\u4E48\uFF1F",
|
|
4406
|
+
onChange: (event) => set({ title: event.target.value })
|
|
4407
|
+
})
|
|
4408
|
+
),
|
|
4409
|
+
(0, import_react11.createElement)(
|
|
4410
|
+
"div",
|
|
4411
|
+
{ className: "task-editor-section" },
|
|
4412
|
+
(0, import_react11.createElement)("label", null, "\u5907\u6CE8"),
|
|
4413
|
+
(0, import_react11.createElement)("textarea", {
|
|
4414
|
+
rows: 5,
|
|
4415
|
+
value: draft.notes,
|
|
4416
|
+
placeholder: "\u80CC\u666F\u3001\u4E0B\u4E00\u6B65\u3001\u94FE\u63A5\u2026",
|
|
4417
|
+
onChange: (event) => set({ notes: event.target.value })
|
|
4418
|
+
})
|
|
4419
|
+
),
|
|
4420
|
+
(0, import_react11.createElement)(
|
|
4421
|
+
"div",
|
|
4422
|
+
{ className: "task-editor-section" },
|
|
4423
|
+
(0, import_react11.createElement)("label", null, "\u6765\u6E90"),
|
|
4424
|
+
draft.id === void 0 ? (0, import_react11.createElement)("span", { className: "small muted" }, "\u4ECE\u90AE\u4EF6\u63D0\u53D6\u7684\u4EFB\u52A1\u4F1A\u81EA\u52A8\u5173\u8054\u6765\u6E90\u90AE\u4EF6\u3002") : (0, import_react11.createElement)(
|
|
4425
|
+
"span",
|
|
4426
|
+
{ className: "small muted" },
|
|
4427
|
+
task?.messageSubject !== void 0 && task.messageSubject !== "" ? `\u6765\u81EA\u90AE\u4EF6\uFF1A${task.messageSubject}` : "\u65E0\u5173\u8054\u90AE\u4EF6"
|
|
4428
|
+
)
|
|
4400
4429
|
)
|
|
4401
4430
|
),
|
|
4402
4431
|
(0, import_react11.createElement)(
|
|
4403
|
-
"
|
|
4404
|
-
{ className: "
|
|
4405
|
-
(0, import_react11.createElement)("label", null, "\u9879\u76EE"),
|
|
4432
|
+
"aside",
|
|
4433
|
+
{ className: "task-editor-side" },
|
|
4406
4434
|
(0, import_react11.createElement)(
|
|
4407
|
-
"
|
|
4408
|
-
{
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4435
|
+
"div",
|
|
4436
|
+
{ className: "task-editor-field" },
|
|
4437
|
+
(0, import_react11.createElement)("label", null, "\u72B6\u6001"),
|
|
4438
|
+
(0, import_react11.createElement)(
|
|
4439
|
+
"select",
|
|
4440
|
+
{
|
|
4441
|
+
value: draft.status,
|
|
4442
|
+
onChange: (event) => set({ status: event.target.value })
|
|
4443
|
+
},
|
|
4444
|
+
COLUMNS.map((column) => (0, import_react11.createElement)("option", { key: column.status, value: column.status }, column.label))
|
|
4445
|
+
)
|
|
4446
|
+
),
|
|
4447
|
+
(0, import_react11.createElement)(
|
|
4448
|
+
"div",
|
|
4449
|
+
{ className: "task-editor-field" },
|
|
4450
|
+
(0, import_react11.createElement)("label", null, "\u622A\u6B62\u65E5\u671F"),
|
|
4451
|
+
(0, import_react11.createElement)("input", {
|
|
4452
|
+
type: "date",
|
|
4453
|
+
value: draft.due,
|
|
4454
|
+
onChange: (event) => set({ due: event.target.value })
|
|
4455
|
+
})
|
|
4456
|
+
),
|
|
4457
|
+
(0, import_react11.createElement)(
|
|
4458
|
+
"div",
|
|
4459
|
+
{ className: "task-editor-field" },
|
|
4460
|
+
(0, import_react11.createElement)("label", null, "\u4F18\u5148\u7EA7"),
|
|
4461
|
+
(0, import_react11.createElement)(
|
|
4462
|
+
"div",
|
|
4463
|
+
{ className: "task-priority-picker" },
|
|
4464
|
+
[0, 5, 9].map(
|
|
4465
|
+
(value) => (0, import_react11.createElement)(
|
|
4466
|
+
"button",
|
|
4467
|
+
{
|
|
4468
|
+
key: value,
|
|
4469
|
+
className: draft.priority === value ? "active" : "",
|
|
4470
|
+
onClick: () => set({ priority: value })
|
|
4471
|
+
},
|
|
4472
|
+
PRIORITY_LABEL[value] ?? String(value)
|
|
4473
|
+
)
|
|
4474
|
+
)
|
|
4475
|
+
)
|
|
4476
|
+
),
|
|
4477
|
+
(0, import_react11.createElement)(
|
|
4478
|
+
"div",
|
|
4479
|
+
{ className: "task-editor-field" },
|
|
4480
|
+
(0, import_react11.createElement)("label", null, "\u9879\u76EE"),
|
|
4481
|
+
(0, import_react11.createElement)(
|
|
4482
|
+
"select",
|
|
4483
|
+
{
|
|
4484
|
+
value: draft.projectId,
|
|
4485
|
+
onChange: (event) => set({ projectId: event.target.value })
|
|
4486
|
+
},
|
|
4487
|
+
(0, import_react11.createElement)("option", { value: "" }, "\u65E0\u9879\u76EE"),
|
|
4488
|
+
projects.map((project) => (0, import_react11.createElement)("option", { key: project.id, value: project.id }, project.name))
|
|
4489
|
+
)
|
|
4490
|
+
),
|
|
4491
|
+
(0, import_react11.createElement)(
|
|
4492
|
+
"div",
|
|
4493
|
+
{ className: "task-editor-field" },
|
|
4494
|
+
(0, import_react11.createElement)("label", null, "\u8D1F\u8D23\u4EBA"),
|
|
4495
|
+
(0, import_react11.createElement)("input", {
|
|
4496
|
+
value: draft.owner,
|
|
4497
|
+
placeholder: "\u53EF\u9009",
|
|
4498
|
+
onChange: (event) => set({ owner: event.target.value })
|
|
4499
|
+
})
|
|
4414
4500
|
)
|
|
4415
4501
|
)
|
|
4416
|
-
),
|
|
4417
|
-
(0, import_react11.createElement)(
|
|
4418
|
-
"div",
|
|
4419
|
-
{ className: "mailharbor-field" },
|
|
4420
|
-
(0, import_react11.createElement)("label", null, "\u8D1F\u8D23\u4EBA"),
|
|
4421
|
-
(0, import_react11.createElement)("input", {
|
|
4422
|
-
value: draft.owner,
|
|
4423
|
-
onChange: (event) => set({ owner: event.target.value })
|
|
4424
|
-
})
|
|
4425
4502
|
)
|
|
4426
4503
|
);
|
|
4427
4504
|
}
|
|
4428
4505
|
|
|
4429
4506
|
// src/client/index.tsx
|
|
4430
|
-
var VERSION = "0.
|
|
4507
|
+
var VERSION = "0.7.0";
|
|
4431
4508
|
var inject = ["slots"];
|
|
4432
4509
|
var PANEL_ID = "mailharbor";
|
|
4433
4510
|
var TASKS_PANEL_ID = "mailharbor-tasks";
|