修复gmail附件和回复编辑器图片不显示
This commit is contained in:
@@ -55,7 +55,7 @@
|
|||||||
effect="dark"
|
effect="dark"
|
||||||
content="已发送"
|
content="已发送"
|
||||||
>
|
>
|
||||||
<Icon icon="bi:send-arrow-up" style="color: #67C23A" width="20" height="20"
|
<Icon icon="bi:send-arrow-up-fill" style="color: #67C23A" width="20" height="20"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
effect="dark"
|
effect="dark"
|
||||||
content="发送延迟"
|
content="发送延迟"
|
||||||
>
|
>
|
||||||
<Icon icon="quill:send-later" style="color:#FBBD08" width="20"
|
<Icon icon="bi:send-arrow-up-fill" style="color:#FBBD08" width="20"
|
||||||
height="20"/>
|
height="20"/>
|
||||||
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ function initEditor() {
|
|||||||
toolbar_mode: 'scrolling',
|
toolbar_mode: 'scrolling',
|
||||||
mobile: {
|
mobile: {
|
||||||
toolbar: 'fullscreen bold emoticons forecolor backcolor italic fontsize | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist | link image | table code preview ',
|
toolbar: 'fullscreen bold emoticons forecolor backcolor italic fontsize | alignleft aligncenter alignright alignjustify | outdent indent | bullist numlist | link image | table code preview ',
|
||||||
|
|
||||||
},
|
},
|
||||||
font_size_formats: '8px 10px 12px 14px 16px 18px 24px 36px',
|
font_size_formats: '8px 10px 12px 14px 16px 18px 24px 36px',
|
||||||
emoticons_search: false,
|
emoticons_search: false,
|
||||||
@@ -85,8 +84,9 @@ function initEditor() {
|
|||||||
language_url: '/tinymce/langs/zh_CN.js',
|
language_url: '/tinymce/langs/zh_CN.js',
|
||||||
menubar: false,
|
menubar: false,
|
||||||
license_key: 'gpl',
|
license_key: 'gpl',
|
||||||
|
noneditable_class: 'mceNonEditable',
|
||||||
content_style: ` .tox-dialog__body-content { margin: 0 !important; }
|
content_style: ` .tox-dialog__body-content { margin: 0 !important; }
|
||||||
img { max-width: 100%; height: auto; }
|
img { max-width: 100% !important; height: auto !important; }
|
||||||
body {margin: 10px 8px 0 5px !important; font-family: 'HarmonyOS'; font-size: 14px;}
|
body {margin: 10px 8px 0 5px !important; font-family: 'HarmonyOS'; font-size: 14px;}
|
||||||
@media (pointer: fine) and (hover: hover) {
|
@media (pointer: fine) and (hover: hover) {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@@ -110,9 +110,6 @@ function initEditor() {
|
|||||||
.mce-item-table:not([border]), .mce-item-table:not([border]) caption, .mce-item-table:not([border]) td, .mce-item-table:not([border]) th, .mce-item-table[border="0"], .mce-item-table[border="0"] caption, .mce-item-table[border="0"] td, .mce-item-table[border="0"] th, table[style*="border-width: 0px"], table[style*="border-width: 0px"] caption, table[style*="border-width: 0px"] td, table[style*="border-width: 0px"] th {
|
.mce-item-table:not([border]), .mce-item-table:not([border]) caption, .mce-item-table:not([border]) td, .mce-item-table:not([border]) th, .mce-item-table[border="0"], .mce-item-table[border="0"] caption, .mce-item-table[border="0"] td, .mce-item-table[border="0"] th, table[style*="border-width: 0px"], table[style*="border-width: 0px"] caption, table[style*="border-width: 0px"] td, table[style*="border-width: 0px"] th {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
a {
|
|
||||||
color: #409EFF !important;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
setup: (ed) => {
|
setup: (ed) => {
|
||||||
editor.value = ed;
|
editor.value = ed;
|
||||||
|
|||||||
@@ -67,12 +67,14 @@ import {fileToBase64, formatBytes} from "@/utils/file-utils.js";
|
|||||||
import {getIconByName} from "@/utils/icon-utils.js";
|
import {getIconByName} from "@/utils/icon-utils.js";
|
||||||
import sendPercent from "@/components/send-percent/index.vue"
|
import sendPercent from "@/components/send-percent/index.vue"
|
||||||
import {formatDetailDate} from "@/utils/day.js";
|
import {formatDetailDate} from "@/utils/day.js";
|
||||||
|
import {useSettingStore} from "@/store/setting.js";
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open,
|
open,
|
||||||
openReply
|
openReply
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const settingStore = useSettingStore()
|
||||||
const emailStore = useEmailStore();
|
const emailStore = useEmailStore();
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
const editor = ref({})
|
const editor = ref({})
|
||||||
@@ -283,11 +285,22 @@ function openReply(email) {
|
|||||||
<br>
|
<br>
|
||||||
${ formatDetailDate(email.createTime) },${email.name} <${email.sendEmail}> 来信:
|
${ formatDetailDate(email.createTime) },${email.name} <${email.sendEmail}> 来信:
|
||||||
</div>
|
</div>
|
||||||
<blockquote style="margin: 0 0 0 0.8ex;border-left: 1px solid rgb(204,204,204);padding-left: 1ex;">${email.content}</blockquote>`
|
<blockquote class="mceNonEditable" style="margin: 0 0 0 0.8ex;border-left: 1px solid rgb(204,204,204);padding-left: 1ex;">
|
||||||
|
<articl>
|
||||||
|
${formatImage(email.content) || `<pre style="font-family: inherit;word-break: break-word;white-space: pre-wrap;margin: 0">${email.text}</pre>`}
|
||||||
|
</article>
|
||||||
|
</blockquote>`
|
||||||
open()
|
open()
|
||||||
|
console.log(defValue.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatImage(content) {
|
||||||
|
content = content || '';
|
||||||
|
const domain = settingStore.settings.r2Domain;
|
||||||
|
console.log(content)
|
||||||
|
return content.replace(/{{domain}}/g, domain + '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-scrollbar class="htm-scrollbar" :class="email.attList.length === 0 ? 'bottom-distance' : ''">
|
<el-scrollbar class="htm-scrollbar" :class="email.attList.length === 0 ? 'bottom-distance' : ''">
|
||||||
<ShadowHtml :html="formatImage(email.content)" v-if="email.content" />
|
<ShadowHtml :html="formatImage(email.content)" v-if="email.content" />
|
||||||
<span v-else class="email-text" >{{email.text}}</span>
|
<pre v-else class="email-text" >{{email.text}}</pre>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<div class="att" v-if="email.attList.length > 0">
|
<div class="att" v-if="email.attList.length > 0">
|
||||||
<div class="att-title">
|
<div class="att-title">
|
||||||
@@ -380,8 +380,10 @@ const handleDelete = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.email-text {
|
.email-text {
|
||||||
|
font-family: inherit;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-distance {
|
.bottom-distance {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
mail-worker/dist/index.html
vendored
4
mail-worker/dist/index.html
vendored
@@ -6,8 +6,8 @@
|
|||||||
<title></title>
|
<title></title>
|
||||||
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
||||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||||
<script type="module" crossorigin src="/assets/index-C4HVi8Hj.js"></script>
|
<script type="module" crossorigin src="/assets/index-BXq2c1cR.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DI8kV-nO.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DAVUbrwg.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loading-first">
|
<div id="loading-first">
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export async function email(message, env, ctx) {
|
|||||||
forwardStatus,
|
forwardStatus,
|
||||||
forwardEmail,
|
forwardEmail,
|
||||||
ruleEmail,
|
ruleEmail,
|
||||||
ruleType
|
ruleType,
|
||||||
|
r2Domain
|
||||||
} = await settingService.query({ env });
|
} = await settingService.query({ env });
|
||||||
|
|
||||||
if (receive === settingConst.receive.CLOSE) {
|
if (receive === settingConst.receive.CLOSE) {
|
||||||
@@ -45,8 +46,6 @@ export async function email(message, env, ctx) {
|
|||||||
|
|
||||||
const email = await PostalMime.parse(content);
|
const email = await PostalMime.parse(content);
|
||||||
|
|
||||||
console.log(email)
|
|
||||||
|
|
||||||
const toName = email.to.find(item => item.address === message.to)?.name || '';
|
const toName = email.to.find(item => item.address === message.to)?.name || '';
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
@@ -82,13 +81,12 @@ export async function email(message, env, ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let emailRow = await emailService.receive({ env }, params, cidAttachments);
|
let emailRow = await emailService.receive({ env }, params, cidAttachments, r2Domain);
|
||||||
|
|
||||||
attachments.forEach(attachment => {
|
attachments.forEach(attachment => {
|
||||||
attachment.emailId = emailRow.emailId;
|
attachment.emailId = emailRow.emailId;
|
||||||
attachment.userId = emailRow.userId;
|
attachment.userId = emailRow.userId;
|
||||||
attachment.accountId = emailRow.accountId;
|
attachment.accountId = emailRow.accountId;
|
||||||
attachment.type = attachment.contentId ? attConst.type.EMBED : attConst.type.ATT;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (attachments.length > 0 && env.r2) {
|
if (attachments.length > 0 && env.r2) {
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ const attService = {
|
|||||||
return orm(c).select().from(att).where(
|
return orm(c).select().from(att).where(
|
||||||
and(
|
and(
|
||||||
inArray(att.emailId,emailIds),
|
inArray(att.emailId,emailIds),
|
||||||
eq(att.type, attConst.type.ATT),
|
eq(att.type, attConst.type.ATT)
|
||||||
isNull(att.contentId)))
|
))
|
||||||
.all();
|
.all();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import orm from '../entity/orm';
|
import orm from '../entity/orm';
|
||||||
import email from '../entity/email';
|
import email from '../entity/email';
|
||||||
import { emailConst, isDel, settingConst } from '../const/entity-const';
|
import { attConst, emailConst, isDel, settingConst } from '../const/entity-const';
|
||||||
import { and, desc, eq, gt, inArray, lt, count, asc, sql, ne, or } from 'drizzle-orm';
|
import { and, desc, eq, gt, inArray, lt, count, asc, sql, ne, or } from 'drizzle-orm';
|
||||||
import { star } from '../entity/star';
|
import { star } from '../entity/star';
|
||||||
import settingService from './setting-service';
|
import settingService from './setting-service';
|
||||||
@@ -120,28 +120,8 @@ const emailService = {
|
|||||||
.run();
|
.run();
|
||||||
},
|
},
|
||||||
|
|
||||||
receive(c, params, cidAttList) {
|
receive(c, params, cidAttList, r2domain) {
|
||||||
|
params.content = this.imgReplace(params.content, cidAttList, r2domain)
|
||||||
const { document } = parseHTML(params.content);
|
|
||||||
|
|
||||||
const images = Array.from(document.querySelectorAll('img'));
|
|
||||||
|
|
||||||
for (const img of images) {
|
|
||||||
|
|
||||||
const src = img.getAttribute('src');
|
|
||||||
if (src && src.startsWith('cid:')) {
|
|
||||||
|
|
||||||
const cid = src.replace(/^cid:/, '');
|
|
||||||
const attCidIndex = cidAttList.findIndex(cidAtt => cidAtt.contentId.replace(/^<|>$/g, '') === cid);
|
|
||||||
|
|
||||||
if (attCidIndex > -1) {
|
|
||||||
const cidAtt = cidAttList[attCidIndex];
|
|
||||||
img.setAttribute('src', '{{domain}}' + cidAtt.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
params.content = document.toString();
|
|
||||||
return orm(c).insert(email).values({ ...params }).returning().get();
|
return orm(c).insert(email).values({ ...params }).returning().get();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -151,7 +131,7 @@ const emailService = {
|
|||||||
|
|
||||||
const { resendTokens, r2Domain, send } = await settingService.query(c);
|
const { resendTokens, r2Domain, send } = await settingService.query(c);
|
||||||
|
|
||||||
const { attDataList, html } = await attService.toImageUrlHtml(c, content, r2Domain);
|
let { attDataList, html } = await attService.toImageUrlHtml(c, content, r2Domain);
|
||||||
|
|
||||||
if (attDataList.length > 0 && !r2Domain) {
|
if (attDataList.length > 0 && !r2Domain) {
|
||||||
throw new BizError('r2域名未配置不能发送正文图片');
|
throw new BizError('r2域名未配置不能发送正文图片');
|
||||||
@@ -216,7 +196,6 @@ const emailService = {
|
|||||||
name = emailUtils.getName(accountRow.email);
|
name = emailUtils.getName(accountRow.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let emailRow = {
|
let emailRow = {
|
||||||
messageId: null
|
messageId: null
|
||||||
};
|
};
|
||||||
@@ -290,6 +269,7 @@ const emailService = {
|
|||||||
throw new BizError(error.message);
|
throw new BizError(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html = this.imgReplace(html, null, r2Domain);
|
||||||
|
|
||||||
const emailData = {};
|
const emailData = {};
|
||||||
emailData.sendEmail = accountRow.email;
|
emailData.sendEmail = accountRow.email;
|
||||||
@@ -373,6 +353,47 @@ const emailService = {
|
|||||||
return emailRowList;
|
return emailRowList;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
imgReplace(content, cidAttList, r2domain) {
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const { document } = parseHTML(content);
|
||||||
|
|
||||||
|
const images = Array.from(document.querySelectorAll('img'));
|
||||||
|
|
||||||
|
const useAtts = []
|
||||||
|
|
||||||
|
for (const img of images) {
|
||||||
|
|
||||||
|
const src = img.getAttribute('src');
|
||||||
|
if (src && src.startsWith('cid:') && cidAttList) {
|
||||||
|
|
||||||
|
const cid = src.replace(/^cid:/, '');
|
||||||
|
const attCidIndex = cidAttList.findIndex(cidAtt => cidAtt.contentId.replace(/^<|>$/g, '') === cid);
|
||||||
|
|
||||||
|
if (attCidIndex > -1) {
|
||||||
|
const cidAtt = cidAttList[attCidIndex];
|
||||||
|
img.setAttribute('src', '{{domain}}' + cidAtt.key);
|
||||||
|
useAtts.push(cidAtt)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src && src.startsWith(r2domain + '/')) {
|
||||||
|
img.setAttribute('src', src.replace(r2domain + '/', '{{domain}}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
useAtts.forEach(att => {
|
||||||
|
att.type = attConst.type.EMBED
|
||||||
|
})
|
||||||
|
|
||||||
|
return document.toString();
|
||||||
|
},
|
||||||
|
|
||||||
selectById(c, emailId) {
|
selectById(c, emailId) {
|
||||||
return orm(c).select().from(email).where(
|
return orm(c).select().from(email).where(
|
||||||
and(eq(email.emailId, emailId),
|
and(eq(email.emailId, emailId),
|
||||||
|
|||||||
Reference in New Issue
Block a user