feat: v2.1.0 界面与凭据改进 —— 单一 API/MCP Key、邮件 Markdown 正文、/inboxs 域名筛选、暗色主题
- 单一 Key:API 与 MCP 共用一个 wmh_ Key,凭据迁移至 settings 表(0004 删除 api_credentials) - Markdown:入库时 HTML→Markdown(mailhook 同思路),webhook payload 新增 markdown 字段 - /emails 路由改为 /inboxs 并支持域名筛选;设置按钮移入头像下拉 - 主题切换按钮(头像左侧,亮/暗持久化);设置模态仅 全局 Webhook/账号密码 两 tab 且高度限屏 - 列表操作列按钮加 SVG 图标;mailboxes 去 Webhook 列;最近邮件加收件人列
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import PostalMime from "postal-mime";
|
||||
import type { Env } from "../env.ts";
|
||||
import {
|
||||
htmlToMarkdown,
|
||||
ids,
|
||||
DEFAULT_RETENTION_SECONDS,
|
||||
MAX_EMAIL_BYTES,
|
||||
@@ -159,11 +160,16 @@ export async function ingestEmail(env: Env, input: IngestInput): Promise<IngestR
|
||||
});
|
||||
}
|
||||
|
||||
// Markdown 正文:HTML 优先转换,其次纯文本(mailhook 同思路;webhook 投递使用)
|
||||
const markdownBody = parsed?.html
|
||||
? htmlToMarkdown(parsed.html)
|
||||
: (parsed?.text ?? "");
|
||||
|
||||
// 入库
|
||||
await env.DB.prepare(
|
||||
`INSERT INTO emails (id, account_id, mailbox_id, message_id, from_address, to_addresses, cc_addresses,
|
||||
subject, text_body, html_body, raw_storage_key, size, received_at, expires_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
subject, text_body, html_body, markdown_body, raw_storage_key, size, received_at, expires_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
)
|
||||
.bind(
|
||||
emailId,
|
||||
@@ -176,6 +182,7 @@ export async function ingestEmail(env: Env, input: IngestInput): Promise<IngestR
|
||||
parsed?.subject ?? "(unparsable mail)",
|
||||
parsed?.text ?? null,
|
||||
parsed?.html ?? null,
|
||||
markdownBody || null,
|
||||
rawKey,
|
||||
input.raw.byteLength,
|
||||
receivedAt,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
WEBHOOK_MAX_ATTEMPTS,
|
||||
WEBHOOK_RETRY_BASE_MS,
|
||||
} from "../../../shared/src/index.ts";
|
||||
import { htmlToMarkdown } from "../../../shared/src/index.ts";
|
||||
import { encryptSecret, decryptSecret, getAesKey, getSetting, setSetting } from "../db.ts";
|
||||
import { nowIso } from "../util.ts";
|
||||
|
||||
@@ -211,6 +212,8 @@ export function buildWebhookPayload(email: {
|
||||
toAddresses: string;
|
||||
subject: string | null;
|
||||
textBody: string | null;
|
||||
htmlBody: string | null;
|
||||
markdownBody: string | null;
|
||||
receivedAt: string;
|
||||
}): Record<string, unknown> {
|
||||
return {
|
||||
@@ -221,6 +224,11 @@ export function buildWebhookPayload(email: {
|
||||
to: safeParseArray(email.toAddresses),
|
||||
subject: email.subject,
|
||||
text: email.textBody,
|
||||
// Markdown 正文(HTML 转换优先,纯文本兜底);v2.1.0 新增
|
||||
markdown:
|
||||
email.markdownBody ??
|
||||
(email.htmlBody ? htmlToMarkdown(email.htmlBody) : null) ??
|
||||
email.textBody,
|
||||
received_at: email.receivedAt,
|
||||
};
|
||||
}
|
||||
@@ -305,6 +313,8 @@ interface DueDeliveryRow {
|
||||
to_addresses: string;
|
||||
subject: string | null;
|
||||
text_body: string | null;
|
||||
html_body: string | null;
|
||||
markdown_body: string | null;
|
||||
received_at: string;
|
||||
expires_at: string;
|
||||
}
|
||||
@@ -323,7 +333,7 @@ export async function processDueDeliveries(
|
||||
const due = await env.DB.prepare(
|
||||
`SELECT d.id AS delivery_id, d.url, d.headers, d.key_enc, d.attempt_count,
|
||||
e.id AS email_id, e.mailbox_id, e.from_address, e.to_addresses, e.subject, e.text_body,
|
||||
e.received_at, e.expires_at
|
||||
e.html_body, e.markdown_body, e.received_at, e.expires_at
|
||||
FROM webhook_deliveries d
|
||||
JOIN emails e ON e.id = d.email_id
|
||||
WHERE d.status IN ('pending', 'failed')
|
||||
@@ -362,6 +372,8 @@ export async function processDueDeliveries(
|
||||
toAddresses: row.to_addresses,
|
||||
subject: row.subject,
|
||||
textBody: row.text_body,
|
||||
htmlBody: row.html_body,
|
||||
markdownBody: row.markdown_body,
|
||||
receivedAt: row.received_at,
|
||||
});
|
||||
const result = await attemptDelivery(
|
||||
|
||||
Reference in New Issue
Block a user