mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-06-09 17:39:56 +08:00
feat: UI: MailBox add reply button (#187)
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
<script setup>
|
||||
import { watch, onMounted, ref, onBeforeUnmount } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useGlobalState } from '../store'
|
||||
import { CloudDownloadRound } from '@vicons/material'
|
||||
import { CloudDownloadRound, ReplyFilled } from '@vicons/material'
|
||||
import { useIsMobile } from '../utils/composables'
|
||||
import { processItem, getDownloadEmlUrl } from '../utils/email-parser'
|
||||
|
||||
const message = useMessage()
|
||||
const isMobile = useIsMobile()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
enableUserDeleteEmail: {
|
||||
@@ -31,9 +33,16 @@ const props = defineProps({
|
||||
default: () => { },
|
||||
requried: false
|
||||
},
|
||||
showReply: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
requried: false
|
||||
}
|
||||
})
|
||||
|
||||
const { themeSwitch, mailboxSplitSize, useIframeShowMail } = useGlobalState()
|
||||
const {
|
||||
localeCache, isDark, mailboxSplitSize, useIframeShowMail, sendMailModel
|
||||
} = useGlobalState()
|
||||
const autoRefresh = ref(false)
|
||||
const autoRefreshInterval = ref(30)
|
||||
const data = ref([])
|
||||
@@ -48,7 +57,7 @@ const curAttachments = ref([])
|
||||
const curMail = ref(null);
|
||||
|
||||
const { t } = useI18n({
|
||||
locale: 'zh',
|
||||
locale: localeCache.value || 'zh',
|
||||
messages: {
|
||||
en: {
|
||||
success: 'Success',
|
||||
@@ -59,7 +68,8 @@ const { t } = useI18n({
|
||||
downloadMail: 'Download Mail',
|
||||
pleaseSelectMail: "Please select a mail to view.",
|
||||
delete: 'Delete',
|
||||
deleteMailTip: 'Are you sure you want to delete this mail?'
|
||||
deleteMailTip: 'Are you sure you want to delete this mail?',
|
||||
reply: 'Reply'
|
||||
},
|
||||
zh: {
|
||||
success: '成功',
|
||||
@@ -70,7 +80,8 @@ const { t } = useI18n({
|
||||
attachments: '查看附件',
|
||||
pleaseSelectMail: "请选择一封邮件查看。",
|
||||
delete: '删除',
|
||||
deleteMailTip: '确定要删除这封邮件吗?'
|
||||
deleteMailTip: '确定要删除这封邮件吗?',
|
||||
reply: '回复'
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -132,7 +143,7 @@ const getAttachments = (attachments) => {
|
||||
};
|
||||
|
||||
const mailItemClass = (row) => {
|
||||
return curMail.value && row.id == curMail.value.id ? (themeSwitch.value ? 'overlay overlay-dark-backgroud' : 'overlay overlay-light-backgroud') : '';
|
||||
return curMail.value && row.id == curMail.value.id ? (isDark.value ? 'overlay overlay-dark-backgroud' : 'overlay overlay-light-backgroud') : '';
|
||||
};
|
||||
|
||||
const deleteMail = async () => {
|
||||
@@ -146,6 +157,25 @@ const deleteMail = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const replyMail = async () => {
|
||||
const emailRegex = /(.+?) <(.+?)>/;
|
||||
let toMail = curMail.value.originalSource;
|
||||
let toName = ""
|
||||
const match = emailRegex.exec(curMail.value.source);
|
||||
if (match) {
|
||||
toName = match[1];
|
||||
toMail = match[2];
|
||||
}
|
||||
Object.assign(sendMailModel.value, {
|
||||
toName: toName,
|
||||
toMail: toMail,
|
||||
subject: localeCache.value == 'zh' ? `回复: ${curMail.value.subject}` : `Re: ${curMail.value.subject}`,
|
||||
contentType: 'text',
|
||||
content: "",
|
||||
});
|
||||
await router.push('/send');
|
||||
};
|
||||
|
||||
const onSpiltSizeChange = (size) => {
|
||||
mailboxSplitSize.value = size;
|
||||
}
|
||||
@@ -231,9 +261,17 @@ onBeforeUnmount(() => {
|
||||
</n-button>
|
||||
<n-button tag="a" target="_blank" tertiary type="info" size="small" :download="curMail.id + '.eml'"
|
||||
:href="getDownloadEmlUrl(curMail.raw)">
|
||||
<n-icon :component="CloudDownloadRound" />
|
||||
<template #icon>
|
||||
<n-icon :component="CloudDownloadRound" />
|
||||
</template>
|
||||
{{ t('downloadMail') }}
|
||||
</n-button>
|
||||
<n-button v-if="showReply" size="small" tertiary type="info" @click="replyMail">
|
||||
<template #icon>
|
||||
<n-icon :component="ReplyFilled" />
|
||||
</template>
|
||||
{{ t('reply') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<iframe v-if="useIframeShowMail" :srcdoc="curMail.message"
|
||||
style="margin-top: 10px;width: 100%; height: 100%;">
|
||||
@@ -251,13 +289,14 @@ onBeforeUnmount(() => {
|
||||
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
||||
</div>
|
||||
<n-switch v-model:value="autoRefresh" size="small">
|
||||
<n-switch v-model:value="autoRefresh" size="small" :round="false">
|
||||
<template #checked>
|
||||
{{ t('autoRefresh') }}
|
||||
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
|
||||
</template>
|
||||
<template #unchecked>
|
||||
{{ t('autoRefresh') }}
|
||||
</template></n-switch>
|
||||
</template>
|
||||
</n-switch>
|
||||
<n-button @click="refresh" size="small" type="primary">
|
||||
{{ t('refresh') }}
|
||||
</n-button>
|
||||
@@ -316,6 +355,12 @@ onBeforeUnmount(() => {
|
||||
<n-icon :component="CloudDownloadRound" />
|
||||
{{ t('downloadMail') }}
|
||||
</n-button>
|
||||
<n-button v-if="showReply" size="small" tertiary type="info" @click="replyMail">
|
||||
<template #icon>
|
||||
<n-icon :component="ReplyFilled" />
|
||||
</template>
|
||||
{{ t('reply') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<div v-html="curMail.message" style="margin-top: 10px;"></div>
|
||||
</n-card>
|
||||
|
||||
@@ -29,7 +29,15 @@ export const useGlobalState = createGlobalState(
|
||||
source_prefix: '',
|
||||
name: '',
|
||||
}
|
||||
})
|
||||
});
|
||||
const sendMailModel = useStorage('sendMailModel', {
|
||||
fromName: "",
|
||||
toName: "",
|
||||
toMail: "",
|
||||
subject: "",
|
||||
contentType: 'text',
|
||||
content: "",
|
||||
});
|
||||
const showAuth = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const showAdminAuth = ref(false);
|
||||
@@ -37,7 +45,6 @@ export const useGlobalState = createGlobalState(
|
||||
const adminAuth = useStorage('adminAuth', '');
|
||||
const jwt = useStorage('jwt', '');
|
||||
const localeCache = useStorage('locale', 'zh');
|
||||
const themeSwitch = useStorage('themeSwitch', false);
|
||||
const adminTab = ref("account");
|
||||
const adminMailTabAddress = ref("");
|
||||
const adminSendBoxTabAddress = ref("");
|
||||
@@ -48,13 +55,13 @@ export const useGlobalState = createGlobalState(
|
||||
toggleDark,
|
||||
loading,
|
||||
settings,
|
||||
sendMailModel,
|
||||
openSettings,
|
||||
showAuth,
|
||||
showPassword,
|
||||
auth,
|
||||
jwt,
|
||||
localeCache,
|
||||
themeSwitch,
|
||||
adminAuth,
|
||||
showAdminAuth,
|
||||
adminTab,
|
||||
|
||||
@@ -7,6 +7,7 @@ function humanFileSize(size) {
|
||||
|
||||
export async function processItem(item) {
|
||||
// Try to parse the email using mail-parser-wasm
|
||||
item.originalSource = item.source;
|
||||
try {
|
||||
const { parse_message } = await import('mail-parser-wasm');
|
||||
const parsedEmail = parse_message(item.raw);
|
||||
|
||||
@@ -16,7 +16,7 @@ const deleteMail = async (curMailId) => {
|
||||
|
||||
<template>
|
||||
<div v-if="settings.address">
|
||||
<MailBox :showEMailTo="false" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||
<MailBox :showEMailTo="false" :showReply="true" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<script setup>
|
||||
import '@wangeditor/editor/dist/css/style.css'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { DomEditor } from '@wangeditor/editor'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { onMounted, onBeforeUnmount, ref, shallowRef } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import AdminContact from '../admin/AdminContact.vue'
|
||||
|
||||
import { useGlobalState } from '../../store'
|
||||
@@ -15,16 +13,8 @@ const message = useMessage()
|
||||
const isPreview = ref(false)
|
||||
const editorRef = shallowRef()
|
||||
|
||||
const mailModel = useStorage('mailModelCache', {
|
||||
fromName: "",
|
||||
toName: "",
|
||||
toMail: "",
|
||||
subject: "",
|
||||
contentType: 'text',
|
||||
content: "",
|
||||
})
|
||||
|
||||
const { settings } = useGlobalState()
|
||||
const { settings, sendMailModel } = useGlobalState()
|
||||
|
||||
const { t } = useI18n({
|
||||
locale: 'zh',
|
||||
@@ -81,15 +71,15 @@ const send = async () => {
|
||||
method: 'POST',
|
||||
body:
|
||||
JSON.stringify({
|
||||
from_name: mailModel.value.fromName,
|
||||
to_name: mailModel.value.toName,
|
||||
to_mail: mailModel.value.toMail,
|
||||
subject: mailModel.value.subject,
|
||||
is_html: mailModel.value.contentType != 'text',
|
||||
content: mailModel.value.content,
|
||||
from_name: sendMailModel.value.fromName,
|
||||
to_name: sendMailModel.value.toName,
|
||||
to_mail: sendMailModel.value.toMail,
|
||||
subject: sendMailModel.value.subject,
|
||||
is_html: sendMailModel.value.contentType != 'text',
|
||||
content: sendMailModel.value.content,
|
||||
})
|
||||
})
|
||||
mailModel.value = {
|
||||
sendMailModel.value = {
|
||||
fromName: "",
|
||||
toName: "",
|
||||
toMail: "",
|
||||
@@ -170,43 +160,43 @@ onMounted(async () => {
|
||||
<n-button type="primary" @click="send">{{ t('send') }}</n-button>
|
||||
</div>
|
||||
<div class="left">
|
||||
<n-form :model="mailModel">
|
||||
<n-form :model="sendMailModel">
|
||||
<n-form-item :label="t('fromName')" label-placement="top">
|
||||
<n-input-group>
|
||||
<n-input v-model:value="mailModel.fromName" />
|
||||
<n-input v-model:value="sendMailModel.fromName" />
|
||||
<n-input :value="settings.address" disabled />
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('toName')" label-placement="top">
|
||||
<n-input-group>
|
||||
<n-input v-model:value="mailModel.toName" />
|
||||
<n-input v-model:value="mailModel.toMail" />
|
||||
<n-input v-model:value="sendMailModel.toName" />
|
||||
<n-input v-model:value="sendMailModel.toMail" />
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('subject')" label-placement="top">
|
||||
<n-input v-model:value="mailModel.subject" />
|
||||
<n-input v-model:value="sendMailModel.subject" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('options')" label-placement="top">
|
||||
<n-radio-group v-model:value="mailModel.contentType">
|
||||
<n-radio-group v-model:value="sendMailModel.contentType">
|
||||
<n-radio-button v-for="option in contentTypes" :key="option.value" :value="option.value"
|
||||
:label="option.label" />
|
||||
</n-radio-group>
|
||||
<n-button v-if="mailModel.contentType != 'text'" @click="isPreview = !isPreview"
|
||||
<n-button v-if="sendMailModel.contentType != 'text'" @click="isPreview = !isPreview"
|
||||
style="margin-left: 10px;">
|
||||
{{ isPreview ? t('edit') : t('preview') }}
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item :label="t('content')" label-placement="top">
|
||||
<n-card v-if="isPreview">
|
||||
<div v-html="mailModel.content" />
|
||||
<div v-html="sendMailModel.content" />
|
||||
</n-card>
|
||||
<div v-else-if="mailModel.contentType == 'rich'" style="border: 1px solid #ccc">
|
||||
<div v-else-if="sendMailModel.contentType == 'rich'" style="border: 1px solid #ccc">
|
||||
<Toolbar style="border-bottom: 1px solid #ccc" :defaultConfig="toolbarConfig"
|
||||
:editor="editorRef" mode="default" />
|
||||
<Editor style="height: 500px; overflow-y: hidden;" v-model="mailModel.content"
|
||||
<Editor style="height: 500px; overflow-y: hidden;" v-model="sendMailModel.content"
|
||||
:defaultConfig="editorConfig" mode="default" @onCreated="handleCreated" />
|
||||
</div>
|
||||
<n-input v-else type="textarea" v-model:value="mailModel.content" :autosize="{
|
||||
<n-input v-else type="textarea" v-model:value="sendMailModel.content" :autosize="{
|
||||
minRows: 3
|
||||
}" />
|
||||
</n-form-item>
|
||||
|
||||
Reference in New Issue
Block a user