@zaclinley/dsh-mailharbor 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1987 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@zaclinley/dsh-mailharbor",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+
7
+ "use strict";
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // src/client/index.tsx
27
+ var index_exports = {};
28
+ __export(index_exports, {
29
+ PANEL_ID: () => PANEL_ID,
30
+ VERSION: () => VERSION,
31
+ apply: () => apply,
32
+ inject: () => inject
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/client/styles.css
37
+ var styles_default = "/* MailHarbor panel + settings. Colors come from the DSH shell tokens with\n light-mode fallbacks so the plugin also renders in a bare host page. */\n\n.mailharbor {\n --m-bg: var(--dsw-alias-bg-base, #ffffff);\n --m-bg-1: var(--dsw-alias-bg-layer-1, #f6f7f9);\n --m-bg-2: var(--dsw-alias-bg-layer-2, #eceef2);\n --m-bg-3: var(--dsw-alias-bg-layer-3, #e4e7ec);\n --m-line: var(--dsw-alias-border-l2, rgba(15, 23, 42, 0.1));\n --m-line-soft: var(--dsw-alias-border-l1, rgba(15, 23, 42, 0.06));\n --m-text: var(--dsw-alias-label-primary, #171a21);\n --m-text-2: var(--dsw-alias-label-secondary, #565c67);\n --m-text-3: var(--dsw-alias-label-tertiary, #8a909c);\n --m-brand: var(--dsw-alias-brand-primary, #4d6bfe);\n --m-brand-soft: color-mix(in srgb, var(--m-brand) 12%, transparent);\n --m-hover: var(--dsw-alias-interactive-bg-hover, rgba(15, 23, 42, 0.05));\n --m-danger: var(--dsw-alias-label-error, #d93025);\n --m-ok: var(--dsw-alias-state-success-primary, #1a9e5c);\n --m-warn: var(--dsw-alias-state-warn-primary, #d88a2a);\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}\n\n.mailharbor *,\n.mailharbor *::before,\n.mailharbor *::after {\n box-sizing: border-box;\n}\n\n/* ---------- shell ---------- */\n\n.mailharbor-topbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-body {\n display: flex;\n flex: 1;\n min-height: 0;\n}\n\n.mailharbor-list {\n display: flex;\n flex-direction: column;\n width: 340px;\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: 900px) {\n .mailharbor-list {\n width: 280px;\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/* ---------- controls ---------- */\n\n.mailharbor button {\n font: inherit;\n color: inherit;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 7px;\n padding: 5px 9px;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n white-space: nowrap;\n}\n\n.mailharbor button:hover:not(:disabled) {\n background: var(--m-hover);\n}\n\n.mailharbor button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.mailharbor button.primary {\n background: var(--m-brand);\n color: #fff;\n}\n\n.mailharbor button.primary:hover:not(:disabled) {\n filter: brightness(1.05);\n background: var(--m-brand);\n}\n\n.mailharbor button.ghost {\n border-color: var(--m-line);\n}\n\n.mailharbor button.subtle {\n color: var(--m-text-2);\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: inherit;\n background: var(--m-bg-1);\n border: 1px solid var(--m-line);\n border-radius: 7px;\n padding: 6px 8px;\n min-width: 0;\n}\n\n.mailharbor input:focus,\n.mailharbor select:focus,\n.mailharbor textarea:focus,\n.mailharbor [contenteditable='true']:focus {\n outline: 2px solid color-mix(in srgb, var(--m-brand) 45%, transparent);\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 font-size: 11px;\n padding: 1px 6px;\n border-radius: 999px;\n background: var(--m-bg-2);\n color: var(--m-text-2);\n}\n\n.mailharbor .badge.ok {\n background: color-mix(in srgb, var(--m-ok) 15%, transparent);\n color: var(--m-ok);\n}\n\n.mailharbor .badge.warn {\n background: color-mix(in srgb, var(--m-warn) 16%, transparent);\n color: var(--m-warn);\n}\n\n.mailharbor .error {\n color: var(--m-danger);\n padding: 8px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n background: color-mix(in srgb, var(--m-danger) 7%, transparent);\n}\n\n/* ---------- list ---------- */\n\n.mailharbor-filters {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 8px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n}\n\n.mailharbor-filters input[type='search'] {\n flex: 1;\n}\n\n.mailharbor-segment {\n display: inline-flex;\n background: var(--m-bg-2);\n border-radius: 8px;\n padding: 2px;\n gap: 2px;\n}\n\n.mailharbor-segment button {\n padding: 3px 10px;\n border-radius: 6px;\n color: var(--m-text-2);\n}\n\n.mailharbor-segment button.active {\n background: var(--m-bg);\n color: var(--m-text);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);\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: 2px;\n padding: 9px 12px;\n border-bottom: 1px solid var(--m-line-soft);\n cursor: pointer;\n text-align: left;\n width: 100%;\n border-radius: 0;\n border-left: 2px solid transparent;\n}\n\n.mailharbor-row:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-row.selected {\n background: var(--m-brand-soft);\n border-left-color: var(--m-brand);\n}\n\n.mailharbor-row .line-1,\n.mailharbor-row .line-2 {\n display: flex;\n gap: 8px;\n align-items: baseline;\n min-width: 0;\n}\n\n.mailharbor-row .from {\n font-weight: 600;\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-text-3);\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}\n\n.mailharbor-row.unread .subject,\n.mailharbor-row.unread .from {\n color: var(--m-text);\n}\n\n.mailharbor-row.unread .from::before {\n content: '';\n display: inline-block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--m-brand);\n margin-right: 6px;\n vertical-align: middle;\n}\n\n.mailharbor-row .snippet {\n color: var(--m-text-3);\n font-size: 12px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mailharbor-empty {\n padding: 40px 20px;\n text-align: center;\n color: var(--m-text-3);\n}\n\n/* ---------- reader ---------- */\n\n.mailharbor-reader-head {\n padding: 10px 14px;\n border-bottom: 1px solid var(--m-line-soft);\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.mailharbor-reader-head h2 {\n margin: 0;\n font-size: 15px;\n line-height: 1.35;\n word-break: break-word;\n}\n\n.mailharbor-reader-meta {\n color: var(--m-text-2);\n font-size: 12px;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 12px;\n}\n\n.mailharbor-reader-actions {\n display: flex;\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 background: var(--m-bg-1);\n}\n\n.mailharbor-reader-body iframe {\n flex: 1;\n width: 100%;\n border: 0;\n background: #fff;\n}\n\n.mailharbor-reader-body pre {\n flex: 1;\n overflow: auto;\n margin: 0;\n padding: 16px 18px;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n line-height: 1.6;\n}\n\n.mailharbor-attachments {\n padding: 8px 14px;\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: 6px;\n border: 1px solid var(--m-line);\n border-radius: 8px;\n padding: 4px 8px;\n font-size: 12px;\n text-decoration: none;\n color: var(--m-text);\n max-width: 260px;\n}\n\n.mailharbor-attachment:hover {\n background: var(--m-hover);\n}\n\n.mailharbor-attachment span.name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ---------- modal / form ---------- */\n\n.mailharbor-modal-mask {\n position: fixed;\n inset: 0;\n background: rgba(15, 23, 42, 0.35);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 60;\n padding: 24px;\n}\n\n.mailharbor-modal {\n background: var(--m-bg);\n color: var(--m-text);\n border-radius: 12px;\n box-shadow: 0 18px 60px rgba(15, 23, 42, 0.28);\n width: min(760px, 100%);\n max-height: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.mailharbor-modal header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--m-line-soft);\n font-weight: 600;\n}\n\n.mailharbor-modal .content {\n padding: 16px;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.mailharbor-modal footer {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n padding: 12px 16px;\n border-top: 1px solid var(--m-line-soft);\n}\n\n.mailharbor-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.mailharbor-field > label {\n font-size: 12px;\n color: var(--m-text-2);\n}\n\n.mailharbor-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n}\n\n.mailharbor-grid.three {\n grid-template-columns: 2fr 1fr auto;\n}\n\n.mailharbor-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: var(--m-text-2);\n cursor: pointer;\n}\n\n/* ---------- composer ---------- */\n\n.mailharbor-editor {\n min-height: 220px;\n max-height: 46vh;\n overflow: auto;\n padding: 10px 12px;\n border: 1px solid var(--m-line);\n border-radius: 8px;\n background: var(--m-bg);\n outline: none;\n}\n\n.mailharbor-editor:empty::before {\n content: attr(data-placeholder);\n color: var(--m-text-3);\n}\n\n.mailharbor-toolbar {\n display: flex;\n gap: 2px;\n flex-wrap: wrap;\n}\n\n.mailharbor-toolbar button {\n padding: 3px 8px;\n font-size: 12px;\n border: 1px solid transparent;\n}\n\n.mailharbor-toolbar button:hover {\n border-color: var(--m-line);\n}\n\n/* ---------- settings ---------- */\n\n.mailharbor-settings {\n padding: 16px 20px 40px;\n display: flex;\n flex-direction: column;\n gap: 18px;\n max-width: 860px;\n}\n\n.mailharbor-settings h2 {\n margin: 0 0 4px;\n font-size: 15px;\n}\n\n.mailharbor-settings h3 {\n margin: 0 0 8px;\n font-size: 13px;\n color: var(--m-text-2);\n font-weight: 600;\n}\n\n.mailharbor-card {\n border: 1px solid var(--m-line-soft);\n border-radius: 10px;\n padding: 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: var(--m-bg);\n}\n\n.mailharbor-account-row {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.mailharbor-account-row .avatar {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n background: var(--m-brand-soft);\n color: var(--m-brand);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n flex: none;\n}\n\n.mailharbor-log {\n background: var(--m-bg-1);\n border-radius: 8px;\n padding: 8px 10px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n line-height: 1.5;\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, 180px) 1fr;\n gap: 4px 12px;\n font-size: 12px;\n}\n\n.mailharbor-kv dt {\n color: var(--m-text-3);\n}\n\n.mailharbor-kv dd {\n margin: 0;\n word-break: break-all;\n}\n";
38
+
39
+ // src/client/icon.tsx
40
+ var import_react = require("react");
41
+
42
+ // src/client/api.ts
43
+ var BASE = new URL("mail-plugin/api/", document.baseURI).href;
44
+ var ApiError = class extends Error {
45
+ status;
46
+ constructor(message, status) {
47
+ super(message);
48
+ this.status = status;
49
+ this.name = "ApiError";
50
+ }
51
+ };
52
+ async function request(path, init = {}) {
53
+ const response = await fetch(new URL(path, BASE), {
54
+ credentials: "same-origin",
55
+ ...init,
56
+ headers: {
57
+ accept: "application/json",
58
+ ...init.body === void 0 ? {} : { "content-type": "application/json" },
59
+ ...init.headers ?? {}
60
+ }
61
+ });
62
+ if (!response.ok) {
63
+ let message = response.statusText;
64
+ try {
65
+ const body = await response.json();
66
+ if (body !== null && typeof body === "object" && typeof body.error === "string") {
67
+ message = body.error;
68
+ }
69
+ } catch {
70
+ }
71
+ throw new ApiError(message, response.status);
72
+ }
73
+ return await response.json();
74
+ }
75
+ function query(params) {
76
+ const search = new URLSearchParams();
77
+ for (const [key, value] of Object.entries(params)) {
78
+ if (value !== void 0 && value !== null && value !== "") search.set(key, String(value));
79
+ }
80
+ const text = search.toString();
81
+ return text === "" ? "" : `?${text}`;
82
+ }
83
+ var api = {
84
+ state: () => request("state"),
85
+ folders: (params) => request(`folders${query(params)}`),
86
+ messages: (params) => request(`messages${query({ ...params })}`),
87
+ message: (params) => request(`messages/${encodeURIComponent(params.id)}${query({ ...params })}`),
88
+ setRead: (params) => request(
89
+ `messages/${encodeURIComponent(params.id)}/read${query({
90
+ accountId: params.accountId ?? null,
91
+ source: params.source,
92
+ mailbox: params.mailbox
93
+ })}`,
94
+ { method: "POST", body: JSON.stringify({ read: params.read }) }
95
+ ),
96
+ attachmentUrl: (params) => new URL(`attachment${query(params)}`, BASE).href,
97
+ drafts: (accountId) => request(`drafts${query({ accountId: accountId ?? null })}`),
98
+ saveDraft: (accountId, draft) => request(`drafts${query({ accountId: accountId ?? null })}`, {
99
+ method: "POST",
100
+ body: JSON.stringify(draft)
101
+ }),
102
+ deleteDraft: (id, accountId) => request(`drafts/${encodeURIComponent(id)}${query({ accountId: accountId ?? null })}`, {
103
+ method: "DELETE"
104
+ }),
105
+ send: (accountId, payload) => request(`send${query({ accountId: accountId ?? null })}`, {
106
+ method: "POST",
107
+ body: JSON.stringify(payload)
108
+ }),
109
+ unread: (accountId) => request(`unread${query({ accountId: accountId ?? null })}`),
110
+ addAccount: (input) => request("accounts", {
111
+ method: "POST",
112
+ body: JSON.stringify(input)
113
+ }),
114
+ updateAccount: (id, patch) => request(`accounts/${encodeURIComponent(id)}`, {
115
+ method: "PATCH",
116
+ body: JSON.stringify(patch)
117
+ }),
118
+ deleteAccount: (id) => request(`accounts/${encodeURIComponent(id)}`, { method: "DELETE" }),
119
+ activateAccount: (id) => request(`accounts/${encodeURIComponent(id)}/activate`, { method: "POST" }),
120
+ login: (id, password, remember) => request(`accounts/${encodeURIComponent(id)}/login`, {
121
+ method: "POST",
122
+ body: JSON.stringify({ password, remember })
123
+ }),
124
+ logout: (id) => request(`accounts/${encodeURIComponent(id)}/logout`, { method: "POST" }),
125
+ check: (id) => request(`accounts/${encodeURIComponent(id)}/check`, { method: "POST" }),
126
+ settings: () => request("settings"),
127
+ saveSettings: (patch) => request("settings", { method: "PATCH", body: JSON.stringify(patch) }),
128
+ backupStatus: (accountId) => request(`backup/status${query({ accountId: accountId ?? null })}`),
129
+ startBackup: (accountId) => request(`backup/start${query({ accountId: accountId ?? null })}`, {
130
+ method: "POST"
131
+ }),
132
+ stopBackup: (accountId) => request(`backup/stop${query({ accountId: accountId ?? null })}`, {
133
+ method: "POST"
134
+ })
135
+ };
136
+
137
+ // src/client/icon.tsx
138
+ function MailIcon() {
139
+ const [unread, setUnread] = (0, import_react.useState)(0);
140
+ (0, import_react.useEffect)(() => {
141
+ let cancelled = false;
142
+ let timer;
143
+ const poll = async () => {
144
+ try {
145
+ const result = await api.unread();
146
+ if (!cancelled) setUnread(result.unread);
147
+ } catch {
148
+ }
149
+ };
150
+ void poll();
151
+ const schedule = () => {
152
+ if (cancelled) return;
153
+ timer = window.setTimeout(() => {
154
+ void poll().finally(schedule);
155
+ }, 6e4);
156
+ };
157
+ schedule();
158
+ return () => {
159
+ cancelled = true;
160
+ if (timer !== void 0) window.clearTimeout(timer);
161
+ };
162
+ }, []);
163
+ return (0, import_react.createElement)(
164
+ "span",
165
+ {
166
+ style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center" }
167
+ },
168
+ (0, import_react.createElement)(
169
+ "svg",
170
+ { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" },
171
+ (0, import_react.createElement)("rect", { x: 2.5, y: 5, width: 19, height: 14, rx: 2.5, stroke: "currentColor", strokeWidth: 1.6 }),
172
+ (0, import_react.createElement)("path", {
173
+ d: "M3.5 7l7.6 5.4a1.6 1.6 0 0 0 1.8 0L20.5 7",
174
+ stroke: "currentColor",
175
+ strokeWidth: 1.6,
176
+ strokeLinecap: "round"
177
+ })
178
+ ),
179
+ unread > 0 ? (0, import_react.createElement)(
180
+ "span",
181
+ {
182
+ style: {
183
+ position: "absolute",
184
+ top: "-4px",
185
+ right: "-6px",
186
+ minWidth: "14px",
187
+ height: "14px",
188
+ padding: "0 3px",
189
+ borderRadius: "999px",
190
+ background: "var(--dsw-alias-brand-primary, #4d6bfe)",
191
+ color: "#fff",
192
+ fontSize: "9px",
193
+ lineHeight: "14px",
194
+ textAlign: "center",
195
+ fontWeight: 600
196
+ }
197
+ },
198
+ unread > 99 ? "99+" : String(unread)
199
+ ) : null
200
+ );
201
+ }
202
+
203
+ // src/client/panel.tsx
204
+ var import_react6 = require("react");
205
+
206
+ // src/client/account-form.tsx
207
+ var import_react3 = require("react");
208
+
209
+ // src/client/components.tsx
210
+ var import_react2 = require("react");
211
+ var import_react_dom = require("react-dom");
212
+ function Avatar({ email, size = 26 }) {
213
+ const letter = (email.trim()[0] ?? "?").toUpperCase();
214
+ return (0, import_react2.createElement)(
215
+ "span",
216
+ {
217
+ className: "avatar",
218
+ style: {
219
+ width: `${String(size)}px`,
220
+ height: `${String(size)}px`,
221
+ fontSize: `${String(Math.round(size * 0.42))}px`
222
+ },
223
+ "aria-hidden": "true"
224
+ },
225
+ letter
226
+ );
227
+ }
228
+ function Field({
229
+ label,
230
+ children,
231
+ hint
232
+ }) {
233
+ return (0, import_react2.createElement)(
234
+ "div",
235
+ { className: "mailharbor-field" },
236
+ (0, import_react2.createElement)("label", null, label),
237
+ children,
238
+ hint === void 0 ? null : (0, import_react2.createElement)("span", { className: "small muted" }, hint)
239
+ );
240
+ }
241
+ function Toggle({
242
+ checked,
243
+ onChange,
244
+ label
245
+ }) {
246
+ return (0, import_react2.createElement)(
247
+ "label",
248
+ { className: "mailharbor-toggle" },
249
+ (0, import_react2.createElement)("input", {
250
+ type: "checkbox",
251
+ checked,
252
+ onChange: (event) => {
253
+ onChange(event.target.checked);
254
+ }
255
+ }),
256
+ label
257
+ );
258
+ }
259
+ function Modal({
260
+ title,
261
+ children,
262
+ footer,
263
+ onClose,
264
+ wide = false
265
+ }) {
266
+ (0, import_react2.useEffect)(() => {
267
+ const onKey = (event) => {
268
+ if (event.key === "Escape") onClose();
269
+ };
270
+ window.addEventListener("keydown", onKey);
271
+ return () => {
272
+ window.removeEventListener("keydown", onKey);
273
+ };
274
+ }, [onClose]);
275
+ return (0, import_react_dom.createPortal)(
276
+ (0, import_react2.createElement)(
277
+ "div",
278
+ {
279
+ className: "mailharbor-modal-mask",
280
+ onMouseDown: (event) => {
281
+ if (event.target === event.currentTarget) onClose();
282
+ }
283
+ },
284
+ (0, import_react2.createElement)(
285
+ "div",
286
+ {
287
+ className: "mailharbor-modal",
288
+ style: wide ? { width: "min(920px, 100%)" } : void 0,
289
+ role: "dialog",
290
+ "aria-modal": "true",
291
+ "aria-label": title
292
+ },
293
+ (0, import_react2.createElement)(
294
+ "header",
295
+ null,
296
+ title,
297
+ (0, import_react2.createElement)("span", { className: "spacer" }),
298
+ (0, import_react2.createElement)("button", { className: "subtle", onClick: onClose, "aria-label": "\u5173\u95ED" }, "\u2715")
299
+ ),
300
+ (0, import_react2.createElement)("div", { className: "content" }, children),
301
+ footer === void 0 ? null : (0, import_react2.createElement)("footer", null, footer)
302
+ )
303
+ ),
304
+ document.body
305
+ );
306
+ }
307
+ function ErrorBar({ message, onDismiss }) {
308
+ return (0, import_react2.createElement)(
309
+ "div",
310
+ { className: "error", role: "alert" },
311
+ (0, import_react2.createElement)("span", null, message),
312
+ (0, import_react2.createElement)("span", { className: "spacer" }),
313
+ (0, import_react2.createElement)("button", { className: "subtle", onClick: onDismiss }, "\u5173\u95ED")
314
+ );
315
+ }
316
+ function shortDate(iso) {
317
+ const date = new Date(iso);
318
+ if (Number.isNaN(date.getTime())) return iso.slice(0, 16);
319
+ const now = /* @__PURE__ */ new Date();
320
+ const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
321
+ const pad = (value) => String(value).padStart(2, "0");
322
+ if (sameDay) return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
323
+ if (date.getFullYear() === now.getFullYear()) return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
324
+ return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
325
+ }
326
+ function longDate(iso) {
327
+ const date = new Date(iso);
328
+ if (Number.isNaN(date.getTime())) return iso;
329
+ return date.toLocaleString(void 0, {
330
+ year: "numeric",
331
+ month: "2-digit",
332
+ day: "2-digit",
333
+ hour: "2-digit",
334
+ minute: "2-digit"
335
+ });
336
+ }
337
+ function formatBytes(size) {
338
+ if (!Number.isFinite(size) || size <= 0) return "";
339
+ if (size < 1024) return `${String(size)} B`;
340
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
341
+ return `${(size / 1024 / 1024).toFixed(1)} MB`;
342
+ }
343
+
344
+ // src/client/account-form.tsx
345
+ function domainOf(email) {
346
+ return email.split("@")[1] ?? "example.com";
347
+ }
348
+ function errorText(error) {
349
+ return error instanceof ApiError || error instanceof Error ? error.message : String(error);
350
+ }
351
+ function AddAccountForm({
352
+ onDone,
353
+ onCancel
354
+ }) {
355
+ const [email, setEmail] = (0, import_react3.useState)("");
356
+ const [password, setPassword] = (0, import_react3.useState)("");
357
+ const [label, setLabel] = (0, import_react3.useState)("");
358
+ const [remember, setRemember] = (0, import_react3.useState)(true);
359
+ const [advanced, setAdvanced] = (0, import_react3.useState)(false);
360
+ const [imapHost, setImapHost] = (0, import_react3.useState)("");
361
+ const [imapPort, setImapPort] = (0, import_react3.useState)("993");
362
+ const [imapSecure, setImapSecure] = (0, import_react3.useState)(true);
363
+ const [smtpHost, setSmtpHost] = (0, import_react3.useState)("");
364
+ const [smtpPort, setSmtpPort] = (0, import_react3.useState)("465");
365
+ const [smtpSecure, setSmtpSecure] = (0, import_react3.useState)(true);
366
+ const [fromName, setFromName] = (0, import_react3.useState)("");
367
+ const [busy, setBusy] = (0, import_react3.useState)(false);
368
+ const [error, setError] = (0, import_react3.useState)("");
369
+ const submit = async () => {
370
+ setBusy(true);
371
+ setError("");
372
+ try {
373
+ const domain = domainOf(email);
374
+ const { state } = await api.addAccount({
375
+ email: email.trim(),
376
+ password,
377
+ remember,
378
+ ...label.trim() === "" ? {} : { label: label.trim() },
379
+ ...advanced ? {
380
+ imap: {
381
+ host: imapHost.trim() === "" ? `imap.${domain}` : imapHost.trim(),
382
+ port: Number(imapPort) || 993,
383
+ secure: imapSecure
384
+ },
385
+ smtp: {
386
+ host: smtpHost.trim() === "" ? `smtp.${domain}` : smtpHost.trim(),
387
+ port: Number(smtpPort) || 465,
388
+ secure: smtpSecure,
389
+ ...fromName.trim() === "" ? {} : { fromName: fromName.trim() }
390
+ }
391
+ } : {}
392
+ });
393
+ onDone(state);
394
+ } catch (caught) {
395
+ setError(errorText(caught));
396
+ } finally {
397
+ setBusy(false);
398
+ }
399
+ };
400
+ return (0, import_react3.createElement)(
401
+ "div",
402
+ { className: "mailharbor-card" },
403
+ (0, import_react3.createElement)("h3", null, "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7"),
404
+ (0, import_react3.createElement)(
405
+ "div",
406
+ { className: "mailharbor-grid" },
407
+ (0, import_react3.createElement)(
408
+ Field,
409
+ { label: "\u90AE\u7BB1\u5730\u5740" },
410
+ (0, import_react3.createElement)("input", {
411
+ type: "email",
412
+ value: email,
413
+ placeholder: "you@example.com",
414
+ onChange: (event) => setEmail(event.target.value),
415
+ autoFocus: true
416
+ })
417
+ ),
418
+ (0, import_react3.createElement)(
419
+ Field,
420
+ { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
421
+ (0, import_react3.createElement)("input", {
422
+ type: "password",
423
+ value: password,
424
+ onChange: (event) => setPassword(event.target.value)
425
+ })
426
+ )
427
+ ),
428
+ (0, import_react3.createElement)(
429
+ Field,
430
+ { label: "\u663E\u793A\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
431
+ (0, import_react3.createElement)("input", {
432
+ value: label,
433
+ placeholder: "\u5DE5\u4F5C\u90AE\u7BB1",
434
+ onChange: (event) => setLabel(event.target.value)
435
+ })
436
+ ),
437
+ (0, import_react3.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
438
+ (0, import_react3.createElement)(
439
+ "button",
440
+ { className: "ghost", onClick: () => setAdvanced(!advanced) },
441
+ advanced ? "\u9690\u85CF\u9AD8\u7EA7\u8BBE\u7F6E" : "\u9AD8\u7EA7\u8BBE\u7F6E\uFF08\u670D\u52A1\u5668\u4E0E\u7AEF\u53E3\uFF09"
442
+ ),
443
+ advanced ? (0, import_react3.createElement)(
444
+ "div",
445
+ { style: { display: "flex", flexDirection: "column", gap: "10px" } },
446
+ (0, import_react3.createElement)(
447
+ "div",
448
+ { className: "mailharbor-grid three" },
449
+ (0, import_react3.createElement)(
450
+ Field,
451
+ { label: "IMAP \u4E3B\u673A" },
452
+ (0, import_react3.createElement)("input", {
453
+ value: imapHost,
454
+ placeholder: `imap.${domainOf(email)}`,
455
+ onChange: (event) => setImapHost(event.target.value)
456
+ })
457
+ ),
458
+ (0, import_react3.createElement)(
459
+ Field,
460
+ { label: "\u7AEF\u53E3" },
461
+ (0, import_react3.createElement)("input", {
462
+ value: imapPort,
463
+ inputMode: "numeric",
464
+ onChange: (event) => setImapPort(event.target.value)
465
+ })
466
+ ),
467
+ (0, import_react3.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
468
+ ),
469
+ (0, import_react3.createElement)(
470
+ "div",
471
+ { className: "mailharbor-grid three" },
472
+ (0, import_react3.createElement)(
473
+ Field,
474
+ { label: "SMTP \u4E3B\u673A" },
475
+ (0, import_react3.createElement)("input", {
476
+ value: smtpHost,
477
+ placeholder: `smtp.${domainOf(email)}`,
478
+ onChange: (event) => setSmtpHost(event.target.value)
479
+ })
480
+ ),
481
+ (0, import_react3.createElement)(
482
+ Field,
483
+ { label: "\u7AEF\u53E3" },
484
+ (0, import_react3.createElement)("input", {
485
+ value: smtpPort,
486
+ inputMode: "numeric",
487
+ onChange: (event) => setSmtpPort(event.target.value)
488
+ })
489
+ ),
490
+ (0, import_react3.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
491
+ ),
492
+ (0, import_react3.createElement)(
493
+ Field,
494
+ { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0\uFF08\u53EF\u9009\uFF09" },
495
+ (0, import_react3.createElement)("input", {
496
+ value: fromName,
497
+ onChange: (event) => setFromName(event.target.value)
498
+ })
499
+ )
500
+ ) : null,
501
+ error === "" ? null : (0, import_react3.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
502
+ (0, import_react3.createElement)(
503
+ "div",
504
+ { style: { display: "flex", gap: "8px", justifyContent: "flex-end" } },
505
+ onCancel === void 0 ? null : (0, import_react3.createElement)("button", { className: "ghost", onClick: onCancel }, "\u53D6\u6D88"),
506
+ (0, import_react3.createElement)(
507
+ "button",
508
+ {
509
+ className: "primary",
510
+ onClick: () => void submit(),
511
+ disabled: busy || email.trim() === "" || password === ""
512
+ },
513
+ busy ? "\u8FDE\u63A5\u4E2D\u2026" : "\u6DFB\u52A0\u5E76\u8FDE\u63A5"
514
+ )
515
+ )
516
+ );
517
+ }
518
+ function LoginForm({ account, onDone }) {
519
+ const [password, setPassword] = (0, import_react3.useState)("");
520
+ const [remember, setRemember] = (0, import_react3.useState)(true);
521
+ const [busy, setBusy] = (0, import_react3.useState)(false);
522
+ const [error, setError] = (0, import_react3.useState)("");
523
+ const submit = async () => {
524
+ setBusy(true);
525
+ setError("");
526
+ try {
527
+ await api.login(account.id, password, remember);
528
+ onDone();
529
+ } catch (caught) {
530
+ setError(errorText(caught));
531
+ } finally {
532
+ setBusy(false);
533
+ }
534
+ };
535
+ return (0, import_react3.createElement)(
536
+ "div",
537
+ { className: "mailharbor-card" },
538
+ (0, import_react3.createElement)("h3", null, `\u767B\u5F55 ${account.email}`),
539
+ (0, import_react3.createElement)(
540
+ "p",
541
+ { className: "small muted" },
542
+ `${account.imap.host}:${String(account.imap.port)} \xB7 ${account.smtp.host}:${String(account.smtp.port)}`
543
+ ),
544
+ (0, import_react3.createElement)(
545
+ Field,
546
+ { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801" },
547
+ (0, import_react3.createElement)("input", {
548
+ type: "password",
549
+ value: password,
550
+ onChange: (event) => setPassword(event.target.value),
551
+ onKeyDown: (event) => {
552
+ if (event.key === "Enter") void submit();
553
+ },
554
+ autoFocus: true
555
+ })
556
+ ),
557
+ (0, import_react3.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801\uFF08\u5199\u5165\u7CFB\u7EDF\u94A5\u5319\u4E32\uFF09" }),
558
+ error === "" ? null : (0, import_react3.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
559
+ (0, import_react3.createElement)(
560
+ "button",
561
+ { className: "primary", onClick: () => void submit(), disabled: busy || password === "" },
562
+ busy ? "\u767B\u5F55\u4E2D\u2026" : "\u767B\u5F55"
563
+ )
564
+ );
565
+ }
566
+ function AddAccountModal({
567
+ onClose,
568
+ onDone
569
+ }) {
570
+ return (0, import_react3.createElement)(
571
+ Modal,
572
+ { title: "\u6DFB\u52A0\u90AE\u7BB1\u8D26\u53F7", onClose },
573
+ (0, import_react3.createElement)(AddAccountForm, {
574
+ onDone: (state) => {
575
+ onDone(state);
576
+ onClose();
577
+ },
578
+ onCancel: onClose
579
+ })
580
+ );
581
+ }
582
+
583
+ // src/client/composer.tsx
584
+ var import_react4 = require("react");
585
+ function errorText2(error) {
586
+ return error instanceof ApiError || error instanceof Error ? error.message : String(error);
587
+ }
588
+ function readFileBase64(file) {
589
+ return new Promise((resolve, reject) => {
590
+ const reader = new FileReader();
591
+ reader.onload = () => {
592
+ const result = String(reader.result ?? "");
593
+ resolve(result.slice(result.indexOf(",") + 1));
594
+ };
595
+ reader.onerror = () => reject(new Error(`\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${file.name}`));
596
+ reader.readAsDataURL(file);
597
+ });
598
+ }
599
+ function Composer({
600
+ account,
601
+ initial,
602
+ onClose,
603
+ onSent
604
+ }) {
605
+ const [to, setTo] = (0, import_react4.useState)(initial.to);
606
+ const [cc, setCc] = (0, import_react4.useState)(initial.cc);
607
+ const [bcc, setBcc] = (0, import_react4.useState)(initial.bcc);
608
+ const [subject, setSubject] = (0, import_react4.useState)(initial.subject);
609
+ const [showCc, setShowCc] = (0, import_react4.useState)(initial.cc !== "" || initial.bcc !== "");
610
+ const [attachments, setAttachments] = (0, import_react4.useState)(initial.attachments ?? []);
611
+ const [busy, setBusy] = (0, import_react4.useState)(false);
612
+ const [error, setError] = (0, import_react4.useState)("");
613
+ const [confirming, setConfirming] = (0, import_react4.useState)(false);
614
+ const [draftId, setDraftId] = (0, import_react4.useState)(initial.draftId);
615
+ const editor = (0, import_react4.useRef)(null);
616
+ (0, import_react4.useEffect)(() => {
617
+ if (editor.current !== null) editor.current.innerHTML = initial.html;
618
+ }, []);
619
+ const html = () => editor.current?.innerHTML ?? "";
620
+ const text = () => editor.current?.innerText ?? "";
621
+ const exec = (command, value) => {
622
+ editor.current?.focus();
623
+ document.execCommand(command, false, value);
624
+ };
625
+ const addFiles = async (files) => {
626
+ if (files === null || files.length === 0) return;
627
+ setError("");
628
+ const next = [...attachments];
629
+ let total = next.reduce((sum, file) => sum + file.data.length, 0);
630
+ for (const file of Array.from(files)) {
631
+ if (total + file.size > 8 * 1024 * 1024) {
632
+ setError("\u9644\u4EF6\u5408\u8BA1\u4E0D\u80FD\u8D85\u8FC7 8 MB");
633
+ break;
634
+ }
635
+ const data = await readFileBase64(file);
636
+ next.push({ filename: file.name, contentType: file.type || "application/octet-stream", data });
637
+ total += file.size;
638
+ }
639
+ setAttachments(next);
640
+ };
641
+ const saveDraft = async () => {
642
+ setBusy(true);
643
+ setError("");
644
+ try {
645
+ const { draft } = await api.saveDraft(account.id, {
646
+ ...draftId === void 0 ? {} : { id: draftId },
647
+ to,
648
+ cc,
649
+ bcc,
650
+ subject,
651
+ html: html(),
652
+ attachments,
653
+ ...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
654
+ ...initial.references === void 0 ? {} : { references: initial.references }
655
+ });
656
+ setDraftId(draft.id);
657
+ onSent("\u8349\u7A3F\u5DF2\u4FDD\u5B58");
658
+ } catch (caught) {
659
+ setError(errorText2(caught));
660
+ } finally {
661
+ setBusy(false);
662
+ }
663
+ };
664
+ const send = async () => {
665
+ setBusy(true);
666
+ setError("");
667
+ try {
668
+ await api.send(account.id, {
669
+ to,
670
+ cc,
671
+ bcc,
672
+ subject,
673
+ html: html(),
674
+ text: text(),
675
+ attachments,
676
+ ...initial.inReplyTo === void 0 ? {} : { inReplyTo: initial.inReplyTo },
677
+ ...initial.references === void 0 ? {} : { references: initial.references }
678
+ });
679
+ if (draftId !== void 0) {
680
+ try {
681
+ await api.deleteDraft(draftId, account.id);
682
+ } catch {
683
+ }
684
+ }
685
+ onSent("\u90AE\u4EF6\u5DF2\u53D1\u9001");
686
+ onClose();
687
+ } catch (caught) {
688
+ setError(errorText2(caught));
689
+ } finally {
690
+ setBusy(false);
691
+ setConfirming(false);
692
+ }
693
+ };
694
+ return (0, import_react4.createElement)(
695
+ Modal,
696
+ {
697
+ title: initial.mode === "reply" ? "\u56DE\u590D" : initial.mode === "replyAll" ? "\u5168\u90E8\u56DE\u590D" : initial.mode === "forward" ? "\u8F6C\u53D1" : "\u5199\u90AE\u4EF6",
698
+ onClose,
699
+ wide: true,
700
+ footer: (0, import_react4.createElement)(
701
+ "div",
702
+ { style: { display: "flex", gap: "8px", alignItems: "center", width: "100%" } },
703
+ (0, import_react4.createElement)("span", { className: "small muted" }, `\u53D1\u4EF6\u4EBA ${account.email}`),
704
+ (0, import_react4.createElement)("span", { className: "spacer" }),
705
+ (0, import_react4.createElement)("button", { className: "ghost", onClick: () => void saveDraft(), disabled: busy }, "\u5B58\u8349\u7A3F"),
706
+ confirming ? (0, import_react4.createElement)(
707
+ "span",
708
+ { style: { display: "inline-flex", gap: "6px", alignItems: "center" } },
709
+ (0, import_react4.createElement)("span", { className: "small" }, "\u786E\u5B9A\u53D1\u9001\uFF1F"),
710
+ (0, import_react4.createElement)(
711
+ "button",
712
+ { className: "primary", onClick: () => void send(), disabled: busy },
713
+ busy ? "\u53D1\u9001\u4E2D\u2026" : "\u786E\u5B9A"
714
+ ),
715
+ (0, import_react4.createElement)("button", { className: "ghost", onClick: () => setConfirming(false) }, "\u53D6\u6D88")
716
+ ) : (0, import_react4.createElement)(
717
+ "button",
718
+ {
719
+ className: "primary",
720
+ onClick: () => setConfirming(true),
721
+ disabled: busy || to.trim() === ""
722
+ },
723
+ "\u53D1\u9001"
724
+ )
725
+ )
726
+ },
727
+ error === "" ? null : (0, import_react4.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
728
+ (0, import_react4.createElement)(
729
+ "div",
730
+ { className: "mailharbor-grid" },
731
+ (0, import_react4.createElement)(
732
+ Field,
733
+ { label: "\u6536\u4EF6\u4EBA" },
734
+ (0, import_react4.createElement)("input", {
735
+ value: to,
736
+ onChange: (event) => setTo(event.target.value),
737
+ placeholder: "a@example.com, b@example.com"
738
+ })
739
+ ),
740
+ (0, import_react4.createElement)(
741
+ Field,
742
+ { label: "\u4E3B\u9898" },
743
+ (0, import_react4.createElement)("input", {
744
+ value: subject,
745
+ onChange: (event) => setSubject(event.target.value)
746
+ })
747
+ )
748
+ ),
749
+ showCc ? (0, import_react4.createElement)(
750
+ "div",
751
+ { className: "mailharbor-grid" },
752
+ (0, import_react4.createElement)(
753
+ Field,
754
+ { label: "\u6284\u9001" },
755
+ (0, import_react4.createElement)("input", {
756
+ value: cc,
757
+ onChange: (event) => setCc(event.target.value)
758
+ })
759
+ ),
760
+ (0, import_react4.createElement)(
761
+ Field,
762
+ { label: "\u5BC6\u9001" },
763
+ (0, import_react4.createElement)("input", {
764
+ value: bcc,
765
+ onChange: (event) => setBcc(event.target.value)
766
+ })
767
+ )
768
+ ) : (0, import_react4.createElement)(
769
+ "button",
770
+ { className: "subtle small", style: { alignSelf: "flex-start" }, onClick: () => setShowCc(true) },
771
+ "\u6DFB\u52A0\u6284\u9001 / \u5BC6\u9001"
772
+ ),
773
+ (0, import_react4.createElement)(
774
+ "div",
775
+ { className: "mailharbor-toolbar" },
776
+ (0, import_react4.createElement)("button", { onClick: () => exec("bold"), title: "\u52A0\u7C97" }, "B"),
777
+ (0, import_react4.createElement)("button", { onClick: () => exec("italic"), title: "\u659C\u4F53" }, "I"),
778
+ (0, import_react4.createElement)("button", { onClick: () => exec("underline"), title: "\u4E0B\u5212\u7EBF" }, "U"),
779
+ (0, import_react4.createElement)("button", { onClick: () => exec("insertUnorderedList"), title: "\u65E0\u5E8F\u5217\u8868" }, "\u2022 \u5217\u8868"),
780
+ (0, import_react4.createElement)("button", { onClick: () => exec("insertOrderedList"), title: "\u6709\u5E8F\u5217\u8868" }, "1. \u5217\u8868"),
781
+ (0, import_react4.createElement)("button", { onClick: () => exec("formatBlock", "blockquote"), title: "\u5F15\u7528" }, "\u5F15\u7528"),
782
+ (0, import_react4.createElement)(
783
+ "button",
784
+ {
785
+ onClick: () => {
786
+ const url = window.prompt("\u94FE\u63A5\u5730\u5740\uFF08https://\u2026\uFF09");
787
+ if (url !== null && /^https?:\/\//i.test(url)) exec("createLink", url);
788
+ },
789
+ title: "\u94FE\u63A5"
790
+ },
791
+ "\u94FE\u63A5"
792
+ ),
793
+ (0, import_react4.createElement)("button", { onClick: () => exec("removeFormat"), title: "\u6E05\u9664\u683C\u5F0F" }, "\u6E05\u9664"),
794
+ (0, import_react4.createElement)("span", { className: "spacer" }),
795
+ (0, import_react4.createElement)(
796
+ "label",
797
+ { className: "mailharbor-toggle small" },
798
+ "\u9644\u4EF6",
799
+ (0, import_react4.createElement)("input", {
800
+ type: "file",
801
+ multiple: true,
802
+ style: { display: "none" },
803
+ onChange: (event) => {
804
+ void addFiles(event.target.files);
805
+ event.target.value = "";
806
+ }
807
+ })
808
+ )
809
+ ),
810
+ attachments.length === 0 ? null : (0, import_react4.createElement)(
811
+ "div",
812
+ { className: "mailharbor-attachments", style: { padding: 0, border: 0 } },
813
+ attachments.map(
814
+ (file, index) => (0, import_react4.createElement)(
815
+ "span",
816
+ { className: "mailharbor-attachment", key: `${file.filename}:${String(index)}` },
817
+ (0, import_react4.createElement)("span", { className: "name" }, file.filename),
818
+ (0, import_react4.createElement)(
819
+ "button",
820
+ {
821
+ className: "subtle",
822
+ onClick: () => setAttachments(attachments.filter((_item, position) => position !== index))
823
+ },
824
+ "\u2715"
825
+ )
826
+ )
827
+ )
828
+ ),
829
+ (0, import_react4.createElement)("div", {
830
+ className: "mailharbor-editor",
831
+ ref: editor,
832
+ contentEditable: true,
833
+ suppressContentEditableWarning: true,
834
+ "data-placeholder": "\u5199\u70B9\u4EC0\u4E48\u2026",
835
+ spellCheck: true
836
+ })
837
+ );
838
+ }
839
+
840
+ // src/client/reader.tsx
841
+ var import_react5 = require("react");
842
+ var IFRAME_STYLE = `
843
+ html, body { margin: 0; padding: 0; }
844
+ body {
845
+ font: 15px/1.7 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
846
+ color: #171a21; background: #fff; padding: 18px 20px;
847
+ overflow-wrap: anywhere;
848
+ }
849
+ img { max-width: 100%; height: auto; }
850
+ table { max-width: 100%; border-collapse: collapse; }
851
+ td, th { padding: 2px 6px; }
852
+ a { color: #2f5bd3; }
853
+ blockquote { margin: 0 0 0 12px; padding-left: 12px; border-left: 2px solid #d7dbe3; color: #565c67; }
854
+ pre { white-space: pre-wrap; }
855
+ `;
856
+ function csp(allowRemoteImages) {
857
+ return [
858
+ "default-src 'none'",
859
+ "style-src 'unsafe-inline'",
860
+ "font-src data:",
861
+ `img-src data:${allowRemoteImages ? " https: http:" : ""}`,
862
+ "form-action 'none'",
863
+ "base-uri 'none'"
864
+ ].join("; ");
865
+ }
866
+ function srcDoc(detail, allowRemoteImages) {
867
+ const html = detail.html !== null && detail.html !== "" ? detail.html : `<pre>${escapeHtml(detail.text)}</pre>`;
868
+ return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="${csp(allowRemoteImages)}"><style>${IFRAME_STYLE}</style></head><body>${html}</body></html>`;
869
+ }
870
+ function escapeHtml(value) {
871
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
872
+ }
873
+ function hasRemoteImages(detail) {
874
+ return detail.html !== null && /<img[^>]+src=["']https?:/i.test(detail.html);
875
+ }
876
+ function Reader({
877
+ detail,
878
+ account,
879
+ source,
880
+ mailbox,
881
+ onChanged,
882
+ onReply,
883
+ onClose
884
+ }) {
885
+ const [plain, setPlain] = (0, import_react5.useState)(false);
886
+ const [loadImages, setLoadImages] = (0, import_react5.useState)(false);
887
+ const [busy, setBusy] = (0, import_react5.useState)(false);
888
+ const [error, setError] = (0, import_react5.useState)("");
889
+ const remoteImages = (0, import_react5.useMemo)(() => hasRemoteImages(detail), [detail]);
890
+ const html = detail.html !== null && detail.html !== "";
891
+ const toggleRead = async () => {
892
+ setBusy(true);
893
+ setError("");
894
+ try {
895
+ const result = await api.setRead({
896
+ id: detail.id,
897
+ accountId: account.id,
898
+ source,
899
+ mailbox,
900
+ read: detail.unread
901
+ });
902
+ onChanged({ unread: !result.read });
903
+ } catch (caught) {
904
+ setError(caught instanceof Error ? caught.message : String(caught));
905
+ } finally {
906
+ setBusy(false);
907
+ }
908
+ };
909
+ return (0, import_react5.createElement)(
910
+ "div",
911
+ { className: "mailharbor-reader" },
912
+ (0, import_react5.createElement)(
913
+ "div",
914
+ { className: "mailharbor-reader-head" },
915
+ (0, import_react5.createElement)(
916
+ "div",
917
+ { style: { display: "flex", alignItems: "center", gap: "8px" } },
918
+ (0, import_react5.createElement)("h2", { style: { flex: 1 } }, detail.subject),
919
+ onClose === void 0 ? null : (0, import_react5.createElement)("button", { className: "ghost", onClick: onClose }, "\u8FD4\u56DE\u5217\u8868")
920
+ ),
921
+ (0, import_react5.createElement)(
922
+ "div",
923
+ { className: "mailharbor-reader-meta" },
924
+ (0, import_react5.createElement)(
925
+ "span",
926
+ null,
927
+ `${detail.from.name !== "" ? detail.from.name : detail.from.address} <${detail.from.address}>`
928
+ ),
929
+ (0, import_react5.createElement)("span", null, longDate(detail.date)),
930
+ detail.backedUp === false ? (0, import_react5.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null
931
+ ),
932
+ (0, import_react5.createElement)(
933
+ "div",
934
+ { className: "mailharbor-reader-meta" },
935
+ (0, import_react5.createElement)(
936
+ "span",
937
+ null,
938
+ `\u6536\u4EF6\u4EBA: ${detail.to.map((item) => item.name !== "" ? `${item.name} <${item.address}>` : item.address).join(", ") || "\u2014"}`
939
+ ),
940
+ detail.cc.length === 0 ? null : (0, import_react5.createElement)("span", null, `\u6284\u9001: ${detail.cc.map((item) => item.address).join(", ")}`)
941
+ ),
942
+ (0, import_react5.createElement)(
943
+ "div",
944
+ { className: "mailharbor-reader-actions" },
945
+ (0, import_react5.createElement)(
946
+ "button",
947
+ {
948
+ className: "primary",
949
+ onClick: () => {
950
+ onReply("reply");
951
+ }
952
+ },
953
+ "\u56DE\u590D"
954
+ ),
955
+ (0, import_react5.createElement)(
956
+ "button",
957
+ {
958
+ className: "ghost",
959
+ onClick: () => {
960
+ onReply("replyAll");
961
+ }
962
+ },
963
+ "\u5168\u90E8\u56DE\u590D"
964
+ ),
965
+ (0, import_react5.createElement)(
966
+ "button",
967
+ {
968
+ className: "ghost",
969
+ onClick: () => {
970
+ onReply("forward");
971
+ }
972
+ },
973
+ "\u8F6C\u53D1"
974
+ ),
975
+ (0, import_react5.createElement)("span", { className: "spacer" }),
976
+ (0, import_react5.createElement)(
977
+ "button",
978
+ { className: "ghost", onClick: () => void toggleRead(), disabled: busy },
979
+ detail.unread ? "\u6807\u4E3A\u5DF2\u8BFB" : "\u6807\u4E3A\u672A\u8BFB"
980
+ ),
981
+ html ? (0, import_react5.createElement)(
982
+ "button",
983
+ { className: "ghost", onClick: () => setPlain(!plain) },
984
+ plain ? "\u663E\u793A HTML" : "\u663E\u793A\u7EAF\u6587\u672C"
985
+ ) : null,
986
+ remoteImages && !loadImages ? (0, import_react5.createElement)("button", { className: "ghost", onClick: () => setLoadImages(true) }, "\u663E\u793A\u8FDC\u7A0B\u56FE\u7247") : null
987
+ ),
988
+ error === "" ? null : (0, import_react5.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error)
989
+ ),
990
+ (0, import_react5.createElement)(
991
+ "div",
992
+ { className: "mailharbor-reader-body" },
993
+ html && !plain ? (0, import_react5.createElement)("iframe", {
994
+ title: "\u90AE\u4EF6\u6B63\u6587",
995
+ sandbox: "",
996
+ srcDoc: srcDoc(detail, loadImages),
997
+ referrerPolicy: "no-referrer"
998
+ }) : (0, import_react5.createElement)("pre", null, detail.text !== "" ? detail.text : "\uFF08\u65E0\u6B63\u6587\uFF09")
999
+ ),
1000
+ detail.attachments.length === 0 ? null : (0, import_react5.createElement)(
1001
+ "div",
1002
+ { className: "mailharbor-attachments" },
1003
+ detail.attachments.map(
1004
+ (file) => (0, import_react5.createElement)(
1005
+ "a",
1006
+ {
1007
+ className: "mailharbor-attachment",
1008
+ key: file.id,
1009
+ href: source === "server" ? api.attachmentUrl({
1010
+ accountId: account.id,
1011
+ source: "server",
1012
+ mailbox,
1013
+ uid: detail.uid ?? "",
1014
+ part: file.id
1015
+ }) : api.attachmentUrl({
1016
+ accountId: account.id,
1017
+ source: "local",
1018
+ emailId: detail.id,
1019
+ attachmentId: file.id
1020
+ }),
1021
+ download: file.filename
1022
+ },
1023
+ (0, import_react5.createElement)("span", { className: "name" }, file.filename),
1024
+ (0, import_react5.createElement)("span", { className: "muted small" }, formatBytes(file.size))
1025
+ )
1026
+ )
1027
+ )
1028
+ );
1029
+ }
1030
+
1031
+ // src/client/panel.tsx
1032
+ var EMPTY_PAGE = { items: [], total: 0, offset: 0, limit: 40 };
1033
+ function errorText3(error) {
1034
+ return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1035
+ }
1036
+ function quoteForReply(detail) {
1037
+ const body = detail.html ?? `<pre>${detail.text.replaceAll("&", "&amp;").replaceAll("<", "&lt;")}</pre>`;
1038
+ return `<p><br></p><p>------------------ \u539F\u59CB\u90AE\u4EF6 ------------------</p><blockquote style="margin:0 0 0 12px;padding-left:12px;border-left:2px solid #d7dbe3;color:#565c67">${body}</blockquote>`;
1039
+ }
1040
+ function MailPanel() {
1041
+ const [state, setState] = (0, import_react6.useState)(null);
1042
+ const [error, setError] = (0, import_react6.useState)("");
1043
+ const [source, setSource] = (0, import_react6.useState)("local");
1044
+ const [mailbox, setMailbox] = (0, import_react6.useState)("all");
1045
+ const [folders, setFolders] = (0, import_react6.useState)([]);
1046
+ const [query2, setQuery] = (0, import_react6.useState)("");
1047
+ const [search, setSearch] = (0, import_react6.useState)("");
1048
+ const [page, setPage] = (0, import_react6.useState)(EMPTY_PAGE);
1049
+ const [limit, setLimit] = (0, import_react6.useState)(40);
1050
+ const [loading, setLoading] = (0, import_react6.useState)(false);
1051
+ const [selected, setSelected] = (0, import_react6.useState)(null);
1052
+ const [compose, setCompose] = (0, import_react6.useState)(null);
1053
+ const [showAdd, setShowAdd] = (0, import_react6.useState)(false);
1054
+ const [showBackup, setShowBackup] = (0, import_react6.useState)(false);
1055
+ const [backup, setBackup] = (0, import_react6.useState)(null);
1056
+ const [toast, setToast] = (0, import_react6.useState)("");
1057
+ const [narrow, setNarrow] = (0, import_react6.useState)(() => window.matchMedia("(max-width: 900px)").matches);
1058
+ const [tick, setTick] = (0, import_react6.useState)(0);
1059
+ const account = state?.accounts.find((item) => item.id === state.activeAccountId) ?? null;
1060
+ const accountId = account?.id ?? null;
1061
+ const refresh = (0, import_react6.useCallback)(() => {
1062
+ setTick((value) => value + 1);
1063
+ }, []);
1064
+ const notice = (0, import_react6.useCallback)((message) => {
1065
+ setToast(message);
1066
+ window.setTimeout(() => {
1067
+ setToast("");
1068
+ }, 3200);
1069
+ }, []);
1070
+ (0, import_react6.useEffect)(() => {
1071
+ let cancelled = false;
1072
+ api.state().then((next) => {
1073
+ if (!cancelled) setState(next);
1074
+ }).catch((caught) => {
1075
+ if (!cancelled) setError(errorText3(caught));
1076
+ });
1077
+ return () => {
1078
+ cancelled = true;
1079
+ };
1080
+ }, [tick]);
1081
+ (0, import_react6.useEffect)(() => {
1082
+ if (accountId === null) return;
1083
+ let cancelled = false;
1084
+ api.folders({ accountId, source }).then((result) => {
1085
+ if (cancelled) return;
1086
+ const items = source === "local" ? [
1087
+ {
1088
+ name: "all",
1089
+ label: "\u5168\u90E8\u90AE\u4EF6",
1090
+ count: result.items.reduce((sum, item) => sum + item.count, 0)
1091
+ },
1092
+ ...result.items.map((item) => ({ ...item, label: item.label ?? item.name }))
1093
+ ] : result.items.map((item) => ({ ...item, label: item.label ?? item.name }));
1094
+ setFolders(items);
1095
+ }).catch(() => {
1096
+ if (!cancelled) setFolders([]);
1097
+ });
1098
+ return () => {
1099
+ cancelled = true;
1100
+ };
1101
+ }, [accountId, source, tick]);
1102
+ (0, import_react6.useEffect)(() => {
1103
+ setMailbox(source === "local" ? "all" : "INBOX");
1104
+ setSelected(null);
1105
+ setLimit(state?.settings.render.listLimit ?? 40);
1106
+ }, [source]);
1107
+ (0, import_react6.useEffect)(() => {
1108
+ if (accountId === null) return;
1109
+ let cancelled = false;
1110
+ setLoading(true);
1111
+ api.messages({ accountId, source, mailbox, query: search, limit }).then((result) => {
1112
+ if (!cancelled) setPage(result);
1113
+ }).catch((caught) => {
1114
+ if (!cancelled) setError(errorText3(caught));
1115
+ }).finally(() => {
1116
+ if (!cancelled) setLoading(false);
1117
+ });
1118
+ return () => {
1119
+ cancelled = true;
1120
+ };
1121
+ }, [accountId, source, mailbox, search, limit, tick]);
1122
+ (0, import_react6.useEffect)(() => {
1123
+ const media = window.matchMedia("(max-width: 900px)");
1124
+ const onChange = () => setNarrow(media.matches);
1125
+ media.addEventListener("change", onChange);
1126
+ return () => {
1127
+ media.removeEventListener("change", onChange);
1128
+ };
1129
+ }, []);
1130
+ (0, import_react6.useEffect)(() => {
1131
+ if (!showBackup || accountId === null) return;
1132
+ let cancelled = false;
1133
+ const poll = async () => {
1134
+ try {
1135
+ const status = await api.backupStatus(accountId);
1136
+ if (!cancelled) setBackup(status);
1137
+ } catch {
1138
+ }
1139
+ };
1140
+ void poll();
1141
+ const timer = window.setInterval(() => {
1142
+ void poll();
1143
+ }, 1e3);
1144
+ return () => {
1145
+ cancelled = true;
1146
+ window.clearInterval(timer);
1147
+ };
1148
+ }, [showBackup, accountId]);
1149
+ const openMessage = async (item) => {
1150
+ if (account === null) return;
1151
+ setError("");
1152
+ try {
1153
+ const detail = await api.message({ id: item.id, accountId: account.id, source, mailbox });
1154
+ setSelected(detail);
1155
+ if (detail.unread && state?.settings.read.markOnOpen === true) {
1156
+ try {
1157
+ await api.setRead({ id: detail.id, accountId: account.id, source, mailbox, read: true });
1158
+ setSelected({ ...detail, unread: false });
1159
+ setPage((current) => ({
1160
+ ...current,
1161
+ items: current.items.map((row) => row.id === item.id ? { ...row, unread: false } : row)
1162
+ }));
1163
+ } catch {
1164
+ }
1165
+ }
1166
+ } catch (caught) {
1167
+ setError(errorText3(caught));
1168
+ }
1169
+ };
1170
+ const startCompose = (mode) => {
1171
+ if (account === null) return;
1172
+ const detail = selected;
1173
+ if (mode === "new" || detail === null) {
1174
+ setCompose({ mode: "new", to: "", cc: "", bcc: "", subject: "", html: "" });
1175
+ return;
1176
+ }
1177
+ const subjectPrefix = mode === "forward" ? "Fwd: " : "Re: ";
1178
+ const subject = /^(re|fwd):/i.test(detail.subject) ? detail.subject : `${subjectPrefix}${detail.subject}`;
1179
+ if (mode === "forward") {
1180
+ setCompose({
1181
+ mode,
1182
+ to: "",
1183
+ cc: "",
1184
+ bcc: "",
1185
+ subject,
1186
+ html: quoteForReply(detail),
1187
+ references: detail.references.join(" ")
1188
+ });
1189
+ return;
1190
+ }
1191
+ const to = detail.from.address;
1192
+ const cc = mode === "replyAll" ? [...detail.to, ...detail.cc].map((item) => item.address).filter((address) => address !== account.email && address !== to).join(", ") : "";
1193
+ setCompose({
1194
+ mode,
1195
+ to,
1196
+ cc,
1197
+ bcc: "",
1198
+ subject,
1199
+ html: quoteForReply(detail),
1200
+ ...detail.messageId === "" ? {} : { inReplyTo: detail.messageId, references: [...detail.references, detail.messageId].join(" ") }
1201
+ });
1202
+ };
1203
+ const switchAccount = async (id) => {
1204
+ try {
1205
+ await api.activateAccount(id);
1206
+ setSelected(null);
1207
+ refresh();
1208
+ } catch (caught) {
1209
+ setError(errorText3(caught));
1210
+ }
1211
+ };
1212
+ const startBackup = async () => {
1213
+ if (accountId === null) return;
1214
+ try {
1215
+ const result = await api.startBackup(accountId);
1216
+ setBackup(result.status);
1217
+ setShowBackup(true);
1218
+ } catch (caught) {
1219
+ setError(errorText3(caught));
1220
+ }
1221
+ };
1222
+ const stopBackup = async () => {
1223
+ if (accountId === null) return;
1224
+ try {
1225
+ const result = await api.stopBackup(accountId);
1226
+ setBackup(result.status);
1227
+ } catch (caught) {
1228
+ setError(errorText3(caught));
1229
+ }
1230
+ };
1231
+ const folderOptions = (0, import_react6.useMemo)(
1232
+ () => folders.map((folder) => ({
1233
+ value: folder.name,
1234
+ label: `${folder.label ?? folder.name}${folder.count > 0 ? ` (${String(folder.count)})` : ""}`
1235
+ })),
1236
+ [folders]
1237
+ );
1238
+ const unreadCount = page.items.filter((item) => item.unread).length;
1239
+ if (state === null) {
1240
+ return (0, import_react6.createElement)("div", { className: "mailharbor mailharbor-empty" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u90AE\u7BB1\u2026" : error);
1241
+ }
1242
+ if (account === null) {
1243
+ return (0, import_react6.createElement)(
1244
+ "div",
1245
+ { className: "mailharbor" },
1246
+ (0, import_react6.createElement)(
1247
+ "div",
1248
+ { className: "mailharbor-settings" },
1249
+ (0, import_react6.createElement)("h2", null, "\u6B22\u8FCE\u4F7F\u7528 MailHarbor"),
1250
+ (0, import_react6.createElement)(
1251
+ "p",
1252
+ { className: "muted small" },
1253
+ "\u628A\u4EFB\u610F IMAP/SMTP \u90AE\u7BB1\u63A5\u8FDB DeepSeek Harness\uFF1A\u672C\u5730\u5907\u4EFD\u3001\u5B9E\u65F6\u8BFB\u4FE1\u3001\u5199\u90AE\u4EF6\u3001AI \u4E0E\u4EFB\u52A1\u3002"
1254
+ ),
1255
+ error === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { color: "var(--m-danger)" } }, error),
1256
+ (0, import_react6.createElement)(AddAccountForm, {
1257
+ onDone: (next) => {
1258
+ setState(next);
1259
+ setSource("local");
1260
+ }
1261
+ })
1262
+ )
1263
+ );
1264
+ }
1265
+ const needsLogin = source === "server" && state.loggedIn === false;
1266
+ return (0, import_react6.createElement)(
1267
+ "div",
1268
+ { className: narrow ? "mailharbor mailharbor-narrow" : "mailharbor" },
1269
+ (0, import_react6.createElement)(
1270
+ "div",
1271
+ { className: "mailharbor-topbar" },
1272
+ (0, import_react6.createElement)(Avatar, { email: account.email, size: 26 }),
1273
+ state.accounts.length > 1 ? (0, import_react6.createElement)(
1274
+ "select",
1275
+ {
1276
+ value: account.id,
1277
+ onChange: (event) => void switchAccount(event.target.value),
1278
+ "aria-label": "\u8D26\u53F7"
1279
+ },
1280
+ state.accounts.map((item) => (0, import_react6.createElement)("option", { key: item.id, value: item.id }, item.label))
1281
+ ) : (0, import_react6.createElement)("span", { className: "small" }, account.label),
1282
+ (0, import_react6.createElement)(
1283
+ "div",
1284
+ { className: "mailharbor-segment", role: "tablist" },
1285
+ (0, import_react6.createElement)(
1286
+ "button",
1287
+ { className: source === "local" ? "active" : "", onClick: () => setSource("local"), role: "tab" },
1288
+ "\u672C\u5730\u5907\u4EFD"
1289
+ ),
1290
+ (0, import_react6.createElement)(
1291
+ "button",
1292
+ { className: source === "server" ? "active" : "", onClick: () => setSource("server"), role: "tab" },
1293
+ "\u670D\u52A1\u5668\u5B9E\u65F6"
1294
+ )
1295
+ ),
1296
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1297
+ state.loggedIn === false ? (0, import_react6.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
1298
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setShowAdd(true), title: "\u6DFB\u52A0\u8D26\u53F7" }, "+ \u8D26\u53F7"),
1299
+ (0, import_react6.createElement)("button", { className: "primary", onClick: () => startCompose("new") }, "\u5199\u90AE\u4EF6"),
1300
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => void startBackup() }, "\u5907\u4EFD"),
1301
+ (0, import_react6.createElement)(
1302
+ "button",
1303
+ {
1304
+ className: "ghost",
1305
+ onClick: () => {
1306
+ refresh();
1307
+ }
1308
+ },
1309
+ "\u5237\u65B0"
1310
+ )
1311
+ ),
1312
+ toast === "" ? null : (0, import_react6.createElement)("div", { className: "small", style: { padding: "6px 12px", color: "var(--m-ok)" } }, toast),
1313
+ error === "" ? null : (0, import_react6.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
1314
+ needsLogin ? (0, import_react6.createElement)("div", { className: "mailharbor-settings" }, (0, import_react6.createElement)(LoginForm, { account, onDone: refresh })) : (0, import_react6.createElement)(
1315
+ "div",
1316
+ { className: "mailharbor-body", "data-mode": narrow && selected !== null ? "reader" : "list" },
1317
+ (0, import_react6.createElement)(
1318
+ "div",
1319
+ { className: "mailharbor-list" },
1320
+ (0, import_react6.createElement)(
1321
+ "div",
1322
+ { className: "mailharbor-filters" },
1323
+ (0, import_react6.createElement)(
1324
+ "select",
1325
+ {
1326
+ value: mailbox,
1327
+ onChange: (event) => {
1328
+ setMailbox(event.target.value);
1329
+ setSelected(null);
1330
+ },
1331
+ "aria-label": "\u90AE\u7BB1\u6587\u4EF6\u5939"
1332
+ },
1333
+ folderOptions.map(
1334
+ (option) => (0, import_react6.createElement)("option", { key: option.value, value: option.value }, option.label)
1335
+ )
1336
+ ),
1337
+ (0, import_react6.createElement)("input", {
1338
+ type: "search",
1339
+ placeholder: "\u641C\u7D22\u90AE\u4EF6",
1340
+ value: query2,
1341
+ onChange: (event) => setQuery(event.target.value),
1342
+ onKeyDown: (event) => {
1343
+ if (event.key === "Enter") setSearch(query2);
1344
+ if (event.key === "Escape") {
1345
+ setQuery("");
1346
+ setSearch("");
1347
+ }
1348
+ }
1349
+ }),
1350
+ query2 !== search || search !== "" ? (0, import_react6.createElement)(
1351
+ "button",
1352
+ {
1353
+ className: "subtle small",
1354
+ onClick: () => {
1355
+ setQuery("");
1356
+ setSearch("");
1357
+ }
1358
+ },
1359
+ "\u6E05\u9664"
1360
+ ) : null
1361
+ ),
1362
+ (0, import_react6.createElement)(
1363
+ "div",
1364
+ { className: "mailharbor-rows" },
1365
+ loading && page.items.length === 0 ? (0, import_react6.createElement)("div", { className: "mailharbor-empty" }, "\u6B63\u5728\u8BFB\u53D6\u2026") : page.items.length === 0 ? (0, import_react6.createElement)(
1366
+ "div",
1367
+ { className: "mailharbor-empty" },
1368
+ source === "local" ? "\u672C\u5730\u5907\u4EFD\u91CC\u8FD8\u6CA1\u6709\u90AE\u4EF6\u3002\u70B9\u9876\u90E8\u300C\u5907\u4EFD\u300D\u628A\u670D\u52A1\u5668\u90AE\u4EF6\u540C\u6B65\u4E0B\u6765\u3002" : "\u670D\u52A1\u5668\u4E0A\u6CA1\u6709\u90AE\u4EF6\u3002"
1369
+ ) : page.items.map(
1370
+ (item) => (0, import_react6.createElement)(
1371
+ "button",
1372
+ {
1373
+ key: item.id,
1374
+ className: `mailharbor-row${item.unread ? " unread" : ""}${selected?.id === item.id ? " selected" : ""}`,
1375
+ onClick: () => void openMessage(item)
1376
+ },
1377
+ (0, import_react6.createElement)(
1378
+ "span",
1379
+ { className: "line-1" },
1380
+ (0, import_react6.createElement)(
1381
+ "span",
1382
+ { className: "from" },
1383
+ item.from.name !== "" ? item.from.name : item.from.address
1384
+ ),
1385
+ item.backedUp === false ? (0, import_react6.createElement)("span", { className: "badge warn" }, "\u672A\u5907\u4EFD") : null,
1386
+ (0, import_react6.createElement)("span", { className: "date" }, shortDate(item.date))
1387
+ ),
1388
+ (0, import_react6.createElement)("span", { className: "line-2" }, (0, import_react6.createElement)("span", { className: "subject" }, item.subject)),
1389
+ item.snippet === "" ? null : (0, import_react6.createElement)("span", { className: "snippet" }, item.snippet)
1390
+ )
1391
+ ),
1392
+ page.items.length > 0 ? (0, import_react6.createElement)(
1393
+ "div",
1394
+ { style: { padding: "10px 12px", display: "flex", gap: "8px", alignItems: "center" } },
1395
+ (0, import_react6.createElement)(
1396
+ "span",
1397
+ { className: "small muted" },
1398
+ `${String(page.items.length)} / ${String(page.total)} \u5C01${unreadCount > 0 ? ` \xB7 ${String(unreadCount)} \u672A\u8BFB` : ""}`
1399
+ ),
1400
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1401
+ page.total > page.items.length ? (0, import_react6.createElement)(
1402
+ "button",
1403
+ { className: "subtle small", onClick: () => setLimit((current) => current + 40) },
1404
+ "\u52A0\u8F7D\u66F4\u591A"
1405
+ ) : null
1406
+ ) : null
1407
+ )
1408
+ ),
1409
+ (0, import_react6.createElement)(
1410
+ "div",
1411
+ { className: "mailharbor-reader" },
1412
+ selected === null ? (0, import_react6.createElement)(
1413
+ "div",
1414
+ { className: "mailharbor-empty" },
1415
+ (0, import_react6.createElement)("p", null, "\u4ECE\u5DE6\u4FA7\u9009\u62E9\u4E00\u5C01\u90AE\u4EF6"),
1416
+ (0, import_react6.createElement)(
1417
+ "p",
1418
+ { className: "small" },
1419
+ state.loggedIn ? "\u5DF2\u767B\u5F55\uFF0C\u53EF\u5207\u6362\u300C\u670D\u52A1\u5668\u5B9E\u65F6\u300D\u67E5\u770B\u6700\u65B0\u90AE\u4EF6\u3002" : "\u6D4F\u89C8\u672C\u5730\u5907\u4EFD\u4E0D\u9700\u8981\u767B\u5F55\u3002"
1420
+ )
1421
+ ) : (0, import_react6.createElement)(Reader, {
1422
+ detail: selected,
1423
+ account,
1424
+ source,
1425
+ mailbox,
1426
+ onChanged: (patch) => setSelected({ ...selected, ...patch }),
1427
+ onReply: (mode) => startCompose(mode),
1428
+ onClose: narrow ? () => setSelected(null) : void 0
1429
+ })
1430
+ )
1431
+ ),
1432
+ showBackup && backup !== null ? (0, import_react6.createElement)(
1433
+ Modal,
1434
+ {
1435
+ title: "\u5907\u4EFD",
1436
+ onClose: () => setShowBackup(false),
1437
+ footer: (0, import_react6.createElement)(
1438
+ "div",
1439
+ { style: { display: "flex", gap: "8px", width: "100%", alignItems: "center" } },
1440
+ (0, import_react6.createElement)("span", { className: "small muted" }, backup.running ? "\u5907\u4EFD\u8FDB\u884C\u4E2D\u2026" : backup.message),
1441
+ (0, import_react6.createElement)("span", { className: "spacer" }),
1442
+ backup.running ? (0, import_react6.createElement)("button", { className: "danger ghost", onClick: () => void stopBackup() }, "\u505C\u6B62\u5907\u4EFD") : (0, import_react6.createElement)("button", { className: "primary", onClick: () => void startBackup() }, "\u5F00\u59CB\u5907\u4EFD"),
1443
+ (0, import_react6.createElement)("button", { className: "ghost", onClick: () => setShowBackup(false) }, "\u5173\u95ED")
1444
+ )
1445
+ },
1446
+ (0, import_react6.createElement)(
1447
+ "div",
1448
+ { className: "mailharbor-kv" },
1449
+ (0, import_react6.createElement)("dt", null, "\u5F00\u59CB\u65F6\u95F4"),
1450
+ (0, import_react6.createElement)("dd", null, backup.startedAt ?? "\u2014"),
1451
+ (0, import_react6.createElement)("dt", null, "\u4E0A\u6B21\u6210\u529F"),
1452
+ (0, import_react6.createElement)("dd", null, backup.lastSuccessAt ?? "\u2014"),
1453
+ (0, import_react6.createElement)("dt", null, "\u65B0\u589E"),
1454
+ (0, import_react6.createElement)("dd", null, String(backup.added)),
1455
+ (0, import_react6.createElement)("dt", null, "\u603B\u6570"),
1456
+ (0, import_react6.createElement)("dd", null, String(backup.total))
1457
+ ),
1458
+ (0, import_react6.createElement)(
1459
+ "div",
1460
+ { className: "mailharbor-log" },
1461
+ backup.output.length === 0 ? "\uFF08\u7B49\u5F85\u8F93\u51FA\u2026\uFF09" : backup.output.join("\n")
1462
+ )
1463
+ ) : null,
1464
+ compose !== null ? (0, import_react6.createElement)(Composer, {
1465
+ account,
1466
+ initial: compose,
1467
+ onClose: () => setCompose(null),
1468
+ onSent: (message) => notice(message)
1469
+ }) : null,
1470
+ showAdd ? (0, import_react6.createElement)(AddAccountModal, {
1471
+ onClose: () => setShowAdd(false),
1472
+ onDone: (next) => {
1473
+ setState(next);
1474
+ refresh();
1475
+ }
1476
+ }) : null
1477
+ );
1478
+ }
1479
+
1480
+ // src/client/settings.tsx
1481
+ var import_react7 = require("react");
1482
+ function errorText4(error) {
1483
+ return error instanceof ApiError || error instanceof Error ? error.message : String(error);
1484
+ }
1485
+ function MailSettings() {
1486
+ const [state, setState] = (0, import_react7.useState)(null);
1487
+ const [settings, setSettings] = (0, import_react7.useState)(null);
1488
+ const [error, setError] = (0, import_react7.useState)("");
1489
+ const [toast, setToast] = (0, import_react7.useState)("");
1490
+ const [signature, setSignature] = (0, import_react7.useState)("");
1491
+ const [markOnOpen, setMarkOnOpen] = (0, import_react7.useState)(true);
1492
+ const [pollSeconds, setPollSeconds] = (0, import_react7.useState)(60);
1493
+ const [listLimit, setListLimit] = (0, import_react7.useState)(40);
1494
+ const [aiEnabled, setAiEnabled] = (0, import_react7.useState)(true);
1495
+ const [aiProvider, setAiProvider] = (0, import_react7.useState)("deepseek-account");
1496
+ const [aiModel, setAiModel] = (0, import_react7.useState)("deepseek-flash");
1497
+ const [editing, setEditing] = (0, import_react7.useState)(null);
1498
+ const [checkResult, setCheckResult] = (0, import_react7.useState)("");
1499
+ const [adding, setAdding] = (0, import_react7.useState)(false);
1500
+ const [tick, setTick] = (0, import_react7.useState)(0);
1501
+ (0, import_react7.useEffect)(() => {
1502
+ let cancelled = false;
1503
+ Promise.all([api.state(), api.settings()]).then(([appState, view]) => {
1504
+ if (cancelled) return;
1505
+ setState(appState);
1506
+ setSettings(view);
1507
+ setSignature(appState.settings.signature);
1508
+ setMarkOnOpen(view.read.markOnOpen);
1509
+ setPollSeconds(view.read.pollSeconds);
1510
+ setListLimit(view.render.listLimit);
1511
+ setAiEnabled(view.ai.enabled);
1512
+ setAiProvider(view.ai.provider);
1513
+ setAiModel(view.ai.model);
1514
+ }).catch((caught) => {
1515
+ if (!cancelled) setError(errorText4(caught));
1516
+ });
1517
+ return () => {
1518
+ cancelled = true;
1519
+ };
1520
+ }, [tick]);
1521
+ const reload = () => setTick((value) => value + 1);
1522
+ const notify = (message) => {
1523
+ setToast(message);
1524
+ window.setTimeout(() => {
1525
+ setToast("");
1526
+ }, 3e3);
1527
+ };
1528
+ const saveGeneral = async (patch) => {
1529
+ try {
1530
+ const result = await api.saveSettings(patch);
1531
+ setSettings(result.settings);
1532
+ notify("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58");
1533
+ } catch (caught) {
1534
+ setError(errorText4(caught));
1535
+ }
1536
+ };
1537
+ const account = settings?.accounts.find((item) => item.id === settings.activeAccountId) ?? null;
1538
+ if (state === null || settings === null) {
1539
+ return (0, import_react7.createElement)("div", { className: "mailharbor mailharbor-settings" }, error === "" ? "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" : error);
1540
+ }
1541
+ return (0, import_react7.createElement)(
1542
+ "div",
1543
+ { className: "mailharbor" },
1544
+ (0, import_react7.createElement)(
1545
+ "div",
1546
+ { className: "mailharbor-settings" },
1547
+ (0, import_react7.createElement)("h2", null, "MailHarbor"),
1548
+ (0, import_react7.createElement)("p", { className: "small muted" }, "\u591A\u8D26\u53F7 IMAP/SMTP \u90AE\u7BB1\u3001\u672C\u5730\u5907\u4EFD\u3001AI \u4E0E\u4EFB\u52A1\u3002\u8BBE\u7F6E\u4F1A\u7ACB\u5373\u751F\u6548\u3002"),
1549
+ toast === "" ? null : (0, import_react7.createElement)("div", { className: "small", style: { color: "var(--m-ok)" } }, toast),
1550
+ error === "" ? null : (0, import_react7.createElement)(ErrorBar, { message: error, onDismiss: () => setError("") }),
1551
+ (0, import_react7.createElement)(
1552
+ "div",
1553
+ { className: "mailharbor-card" },
1554
+ (0, import_react7.createElement)("h3", null, "\u8D26\u53F7"),
1555
+ settings.accounts.length === 0 ? (0, import_react7.createElement)(AddAccountForm, { onDone: () => reload() }) : settings.accounts.map(
1556
+ (item) => (0, import_react7.createElement)(AccountRow, {
1557
+ key: item.id,
1558
+ account: item,
1559
+ active: item.id === settings.activeAccountId,
1560
+ loggedIn: item.id === settings.activeAccountId ? state.loggedIn : void 0,
1561
+ editing: editing === item.id,
1562
+ checkResult: editing === item.id ? checkResult : "",
1563
+ onToggleEdit: () => {
1564
+ setEditing(editing === item.id ? null : item.id);
1565
+ setCheckResult("");
1566
+ },
1567
+ onActivate: async () => {
1568
+ await api.activateAccount(item.id);
1569
+ reload();
1570
+ },
1571
+ onLogin: async (password, remember) => {
1572
+ await api.login(item.id, password, remember);
1573
+ reload();
1574
+ },
1575
+ onLogout: async () => {
1576
+ await api.logout(item.id);
1577
+ reload();
1578
+ },
1579
+ onCheck: async () => {
1580
+ try {
1581
+ const result = await api.check(item.id);
1582
+ setCheckResult(
1583
+ `IMAP \u6B63\u5E38\uFF08${String(result.imap.mailboxes.length)} \u4E2A\u6587\u4EF6\u5939\uFF09\xB7 SMTP ${result.smtp.ok ? "\u6B63\u5E38" : `\u5931\u8D25: ${result.smtp.error ?? ""}`}`
1584
+ );
1585
+ } catch (caught) {
1586
+ setCheckResult(`\u5931\u8D25: ${errorText4(caught)}`);
1587
+ }
1588
+ },
1589
+ onSave: async (patch) => {
1590
+ await api.updateAccount(item.id, patch);
1591
+ reload();
1592
+ },
1593
+ onRemove: async () => {
1594
+ if (!window.confirm(`\u79FB\u9664\u8D26\u53F7 ${item.email}\uFF1F\u672C\u5730\u6570\u636E\u76EE\u5F55\u4F1A\u4FDD\u7559\u3002`)) return;
1595
+ await api.deleteAccount(item.id);
1596
+ reload();
1597
+ }
1598
+ })
1599
+ ),
1600
+ adding ? (0, import_react7.createElement)(AddAccountForm, {
1601
+ onDone: () => {
1602
+ setAdding(false);
1603
+ reload();
1604
+ },
1605
+ onCancel: () => setAdding(false)
1606
+ }) : (0, import_react7.createElement)("button", { className: "ghost", onClick: () => setAdding(true) }, "+ \u6DFB\u52A0\u8D26\u53F7")
1607
+ ),
1608
+ (0, import_react7.createElement)(
1609
+ "div",
1610
+ { className: "mailharbor-card" },
1611
+ (0, import_react7.createElement)("h3", null, "\u5199\u4FE1\u4E0E\u9605\u8BFB"),
1612
+ (0, import_react7.createElement)(
1613
+ Field,
1614
+ { label: "\u7B7E\u540D\uFF08\u7EAF\u6587\u672C\uFF0C\u53D1\u9001\u65F6\u8FFD\u52A0\u5230\u6B63\u6587\u672B\u5C3E\uFF09" },
1615
+ (0, import_react7.createElement)("textarea", {
1616
+ rows: 4,
1617
+ value: signature,
1618
+ onChange: (event) => setSignature(event.target.value)
1619
+ })
1620
+ ),
1621
+ (0, import_react7.createElement)(
1622
+ "div",
1623
+ { style: { display: "flex", gap: "10px", alignItems: "center", flexWrap: "wrap" } },
1624
+ (0, import_react7.createElement)("button", { className: "ghost", onClick: () => void saveGeneral({ signature }) }, "\u4FDD\u5B58\u7B7E\u540D"),
1625
+ (0, import_react7.createElement)(Toggle, {
1626
+ checked: markOnOpen,
1627
+ onChange: (value) => {
1628
+ setMarkOnOpen(value);
1629
+ void saveGeneral({ read: { markOnOpen: value } });
1630
+ },
1631
+ label: "\u6253\u5F00\u90AE\u4EF6\u65F6\u6807\u4E3A\u5DF2\u8BFB"
1632
+ })
1633
+ ),
1634
+ (0, import_react7.createElement)(
1635
+ "div",
1636
+ { className: "mailharbor-grid" },
1637
+ (0, import_react7.createElement)(
1638
+ Field,
1639
+ { label: "\u5217\u8868\u6BCF\u9875\u6570\u91CF" },
1640
+ (0, import_react7.createElement)("input", {
1641
+ type: "number",
1642
+ min: 10,
1643
+ max: 200,
1644
+ value: listLimit,
1645
+ onChange: (event) => setListLimit(Number(event.target.value)),
1646
+ onBlur: () => void saveGeneral({ render: { listLimit } })
1647
+ })
1648
+ ),
1649
+ (0, import_react7.createElement)(
1650
+ Field,
1651
+ { label: "\u672A\u8BFB\u89D2\u6807\u8F6E\u8BE2\uFF08\u79D2\uFF09" },
1652
+ (0, import_react7.createElement)("input", {
1653
+ type: "number",
1654
+ min: 15,
1655
+ max: 3600,
1656
+ value: pollSeconds,
1657
+ onChange: (event) => setPollSeconds(Number(event.target.value)),
1658
+ onBlur: () => void saveGeneral({ read: { pollSeconds } })
1659
+ })
1660
+ )
1661
+ )
1662
+ ),
1663
+ (0, import_react7.createElement)(
1664
+ "div",
1665
+ { className: "mailharbor-card" },
1666
+ (0, import_react7.createElement)("h3", null, "AI"),
1667
+ (0, import_react7.createElement)(Toggle, {
1668
+ checked: aiEnabled,
1669
+ onChange: (value) => {
1670
+ setAiEnabled(value);
1671
+ void saveGeneral({ ai: { enabled: value } });
1672
+ },
1673
+ label: "\u542F\u7528 AI\uFF08\u6458\u8981\u3001\u8D77\u8349\u3001\u6DA6\u8272\u3001\u81EA\u7136\u8BED\u8A00\u641C\u7D22\uFF09"
1674
+ }),
1675
+ (0, import_react7.createElement)(
1676
+ "div",
1677
+ { className: "mailharbor-grid" },
1678
+ (0, import_react7.createElement)(
1679
+ Field,
1680
+ { label: "Provider" },
1681
+ (0, import_react7.createElement)("input", {
1682
+ value: aiProvider,
1683
+ onChange: (event) => setAiProvider(event.target.value),
1684
+ onBlur: () => void saveGeneral({ ai: { provider: aiProvider } })
1685
+ })
1686
+ ),
1687
+ (0, import_react7.createElement)(
1688
+ Field,
1689
+ { label: "\u6A21\u578B" },
1690
+ (0, import_react7.createElement)("input", {
1691
+ value: aiModel,
1692
+ onChange: (event) => setAiModel(event.target.value),
1693
+ onBlur: () => void saveGeneral({ ai: { model: aiModel } })
1694
+ })
1695
+ )
1696
+ ),
1697
+ (0, import_react7.createElement)(
1698
+ "p",
1699
+ { className: "small muted" },
1700
+ "AI \u8C03\u7528\u8D70 DSH \u81EA\u5DF1\u7684\u6A21\u578B\u670D\u52A1\uFF0C\u4E0D\u989D\u5916\u4FDD\u5B58\u5BC6\u94A5\u3002AI \u9762\u677F\u529F\u80FD\u5C06\u5728 v0.2 \u63D0\u4F9B\u3002"
1701
+ )
1702
+ ),
1703
+ (0, import_react7.createElement)(
1704
+ "div",
1705
+ { className: "mailharbor-card" },
1706
+ (0, import_react7.createElement)("h3", null, "\u6570\u636E"),
1707
+ (0, import_react7.createElement)(
1708
+ "dl",
1709
+ { className: "mailharbor-kv" },
1710
+ (0, import_react7.createElement)("dt", null, "\u6570\u636E\u76EE\u5F55"),
1711
+ (0, import_react7.createElement)("dd", null, settings.dataDir),
1712
+ (0, import_react7.createElement)("dt", null, "\u914D\u7F6E\u6587\u4EF6"),
1713
+ (0, import_react7.createElement)("dd", null, settings.configFile),
1714
+ (0, import_react7.createElement)("dt", null, "\u5F53\u524D\u8D26\u53F7\u6570\u636E\u5E93"),
1715
+ (0, import_react7.createElement)("dd", null, account === null ? "\u2014" : `${settings.dataDir}/accounts/${account.id}/mail.db`),
1716
+ (0, import_react7.createElement)("dt", null, "\u63D2\u4EF6\u7248\u672C"),
1717
+ (0, import_react7.createElement)("dd", null, state.version)
1718
+ ),
1719
+ (0, import_react7.createElement)(
1720
+ "div",
1721
+ { style: { display: "flex", gap: "8px", flexWrap: "wrap" } },
1722
+ account === null ? null : (0, import_react7.createElement)(
1723
+ "button",
1724
+ {
1725
+ className: "ghost",
1726
+ onClick: async () => {
1727
+ try {
1728
+ const status = await api.startBackup(account.id);
1729
+ notify(status.status.message === "" ? "\u5907\u4EFD\u5DF2\u5F00\u59CB" : status.status.message);
1730
+ reload();
1731
+ } catch (caught) {
1732
+ setError(errorText4(caught));
1733
+ }
1734
+ }
1735
+ },
1736
+ "\u7ACB\u5373\u5907\u4EFD\u5F53\u524D\u8D26\u53F7"
1737
+ )
1738
+ )
1739
+ )
1740
+ )
1741
+ );
1742
+ }
1743
+ function AccountRow({
1744
+ account,
1745
+ active,
1746
+ loggedIn,
1747
+ editing,
1748
+ checkResult,
1749
+ onToggleEdit,
1750
+ onActivate,
1751
+ onLogin,
1752
+ onLogout,
1753
+ onCheck,
1754
+ onSave,
1755
+ onRemove
1756
+ }) {
1757
+ const [password, setPassword] = (0, import_react7.useState)("");
1758
+ const [remember, setRemember] = (0, import_react7.useState)(true);
1759
+ const [label, setLabel] = (0, import_react7.useState)(account.label);
1760
+ const [username, setUsername] = (0, import_react7.useState)(account.username);
1761
+ const [imapHost, setImapHost] = (0, import_react7.useState)(account.imap.host);
1762
+ const [imapPort, setImapPort] = (0, import_react7.useState)(String(account.imap.port));
1763
+ const [imapSecure, setImapSecure] = (0, import_react7.useState)(account.imap.secure);
1764
+ const [smtpHost, setSmtpHost] = (0, import_react7.useState)(account.smtp.host);
1765
+ const [smtpPort, setSmtpPort] = (0, import_react7.useState)(String(account.smtp.port));
1766
+ const [smtpSecure, setSmtpSecure] = (0, import_react7.useState)(account.smtp.secure);
1767
+ const [fromName, setFromName] = (0, import_react7.useState)(account.smtp.fromName);
1768
+ const [busy, setBusy] = (0, import_react7.useState)(false);
1769
+ (0, import_react7.useEffect)(() => {
1770
+ setLabel(account.label);
1771
+ setUsername(account.username);
1772
+ setImapHost(account.imap.host);
1773
+ setImapPort(String(account.imap.port));
1774
+ setImapSecure(account.imap.secure);
1775
+ setSmtpHost(account.smtp.host);
1776
+ setSmtpPort(String(account.smtp.port));
1777
+ setSmtpSecure(account.smtp.secure);
1778
+ setFromName(account.smtp.fromName);
1779
+ }, [account]);
1780
+ const wrap = async (operation) => {
1781
+ setBusy(true);
1782
+ try {
1783
+ await operation();
1784
+ } finally {
1785
+ setBusy(false);
1786
+ }
1787
+ };
1788
+ return (0, import_react7.createElement)(
1789
+ "div",
1790
+ { className: "mailharbor-card", style: { background: "var(--m-bg-1)" } },
1791
+ (0, import_react7.createElement)(
1792
+ "div",
1793
+ { className: "mailharbor-account-row" },
1794
+ (0, import_react7.createElement)(Avatar, { email: account.email, size: 30 }),
1795
+ (0, import_react7.createElement)(
1796
+ "div",
1797
+ { style: { display: "flex", flexDirection: "column", minWidth: 0 } },
1798
+ (0, import_react7.createElement)("strong", null, account.label),
1799
+ (0, import_react7.createElement)("span", { className: "small muted" }, account.email)
1800
+ ),
1801
+ active ? (0, import_react7.createElement)("span", { className: "badge ok" }, "\u5F53\u524D\u8D26\u53F7") : null,
1802
+ loggedIn === false ? (0, import_react7.createElement)("span", { className: "badge warn" }, "\u672A\u767B\u5F55") : null,
1803
+ (0, import_react7.createElement)("span", { className: "spacer" }),
1804
+ active ? null : (0, import_react7.createElement)("button", { className: "ghost small", onClick: () => void wrap(onActivate) }, "\u8BBE\u4E3A\u5F53\u524D"),
1805
+ (0, import_react7.createElement)("button", { className: "ghost small", onClick: () => void wrap(onCheck) }, "\u68C0\u67E5\u8FDE\u63A5"),
1806
+ (0, import_react7.createElement)("button", { className: "ghost small", onClick: onToggleEdit }, editing ? "\u6536\u8D77" : "\u7F16\u8F91")
1807
+ ),
1808
+ editing ? (0, import_react7.createElement)(
1809
+ "div",
1810
+ { style: { display: "flex", flexDirection: "column", gap: "10px" } },
1811
+ (0, import_react7.createElement)(
1812
+ "div",
1813
+ { className: "mailharbor-grid" },
1814
+ (0, import_react7.createElement)(
1815
+ Field,
1816
+ { label: "\u663E\u793A\u540D\u79F0" },
1817
+ (0, import_react7.createElement)("input", {
1818
+ value: label,
1819
+ onChange: (event) => setLabel(event.target.value)
1820
+ })
1821
+ ),
1822
+ (0, import_react7.createElement)(
1823
+ Field,
1824
+ { label: "\u767B\u5F55\u7528\u6237\u540D" },
1825
+ (0, import_react7.createElement)("input", {
1826
+ value: username,
1827
+ onChange: (event) => setUsername(event.target.value)
1828
+ })
1829
+ )
1830
+ ),
1831
+ (0, import_react7.createElement)(
1832
+ "div",
1833
+ { className: "mailharbor-grid three" },
1834
+ (0, import_react7.createElement)(
1835
+ Field,
1836
+ { label: "IMAP \u4E3B\u673A" },
1837
+ (0, import_react7.createElement)("input", {
1838
+ value: imapHost,
1839
+ onChange: (event) => setImapHost(event.target.value)
1840
+ })
1841
+ ),
1842
+ (0, import_react7.createElement)(
1843
+ Field,
1844
+ { label: "\u7AEF\u53E3" },
1845
+ (0, import_react7.createElement)("input", {
1846
+ value: imapPort,
1847
+ onChange: (event) => setImapPort(event.target.value)
1848
+ })
1849
+ ),
1850
+ (0, import_react7.createElement)(Toggle, { checked: imapSecure, onChange: setImapSecure, label: "TLS" })
1851
+ ),
1852
+ (0, import_react7.createElement)(
1853
+ "div",
1854
+ { className: "mailharbor-grid three" },
1855
+ (0, import_react7.createElement)(
1856
+ Field,
1857
+ { label: "SMTP \u4E3B\u673A" },
1858
+ (0, import_react7.createElement)("input", {
1859
+ value: smtpHost,
1860
+ onChange: (event) => setSmtpHost(event.target.value)
1861
+ })
1862
+ ),
1863
+ (0, import_react7.createElement)(
1864
+ Field,
1865
+ { label: "\u7AEF\u53E3" },
1866
+ (0, import_react7.createElement)("input", {
1867
+ value: smtpPort,
1868
+ onChange: (event) => setSmtpPort(event.target.value)
1869
+ })
1870
+ ),
1871
+ (0, import_react7.createElement)(Toggle, { checked: smtpSecure, onChange: setSmtpSecure, label: "TLS" })
1872
+ ),
1873
+ (0, import_react7.createElement)(
1874
+ Field,
1875
+ { label: "\u53D1\u4EF6\u4EBA\u540D\u79F0" },
1876
+ (0, import_react7.createElement)("input", {
1877
+ value: fromName,
1878
+ onChange: (event) => setFromName(event.target.value)
1879
+ })
1880
+ ),
1881
+ (0, import_react7.createElement)(
1882
+ "div",
1883
+ { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
1884
+ (0, import_react7.createElement)(
1885
+ "button",
1886
+ {
1887
+ className: "primary small",
1888
+ disabled: busy,
1889
+ onClick: () => void wrap(async () => {
1890
+ await onSave({
1891
+ label,
1892
+ username,
1893
+ imap: { host: imapHost, port: Number(imapPort) || 993, secure: imapSecure },
1894
+ smtp: { host: smtpHost, port: Number(smtpPort) || 465, secure: smtpSecure, fromName }
1895
+ });
1896
+ })
1897
+ },
1898
+ "\u4FDD\u5B58\u8D26\u53F7"
1899
+ ),
1900
+ (0, import_react7.createElement)(
1901
+ "button",
1902
+ {
1903
+ className: "ghost small danger",
1904
+ onClick: () => void wrap(onRemove)
1905
+ },
1906
+ "\u79FB\u9664\u8D26\u53F7"
1907
+ )
1908
+ ),
1909
+ (0, import_react7.createElement)("hr", { style: { border: 0, borderTop: "1px solid var(--m-line)", width: "100%" } }),
1910
+ (0, import_react7.createElement)(
1911
+ Field,
1912
+ { label: "\u5BC6\u7801 / \u5E94\u7528\u4E13\u7528\u5BC6\u7801\uFF08\u4E0D\u5199\u5165\u914D\u7F6E\u6587\u4EF6\uFF09" },
1913
+ (0, import_react7.createElement)("input", {
1914
+ type: "password",
1915
+ value: password,
1916
+ onChange: (event) => setPassword(event.target.value)
1917
+ })
1918
+ ),
1919
+ (0, import_react7.createElement)(
1920
+ "div",
1921
+ { style: { display: "flex", gap: "8px", alignItems: "center" } },
1922
+ (0, import_react7.createElement)(Toggle, { checked: remember, onChange: setRemember, label: "\u8BB0\u4F4F\u5BC6\u7801" }),
1923
+ (0, import_react7.createElement)(
1924
+ "button",
1925
+ {
1926
+ className: "primary small",
1927
+ disabled: busy || password === "",
1928
+ onClick: () => void wrap(async () => {
1929
+ await onLogin(password, remember);
1930
+ setPassword("");
1931
+ })
1932
+ },
1933
+ "\u767B\u5F55"
1934
+ ),
1935
+ (0, import_react7.createElement)("button", { className: "ghost small", onClick: () => void wrap(onLogout) }, "\u9000\u51FA\u767B\u5F55")
1936
+ ),
1937
+ checkResult === "" ? null : (0, import_react7.createElement)("div", { className: "small muted" }, checkResult)
1938
+ ) : null
1939
+ );
1940
+ }
1941
+
1942
+ // src/client/index.tsx
1943
+ var VERSION = "0.1.0";
1944
+ var inject = ["slots"];
1945
+ var PANEL_ID = "mailharbor";
1946
+ var STYLE_ID = "mailharbor-style";
1947
+ function ensureStyles() {
1948
+ if (document.getElementById(STYLE_ID) !== null) return;
1949
+ const element = document.createElement("style");
1950
+ element.id = STYLE_ID;
1951
+ element.textContent = styles_default;
1952
+ document.head.append(element);
1953
+ }
1954
+ function apply(ctx) {
1955
+ ensureStyles();
1956
+ ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: PANEL_ID }, MailPanel));
1957
+ ctx.slots.inject(
1958
+ "settings.section",
1959
+ () => ctx.slots.register(
1960
+ {
1961
+ name: "settings.section",
1962
+ id: PANEL_ID,
1963
+ order: 35,
1964
+ label: () => "MailHarbor"
1965
+ },
1966
+ MailSettings
1967
+ )
1968
+ );
1969
+ ctx.slots.inject(
1970
+ "sidebar.panellist",
1971
+ () => ctx.slots.register(
1972
+ {
1973
+ name: "sidebar.panellist",
1974
+ id: PANEL_ID,
1975
+ order: 20,
1976
+ label: "MailHarbor"
1977
+ },
1978
+ MailIcon
1979
+ )
1980
+ );
1981
+ }
1982
+
1983
+ return module.exports;
1984
+ },
1985
+ });
1986
+
1987
+ //# sourceMappingURL=client.js.map