Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mail-vue/src/components/email-scroll/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<div class="header-left" :style="'padding-left:' + actionLeft">

<slot name="first"></slot>
<Icon class="icon" icon="mdi:arrow-down-bold-box-outline" width="20" height="20" @click="scrollToBottom" :title="$t('scrollToBottom')"/>
<Icon class="icon reload" icon="ion:reload" width="18" height="18" @click="refresh"/>
<Icon v-perm="'email:delete'" class="icon delete" icon="uiw:delete" width="16" height="16"
v-if="getSelectedMailsIds().length > 0"
Expand Down Expand Up @@ -579,6 +580,13 @@ function loadData() {
getEmailList()
}

function scrollToBottom() {
const wrap = scrollbarRef.value?.wrapRef;
if (wrap) {
scrollbarRef.value.setScrollTop(wrap.scrollHeight);
}
}

</script>
<style lang="scss" scoped>

Expand Down
3 changes: 2 additions & 1 deletion mail-vue/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ const en = {
character: '',
mustNotContain: 'Must Not Contain',
mustNotContainDesc: 'Separate with commas',
setSuccess: 'Settings saved successfully'
setSuccess: 'Settings saved successfully',
scrollToBottom: 'Scroll to Bottom'
}

export default en
3 changes: 2 additions & 1 deletion mail-vue/src/i18n/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ const zh = {
character: '位',
mustNotContain: '禁止包含',
mustNotContainDesc: '输入多个值用,分开',
setSuccess: '设置成功'
setSuccess: '设置成功',
scrollToBottom: '滚动到底部'
}
export default zh
8 changes: 8 additions & 0 deletions mail-vue/src/layout/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<Header />
</el-header>
<Main />
<div class="footer" v-if="uiStore.setting.footer" v-html="uiStore.setting.footer"></div>
</el-main>
</el-container>
</el-container>
Expand Down Expand Up @@ -122,4 +123,11 @@ onBeforeUnmount(() => {
pointer-events: none;
opacity: 0;
}

.footer {
text-align: center;
padding: 20px;
color: var(--el-text-color-secondary);
font-size: 14px;
}
</style>
17 changes: 17 additions & 0 deletions mail-vue/src/views/sys-setting/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
</div>
</div>
</div>
<div class="setting-item">
<div class="title-item"><span>Footer</span></div>
<div class="email-title">
<span>{{ setting.footer || 'None' }}</span>
<el-button class="opt-button" size="small" type="primary" @click="editFooterShow = true">
<Icon icon="lsicon:edit-outline" width="16" height="16"/>
</el-button>
</div>
</div>
</div>
</div>

Expand Down Expand Up @@ -413,6 +422,12 @@
<el-button type="primary" :loading="settingLoading" @click="saveTitle">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog v-model="editFooterShow" title="Change Footer" width="340" @closed="editFooter = setting.footer">
<form>
<el-input type="textarea" :rows="3" placeholder="Footer (HTML supported)" v-model="editFooter"/>
<el-button type="primary" :loading="settingLoading" @click="saveFooter">{{ $t('save') }}</el-button>
</form>
</el-dialog>
<el-dialog v-model="resendTokenFormShow" :title="$t('resendToken')" width="340" @closed="cleanResendTokenForm">
<form>
<el-select style="margin-bottom: 15px" v-model="resendTokenForm.domain" placeholder="Select">
Expand Down Expand Up @@ -764,6 +779,7 @@ const localUpShow = ref(false)
const accountStore = useAccountStore();
const userStore = useUserStore();
const editTitleShow = ref(false)
const editFooterShow = ref(false)
const resendTokenFormShow = ref(false)
const r2DomainShow = ref(false)
const turnstileShow = ref(false)
Expand All @@ -777,6 +793,7 @@ const settingStore = useSettingStore();
const uiStore = useUiStore();
const {settings: setting} = storeToRefs(settingStore);
const editTitle = ref('')
const editFooter = ref('')
const settingLoading = ref(false)
const clearS3Loading = ref(false)
const r2DomainInput = ref('')
Expand Down
5 changes: 3 additions & 2 deletions mail-worker/src/entity/setting.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sqliteTable, text, integer} from 'drizzle-orm/sqlite-core';
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const setting = sqliteTable('setting', {
register: integer('register').default(0).notNull(),
receive: integer('receive').default(0).notNull(),
Expand Down Expand Up @@ -46,6 +46,7 @@ export const setting = sqliteTable('setting', {
tgMsgTo: text('tg_msg_to').default('show').notNull(),
tgMsgText: text('tg_msg_text').default('hide').notNull(),
minEmailPrefix: integer('min_email_prefix').default(0).notNull(),
emailPrefixFilter: text('email_prefix_filter').default('').notNull()
emailPrefixFilter: text('email_prefix_filter').default('').notNull(),
footer: text('footer').default('').notNull()
});
export default setting
20 changes: 15 additions & 5 deletions mail-worker/src/service/setting-service.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import KvConst from '../const/kv-const';
import setting from '../entity/setting';
import orm from '../entity/orm';
import {verifyRecordType} from '../const/entity-const';
import { verifyRecordType } from '../const/entity-const';
import fileUtils from '../utils/file-utils';
import r2Service from './r2-service';
import constant from '../const/constant';
import BizError from '../error/biz-error';
import {t} from '../i18n/i18n'
import { t } from '../i18n/i18n'
import verifyRecordService from './verify-record-service';

const settingService = {

async refresh(c) {
const settingRow = await orm(c).select().from(setting).get();
if (!settingRow) return;
settingRow.resendTokens = JSON.parse(settingRow.resendTokens);
c.set('setting', settingRow);
await c.env.kv.put(KvConst.SETTING, JSON.stringify(settingRow));
Expand Down Expand Up @@ -134,7 +135,7 @@ const settingService = {
}

if (background) {
await r2Service.delete(c,background)
await r2Service.delete(c, background)
await orm(c).update(setting).set({ background: '' }).run();
await this.refresh(c)
}
Expand Down Expand Up @@ -170,6 +171,14 @@ const settingService = {
async websiteConfig(c) {

const settingRow = await this.get(c, true);
let domainList = [];
try {
domainList = JSON.parse(settingRow.domainList);
} catch (e) {
if (settingRow.domainList && typeof settingRow.domainList === 'string') {
domainList = settingRow.domainList.split(',').map(d => d.trim()).filter(d => d);
}
}

return {
register: settingRow.register,
Expand All @@ -184,7 +193,7 @@ const settingService = {
siteKey: settingRow.siteKey,
background: settingRow.background,
loginOpacity: settingRow.loginOpacity,
domainList: settingRow.domainList,
domainList: settingRow.loginDomain === 1 ? domainList : [],
regKey: settingRow.regKey,
regVerifyOpen: settingRow.regVerifyOpen,
addVerifyOpen: settingRow.addVerifyOpen,
Expand All @@ -200,7 +209,8 @@ const settingService = {
linuxdoClientId: settingRow.linuxdoClientId,
linuxdoCallbackUrl: settingRow.linuxdoCallbackUrl,
linuxdoSwitch: settingRow.linuxdoSwitch,
minEmailPrefix: settingRow.minEmailPrefix
minEmailPrefix: settingRow.minEmailPrefix,
footer: settingRow.footer
};
}
};
Expand Down
12 changes: 7 additions & 5 deletions mail-worker/src/service/user-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import saltHashUtils from '../utils/crypto-utils';
import constant from '../const/constant';
import { t } from '../i18n/i18n'
import reqUtils from '../utils/req-utils';
import {oauth} from "../entity/oauth";
import { oauth } from "../entity/oauth";
import oauthService from "./oauth-service";
import settingService from "./setting-service";

const userService = {

Expand Down Expand Up @@ -170,7 +171,7 @@ const userService = {
emailService.selectUserEmailCountList(c, userIds, emailConst.type.SEND, isDel.DELETE),
accountService.selectUserAccountCountList(c, userIds),
accountService.selectUserAccountCountList(c, userIds, isDel.DELETE),
roleService.selectByIdsHasPermKey(c, types,'email:send')
roleService.selectByIdsHasPermKey(c, types, 'email:send')
]);

const receiveMap = Object.fromEntries(emailCounts.map(item => [item.userId, item.count]));
Expand Down Expand Up @@ -223,7 +224,7 @@ const userService = {

const activeIp = reqUtils.getIp(c);

const {os, browser, device} = reqUtils.getUserAgent(c);
const { os, browser, device } = reqUtils.getUserAgent(c);

const params = {
os,
Expand Down Expand Up @@ -303,7 +304,8 @@ const userService = {

const { email, type, password } = params;

if (!c.env.domain.includes(emailUtils.getDomain(email))) {
const { domainList } = await settingService.query(c);
if (!domainList.includes('@' + emailUtils.getDomain(email))) {
throw new BizError(t('notEmailDomain'));
}

Expand Down Expand Up @@ -365,7 +367,7 @@ const userService = {

listByRegKeyId(c, regKeyId) {
return orm(c)
.select({email: user.email,createTime: user.createTime})
.select({ email: user.email, createTime: user.createTime })
.from(user)
.where(eq(user.regKeyId, regKeyId))
.orderBy(desc(user.userId))
Expand Down
2 changes: 1 addition & 1 deletion mail-worker/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ crons = ["0 16 * * *"] #定时任务每天晚上12点执行

#[vars]
#orm_log = false
#domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"]
domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"]
#admin = "" #管理员的邮箱 示例: [email protected]
#jwt_secret = "" #jwt令牌的密钥,随便填一串字符串

Expand Down