1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-03-17 21:18:19 +02:00

Feature: Add UI settings screen

This commit is contained in:
Ralph Slooten 2024-04-13 00:25:04 +12:00
parent a05e4fd48f
commit faded05e47
10 changed files with 251 additions and 185 deletions

6
package-lock.json generated
View File

@ -17,6 +17,7 @@
"moment": "^2.29.4", "moment": "^2.29.4",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"timezones-list": "^3.0.3",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-css-donut-chart": "^2.0.0", "vue-css-donut-chart": "^2.0.0",
"vue-router": "^4.2.4" "vue-router": "^4.2.4"
@ -3559,6 +3560,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/timezones-list": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/timezones-list/-/timezones-list-3.0.3.tgz",
"integrity": "sha512-C+Vdvvj2c1xB6pu81pOX8geo6mrk/QsudFVlTVQET7QQwu8WAIyhDNeCrK5grU7EMzmbKLWqz7uU6dN8fvQvPQ=="
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",

View File

@ -18,6 +18,7 @@
"moment": "^2.29.4", "moment": "^2.29.4",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"timezones-list": "^3.0.3",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-css-donut-chart": "^2.0.0", "vue-css-donut-chart": "^2.0.0",
"vue-router": "^4.2.4" "vue-router": "^4.2.4"

View File

@ -15,8 +15,6 @@ export default {
beforeMount() { beforeMount() {
document.title = document.title + ' - ' + location.hostname document.title = document.title + ' - ' + location.hostname
mailbox.showTagColors = !localStorage.getItem('hideTagColors') == '1'
mailbox.timeZone = localStorage.getItem('timezone') ? localStorage.getItem('timezone') : Intl.DateTimeFormat().resolvedOptions().timeZone;
// load global config // load global config
this.get(this.resolve('/api/v1/webui'), false, function (response) { this.get(this.resolve('/api/v1/webui'), false, function (response) {

View File

@ -320,16 +320,18 @@ body.blur {
display: none; display: none;
} }
.form-control.dropdown { #message-view {
padding: 0; .form-control.dropdown {
border: 0; padding: 0;
border: 0;
input { input {
font-size: 0.875em; font-size: 0.875em;
} }
div { div {
cursor: text; // html5-tags cursor: text; // html5-tags
}
} }
} }

View File

@ -1,5 +1,6 @@
<script> <script>
import AjaxLoader from './AjaxLoader.vue' import AjaxLoader from './AjaxLoader.vue'
import Settings from '../components/Settings.vue'
import CommonMixins from '../mixins/CommonMixins' import CommonMixins from '../mixins/CommonMixins'
import { mailbox } from '../stores/mailbox' import { mailbox } from '../stores/mailbox'
@ -7,7 +8,8 @@ export default {
mixins: [CommonMixins], mixins: [CommonMixins],
components: { components: {
AjaxLoader AjaxLoader,
Settings,
}, },
props: { props: {
@ -20,20 +22,9 @@ export default {
data() { data() {
return { return {
mailbox, mailbox,
theme: 'auto',
icon: 'circle-half',
icons: {
'auto': 'circle-half',
'light': 'sun-fill',
'dark': 'moon-stars-fill'
},
} }
}, },
mounted() {
this.setTheme(this.getPreferredTheme())
},
methods: { methods: {
loadInfo: function () { loadInfo: function () {
let self = this let self = this
@ -43,42 +34,6 @@ export default {
}) })
}, },
getStoredTheme: function () {
let theme = localStorage.getItem('theme')
if (!theme) {
theme = 'auto'
}
return theme
},
setStoredTheme: function (theme) {
localStorage.setItem('theme', theme)
this.setTheme(theme)
},
getPreferredTheme: function () {
const storedTheme = this.getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
},
setTheme: function (theme) {
this.icon = this.icons[theme]
this.theme = theme
if (
theme === 'auto' &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
},
requestNotifications: function () { requestNotifications: function () {
// check if the browser supports notifications // check if the browser supports notifications
if (!("Notification" in window)) { if (!("Notification" in window)) {
@ -101,42 +56,17 @@ export default {
<template> <template>
<template v-if="!modals"> <template v-if="!modals">
<div class="position-fixed bg-body bottom-0 ms-n1 py-2 text-muted small col-xl-2 col-md-3 pe-3 z-3 about-mailpit"> <div
class="position-fixed bg-body bottom-0 ms-n1 py-2 text-muted small col-xl-2 col-md-3 pe-3 z-3 about-mailpit">
<button class="text-muted btn btn-sm" v-on:click="loadInfo"> <button class="text-muted btn btn-sm" v-on:click="loadInfo">
<i class="bi bi-info-circle-fill me-1"></i> <i class="bi bi-info-circle-fill me-1"></i>
About About
</button> </button>
<div class="dropdown bd-mode-toggle float-end me-2 d-inline-block"> <button class="btn btn-sm btn-outline-secondary float-end me-2" data-bs-toggle="modal"
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" aria-expanded="false" data-bs-target="#SettingsModal" title="Mailpit UI settings">
title="Toggle theme" data-bs-toggle="dropdown" aria-label="Toggle theme"> <i class="bi bi-gear-fill"></i>
<i :class="'bi bi-' + icon + ' my-1'"></i> </button>
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
<li>
<button type="button" class="dropdown-item d-flex align-items-center"
:class="theme == 'light' ? 'active' : ''" @click="setStoredTheme('light')">
<i class="bi bi-sun-fill me-2 opacity-50"></i>
Light
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center"
:class="theme == 'dark' ? 'active' : ''" @click="setStoredTheme('dark')">
<i class="bi bi-moon-stars-fill me-2 opacity-50"></i>
Dark
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center"
:class="theme == 'auto' ? 'active' : ''" @click="setStoredTheme('auto')">
<i class="bi bi-circle-half me-2 opacity-50"></i>
Auto
</button>
</li>
</ul>
</div>
<button class="btn btn-sm btn-outline-secondary float-end me-2" data-bs-toggle="modal" <button class="btn btn-sm btn-outline-secondary float-end me-2" data-bs-toggle="modal"
data-bs-target="#EnableNotificationsModal" title="Enable browser notifications" data-bs-target="#EnableNotificationsModal" title="Enable browser notifications"
@ -167,7 +97,8 @@ export default {
There might be a newer version available. The check failed. There might be a newer version available. The check failed.
</div> </div>
</div> </div>
<div class="row g-3" v-else-if="mailbox.appInfo.Version != mailbox.appInfo.LatestVersion"> <div class="row g-3"
v-else-if="mailbox.appInfo.Version != mailbox.appInfo.LatestVersion">
<a class="btn btn-warning d-block mb-3" <a class="btn btn-warning d-block mb-3"
:href="'https://github.com/axllent/mailpit/releases/tag/' + mailbox.appInfo.LatestVersion"> :href="'https://github.com/axllent/mailpit/releases/tag/' + mailbox.appInfo.LatestVersion">
A new version of Mailpit ({{ mailbox.appInfo.LatestVersion }}) is available. A new version of Mailpit ({{ mailbox.appInfo.LatestVersion }}) is available.
@ -197,7 +128,8 @@ export default {
<div class="card border-secondary text-center"> <div class="card border-secondary text-center">
<div class="card-header">Database size</div> <div class="card-header">Database size</div>
<div class="card-body text-secondary"> <div class="card-body text-secondary">
<h5 class="card-title">{{ getFileSize(mailbox.appInfo.DatabaseSize) }} </h5> <h5 class="card-title">{{ getFileSize(mailbox.appInfo.DatabaseSize) }}
</h5>
</div> </div>
</div> </div>
</div> </div>
@ -205,8 +137,9 @@ export default {
<div class="card border-secondary text-center"> <div class="card border-secondary text-center">
<div class="card-header">RAM usage</div> <div class="card-header">RAM usage</div>
<div class="card-body text-secondary"> <div class="card-body text-secondary">
<h5 class="card-title">{{ getFileSize(mailbox.appInfo.RuntimeStats.Memory) <h5 class="card-title">
}} </h5> {{ getFileSize(mailbox.appInfo.RuntimeStats.Memory) }}
</h5>
</div> </div>
</div> </div>
</div> </div>
@ -216,7 +149,8 @@ export default {
<div class="card border-secondary"> <div class="card border-secondary">
<div class="card-header h4"> <div class="card-header h4">
Runtime statistics Runtime statistics
<button class="btn btn-sm btn-outline-secondary float-end" v-on:click="loadInfo"> <button class="btn btn-sm btn-outline-secondary float-end"
v-on:click="loadInfo">
Refresh Refresh
</button> </button>
</div> </div>
@ -246,9 +180,7 @@ export default {
<td> <td>
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }} {{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }}
<small class="text-secondary"> <small class="text-secondary">
({{ ({{ getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize) }})
getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize)
}})
</small> </small>
</td> </td>
</tr> </tr>
@ -285,8 +217,8 @@ export default {
</div> </div>
</div> </div>
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel" <div class="modal fade" id="EnableNotificationsModal" tabindex="-1"
aria-hidden="true"> aria-labelledby="EnableNotificationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -298,7 +230,8 @@ export default {
<p> <p>
Note that your browser will ask you for confirmation when you click Note that your browser will ask you for confirmation when you click
<code>enable notifications</code>, <code>enable notifications</code>,
and that you must have Mailpit open in a browser tab to be able to receive the notifications. and that you must have Mailpit open in a browser tab to be able to receive the
notifications.
</p> </p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -309,6 +242,8 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<Settings />
</template> </template>
<AjaxLoader :loading="loading" /> <AjaxLoader :loading="loading" />

View File

@ -0,0 +1,119 @@
<script>
import CommonMixins from '../mixins/CommonMixins'
import Tags from 'bootstrap5-tags'
import timezones from 'timezones-list'
import { mailbox } from '../stores/mailbox'
export default {
mixins: [CommonMixins],
data() {
return {
mailbox,
theme: localStorage.getItem('theme') ? localStorage.getItem('theme') : 'auto',
timezones,
}
},
watch: {
theme: function(v) {
if (v == 'auto') {
localStorage.removeItem('theme')
} else {
localStorage.setItem('theme', v)
}
this.setTheme()
}
},
mounted() {
this.setTheme()
this.$nextTick(function () {
Tags.init('select.tz')
})
},
methods: {
setTheme: function () {
if (
this.theme === 'auto' &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
document.documentElement.setAttribute('data-bs-theme', 'dark')
} else {
document.documentElement.setAttribute('data-bs-theme', this.theme)
}
},
}
}
</script>
<template>
<div class="modal fade" id="SettingsModal" tabindex="-1" aria-labelledby="SettingsModalLabel" aria-hidden="true"
data-bs-keyboard="false">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="SettingsModalLabel">Mailpit UI settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="theme" class="form-label">Mailpit theme</label>
<select class="form-select" v-model="theme" id="theme">
<option value="auto">Auto (detect from browser)</option>
<option value="light">Light theme</option>
<option value="dark">Dark theme</option>
</select>
</div>
<div class="mb-3">
<label for="timezone" class="form-label">Timezone (for date searches)</label>
<select class="form-select tz" v-model="mailbox.timeZone" id="timezone">
<option disabled hidden value="">Select a timezone...</option>
<option v-for="t in timezones" :value="t.tzCode">{{ t.label }}</option>
</select>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="tagColors"
v-model="mailbox.showTagColors">
<label class="form-check-label" for="tagColors">
Use auto-generated tag colors
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="htmlCheck"
v-model="mailbox.showHTMLCheck">
<label class="form-check-label" for="htmlCheck">
Show HTML check message tab
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="linkCheck"
v-model="mailbox.showLinkCheck">
<label class="form-check-label" for="linkCheck">
Show link check message tab
</label>
</div>
</div>
<div class="mb-3" v-if="mailbox.uiConfig.SpamAssassin">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="spamCheck"
v-model="mailbox.showSpamCheck">
<label class="form-check-label" for="spamCheck">
Show spam check message tab
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>

View File

@ -20,7 +20,6 @@ export default {
data() { data() {
return { return {
error: false, error: false,
enabled: true,
check: false, check: false,
platforms: [], platforms: [],
allPlatforms: { allPlatforms: {
@ -37,7 +36,6 @@ export default {
}, },
mounted() { mounted() {
this.enabled = !localStorage.getItem('htmlCheckDisabled')
this.loadConfig() this.loadConfig()
this.doCheck() this.doCheck()
}, },
@ -46,7 +44,7 @@ export default {
summary: function () { summary: function () {
let self = this let self = this
if (!this.enabled || !this.check) { if (!this.check) {
return false return false
} }
@ -199,23 +197,19 @@ export default {
platforms(v) { platforms(v) {
localStorage.setItem('html-check-platforms', JSON.stringify(v)) localStorage.setItem('html-check-platforms', JSON.stringify(v))
}, },
enabled(v) { // enabled(v) {
if (!v) { // if (!v) {
localStorage.setItem('htmlCheckDisabled', true) // localStorage.setItem('htmlCheckDisabled', true)
this.$emit('setHtmlScore', false) // this.$emit('setHtmlScore', false)
} else { // } else {
localStorage.removeItem('htmlCheckDisabled') // localStorage.removeItem('htmlCheckDisabled')
this.doCheck() // this.doCheck()
} // }
} // }
}, },
methods: { methods: {
doCheck: function () { doCheck: function () {
if (!this.enabled) {
return
}
this.check = false this.check = false
if (this.message.HTML == "") { if (this.message.HTML == "") {
@ -314,20 +308,6 @@ export default {
</div> </div>
</template> </template>
<template v-if="!enabled">
<h2 class="h4 text-secondary">HTML check is currently disabled</h2>
<p class="text-secondary">
This feature is currently in beta. Constructive feedback is welcome via
<a href="https://github.com/axllent/mailpit/issues" target="_blank">GitHub</a>.
</p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" v-model="enabled" id="inlineEnableHTMLCheck">
<label class="form-check-label" for="inlineEnableHTMLCheck">
Enable HTML check
</label>
</div>
</template>
<template v-if="summary"> <template v-if="summary">
<div class="mt-5 mb-3"> <div class="mt-5 mb-3">
<div class="row w-100"> <div class="row w-100">
@ -368,10 +348,6 @@ export default {
<i class="bi bi-info-circle-fill"></i> <i class="bi bi-info-circle-fill"></i>
Help Help
</button> </button>
<button class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#HTMLCheckOptions">
<i class="bi bi-gear-fill"></i>
Settings
</button>
</div> </div>
</div> </div>
<div class="col-md"> <div class="col-md">
@ -635,36 +611,4 @@ export default {
</div> </div>
</div> </div>
</template> </template>
<div class="modal fade" id="HTMLCheckOptions" tabindex="-1" aria-labelledby="HTMLCheckOptionsLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="HTMLCheckOptionsLabel">HTML check options</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>
HTML check is currently in beta. Constructive feedback is welcome via
<a href="https://github.com/axllent/mailpit/issues" target="_blank">GitHub</a>.
</p>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" v-model="enabled"
id="HTMLCheckSwitch">
<label class="form-check-label" for="HTMLCheckSwitch">
<template v-if="enabled">HTML check is enabled in the web UI</template>
<template v-else>HTML check is disabled in the web UI</template>
</label>
</div>
<p class="mt-4 small text-center text-secondary">
HTML check can be globally disabled with <code>--disable-html-check</code><br>
Remote CSS and font support can be globally blocked with <code>--block-remote-css-and-fonts</code>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template> </template>

View File

@ -69,6 +69,14 @@ export default {
} }
}, },
computed: {
hasAnyChecksEnabled: function() {
return (mailbox.showHTMLCheck && this.message.HTML)
|| mailbox.showLinkCheck
|| (mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin)
}
},
mounted() { mounted() {
let self = this let self = this
self.canSaveTags = false self.canSaveTags = false
@ -263,7 +271,7 @@ export default {
<tr class="small"> <tr class="small">
<th>To</th> <th>To</th>
<td class="privacy"> <td class="privacy">
<span v-if="message.To && message.To.length" v-for="( t, i ) in message.To "> <span v-if="message.To && message.To.length" v-for="(t, i) in message.To">
<template v-if="i > 0">, </template> <template v-if="i > 0">, </template>
<span> <span>
<span class="text-spaces">{{ t.Name }}</span> <span class="text-spaces">{{ t.Name }}</span>
@ -278,7 +286,7 @@ export default {
<tr v-if="message.Cc && message.Cc.length" class="small"> <tr v-if="message.Cc && message.Cc.length" class="small">
<th>Cc</th> <th>Cc</th>
<td class="privacy"> <td class="privacy">
<span v-for="( t, i ) in message.Cc "> <span v-for="(t, i) in message.Cc">
<template v-if="i > 0">,</template> <template v-if="i > 0">,</template>
<span class="text-spaces">{{ t.Name }}</span> <span class="text-spaces">{{ t.Name }}</span>
&lt;<a :href="searchURI(t.Address)" class="text-body"> &lt;<a :href="searchURI(t.Address)" class="text-body">
@ -423,16 +431,16 @@ export default {
role="tab" aria-controls="nav-raw" aria-selected="false"> role="tab" aria-controls="nav-raw" aria-selected="false">
Raw Raw
</button> </button>
<div class="dropdown d-xl-none"> <div class="dropdown d-xl-none" v-show="hasAnyChecksEnabled">
<button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown" <button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown"
aria-expanded="false"> aria-expanded="false">
Checks Checks
</button> </button>
<ul class="dropdown-menu checks"> <ul class="dropdown-menu checks">
<li> <li v-if="mailbox.showHTMLCheck && message.HTML != ''">
<button class="dropdown-item" id="nav-html-check-tab" data-bs-toggle="tab" <button class="dropdown-item" id="nav-html-check-tab" data-bs-toggle="tab"
data-bs-target="#nav-html-check" type="button" role="tab" aria-controls="nav-html" data-bs-target="#nav-html-check" type="button" role="tab" aria-controls="nav-html"
aria-selected="false" v-if="!mailbox.uiConfig.DisableHTMLCheck && message.HTML != ''"> aria-selected="false">
HTML Check HTML Check
<span class="badge rounded-pill p-1 float-end" :class="htmlScoreColor" <span class="badge rounded-pill p-1 float-end" :class="htmlScoreColor"
v-if="htmlScore !== false"> v-if="htmlScore !== false">
@ -440,7 +448,7 @@ export default {
</span> </span>
</button> </button>
</li> </li>
<li> <li v-if="mailbox.showLinkCheck">
<button class="dropdown-item" id="nav-link-check-tab" data-bs-toggle="tab" <button class="dropdown-item" id="nav-link-check-tab" data-bs-toggle="tab"
data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check" data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check"
aria-selected="false"> aria-selected="false">
@ -453,7 +461,7 @@ export default {
</span> </span>
</button> </button>
</li> </li>
<li v-if="mailbox.uiConfig.SpamAssassin"> <li v-if="mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin">
<button class="dropdown-item" id="nav-spam-check-tab" data-bs-toggle="tab" <button class="dropdown-item" id="nav-spam-check-tab" data-bs-toggle="tab"
data-bs-target="#nav-spam-check" type="button" role="tab" aria-controls="nav-html" data-bs-target="#nav-spam-check" type="button" role="tab" aria-controls="nav-html"
aria-selected="false"> aria-selected="false">
@ -469,7 +477,7 @@ export default {
<button class="d-none d-xl-inline-block nav-link position-relative" id="nav-html-check-tab" <button class="d-none d-xl-inline-block nav-link position-relative" id="nav-html-check-tab"
data-bs-toggle="tab" data-bs-target="#nav-html-check" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#nav-html-check" type="button" role="tab"
aria-controls="nav-html" aria-selected="false" aria-controls="nav-html" aria-selected="false"
v-if="!mailbox.uiConfig.DisableHTMLCheck && message.HTML != ''"> v-if="mailbox.showHTMLCheck && message.HTML != ''">
HTML Check HTML Check
<span class="badge rounded-pill p-1" :class="htmlScoreColor" v-if="htmlScore !== false"> <span class="badge rounded-pill p-1" :class="htmlScoreColor" v-if="htmlScore !== false">
<small>{{ Math.floor(htmlScore) }}%</small> <small>{{ Math.floor(htmlScore) }}%</small>
@ -477,7 +485,7 @@ export default {
</button> </button>
<button class="d-none d-xl-inline-block nav-link" id="nav-link-check-tab" data-bs-toggle="tab" <button class="d-none d-xl-inline-block nav-link" id="nav-link-check-tab" data-bs-toggle="tab"
data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check" data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check"
aria-selected="false"> aria-selected="false" v-if="mailbox.showLinkCheck">
Link Check Link Check
<i class="bi bi-check-all text-success" v-if="linkCheckErrors === 0"></i> <i class="bi bi-check-all text-success" v-if="linkCheckErrors === 0"></i>
<span class="badge rounded-pill bg-danger" v-else-if="linkCheckErrors > 0"> <span class="badge rounded-pill bg-danger" v-else-if="linkCheckErrors > 0">
@ -486,7 +494,7 @@ export default {
</button> </button>
<button class="d-none d-xl-inline-block nav-link position-relative" id="nav-spam-check-tab" <button class="d-none d-xl-inline-block nav-link position-relative" id="nav-spam-check-tab"
data-bs-toggle="tab" data-bs-target="#nav-spam-check" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#nav-spam-check" type="button" role="tab"
aria-controls="nav-html" aria-selected="false" v-if="mailbox.uiConfig.SpamAssassin"> aria-controls="nav-html" aria-selected="false" v-if="mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin">
Spam Analysis Spam Analysis
<span class="badge rounded-pill" :class="spamScoreColor" v-if="spamScore !== false"> <span class="badge rounded-pill" :class="spamScoreColor" v-if="spamScore !== false">
<small>{{ spamScore }}</small> <small>{{ spamScore }}</small>
@ -537,16 +545,16 @@ export default {
</div> </div>
<div class="tab-pane fade" id="nav-html-check" role="tabpanel" aria-labelledby="nav-html-check-tab" <div class="tab-pane fade" id="nav-html-check" role="tabpanel" aria-labelledby="nav-html-check-tab"
tabindex="0"> tabindex="0">
<HTMLCheck v-if="!mailbox.uiConfig.DisableHTMLCheck && message.HTML != ''" :message="message" <HTMLCheck v-if="mailbox.showHTMLCheck && message.HTML != ''"
@setHtmlScore="(n) => htmlScore = n" @set-badge-style="(v) => htmlScoreColor = v" /> :message="message" @setHtmlScore="(n) => htmlScore = n" @set-badge-style="(v) => htmlScoreColor = v" />
</div> </div>
<div class="tab-pane fade" id="nav-spam-check" role="tabpanel" aria-labelledby="nav-spam-check-tab" <div class="tab-pane fade" id="nav-spam-check" role="tabpanel" aria-labelledby="nav-spam-check-tab"
tabindex="0"> tabindex="0" v-if="mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin">
<SpamAssassin v-if="mailbox.uiConfig.SpamAssassin" :message="message" <SpamAssassin :message="message"
@setSpamScore="(n) => spamScore = n" @set-badge-style="(v) => spamScoreColor = v" /> @setSpamScore="(n) => spamScore = n" @set-badge-style="(v) => spamScoreColor = v" />
</div> </div>
<div class="tab-pane fade" id="nav-link-check" role="tabpanel" aria-labelledby="nav-html-check-tab" <div class="tab-pane fade" id="nav-link-check" role="tabpanel" aria-labelledby="nav-html-check-tab"
tabindex="0"> tabindex="0" v-if="mailbox.showLinkCheck">
<LinkCheck :message="message" @setLinkErrors="(n) => linkCheckErrors = n" /> <LinkCheck :message="message" @setLinkErrors="(n) => linkCheckErrors = n" />
</div> </div>
</div> </div>

View File

@ -122,7 +122,11 @@ export default {
value: p, value: p,
color: c color: c
}, },
]; ]
},
scoreColor: function() {
return this.graphSections[0].color
}, },
} }
} }

View File

@ -9,7 +9,6 @@ export const mailbox = reactive({
count: 0, // total in mailbox or search count: 0, // total in mailbox or search
messages: [], // current messages messages: [], // current messages
tags: [], // all tags tags: [], // all tags
showTagColors: true, // show/hide tag colors
selected: [], // currently selected selected: [], // currently selected
connected: false, // websocket connection connected: false, // websocket connection
searching: false, // current search, false for none searching: false, // current search, false for none
@ -19,7 +18,13 @@ export const mailbox = reactive({
appInfo: {}, // application information appInfo: {}, // application information
uiConfig: {}, // configuration for UI uiConfig: {}, // configuration for UI
lastMessage: false, // return scrolling lastMessage: false, // return scrolling
timeZone: '', // browser timezone
// settings
showTagColors: !localStorage.getItem('hideTagColors') == '1',
showHTMLCheck: !localStorage.getItem('hideHTMLCheck') == '1',
showLinkCheck: !localStorage.getItem('hideLinkCheck') == '1',
showSpamCheck: !localStorage.getItem('hideSpamCheck') == '1',
timeZone: localStorage.getItem('timeZone') ? localStorage.getItem('timeZone') : Intl.DateTimeFormat().resolvedOptions().timeZone,
}) })
watch( watch(
@ -39,3 +44,47 @@ watch(
} }
} }
) )
watch(
() => mailbox.showHTMLCheck,
(v) => {
if (v) {
localStorage.removeItem('hideHTMLCheck')
} else {
localStorage.setItem('hideHTMLCheck', '1')
}
}
)
watch(
() => mailbox.showLinkCheck,
(v) => {
if (v) {
localStorage.removeItem('hideLinkCheck')
} else {
localStorage.setItem('hideLinkCheck', '1')
}
}
)
watch(
() => mailbox.showSpamCheck,
(v) => {
if (v) {
localStorage.removeItem('hideSpamCheck')
} else {
localStorage.setItem('hideSpamCheck', '1')
}
}
)
watch(
() => mailbox.timeZone,
(v) => {
if (v == Intl.DateTimeFormat().resolvedOptions().timeZone) {
localStorage.removeItem('timeZone')
} else {
localStorage.setItem('timeZone', v)
}
}
)