1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-02-03 13:12:03 +02:00

UI: Add option to enable tag colors based on tag name hash

An experimental option to add tag colors (see #127). This will generate a random color for each unique tag
This commit is contained in:
Ralph Slooten 2023-06-14 22:18:51 +12:00
parent ff9a6ff491
commit fc89655b7f
5 changed files with 74 additions and 20 deletions

6
package-lock.json generated
View File

@ -12,6 +12,7 @@
"bootstrap": "^5.2.0",
"bootstrap-icons": "^1.9.1",
"bootstrap5-tags": "^1.4.41",
"color-hash": "^2.0.2",
"moment": "^2.29.4",
"prismjs": "^1.29.0",
"rapidoc": "^9.3.4",
@ -1163,6 +1164,11 @@
"node": ">=0.10.0"
}
},
"node_modules/color-hash": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/color-hash/-/color-hash-2.0.2.tgz",
"integrity": "sha512-6exeENAqBTuIR1wIo36mR8xVVBv6l1hSLd7Qmvf6158Ld1L15/dbahR9VUOiX7GmGJBCnQyS0EY+I8x+wa7egg=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",

View File

@ -12,6 +12,7 @@
"bootstrap": "^5.2.0",
"bootstrap-icons": "^1.9.1",
"bootstrap5-tags": "^1.4.41",
"color-hash": "^2.0.2",
"moment": "^2.29.4",
"prismjs": "^1.29.0",
"rapidoc": "^9.3.4",

View File

@ -1,11 +1,11 @@
<script>
import commonMixins from './mixins.js';
import Message from './templates/Message.vue';
import MessageSummary from './templates/MessageSummary.vue';
import MessageRelease from './templates/MessageRelease.vue';
import MessageToast from './templates/MessageToast.vue';
import moment from 'moment';
import Tinycon from 'tinycon';
import commonMixins from './mixins.js'
import Message from './templates/Message.vue'
import MessageSummary from './templates/MessageSummary.vue'
import MessageRelease from './templates/MessageRelease.vue'
import MessageToast from './templates/MessageToast.vue'
import moment from 'moment'
import Tinycon from 'tinycon'
export default {
mixins: [commonMixins],
@ -906,9 +906,23 @@ export default {
</div>
<template v-if="!selected.length && tags.length && !message">
<h6 class="mt-4 text-muted"><small>Tags</small></h6>
<div class="list-group mt-2 mb-5">
<button class="list-group-item list-group-item-action small" v-for="tag in tags"
<div class="mt-4 text-muted">
<button class="btn btn-sm dropdown-toggle ms-n1" data-bs-toggle="dropdown" aria-expanded="false">
Tags
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button class="dropdown-item" @click="toggleTagColors()">
<template v-if="showTagColors">Hide</template>
<template v-else>Show</template>
tag colors
</button>
</li>
</ul>
</div>
<div class="list-group mt-1 mb-5">
<button class="list-group-item list-group-item-action small px-2" v-for="tag in tags"
:style="showTagColors ? { borderLeftColor: colorHash(tag), borderLeftWidth: '4px' } : ''"
v-on:click="tagSearch($event, tag)" :class="inSearch(tag) ? 'active' : ''">
<i class="bi bi-tag-fill" v-if="inSearch(tag)"></i>
<i class="bi bi-tag" v-else></i>
@ -919,7 +933,7 @@ export default {
<MessageSummary v-if="message" :message="message"></MessageSummary>
<div class="position-fixed bottom-0 bg-white py-2 text-muted w-100">
<div class="position-fixed bottom-0 bg-white py-2 text-muted small w-100">
<a href="#" class="text-muted" v-on:click="loadInfo">
<i class="bi bi-info-circle-fill"></i>
About
@ -927,7 +941,7 @@ export default {
</div>
</div>
<div class="col-lg-10 col-md-9 mh-100 ps-0 pe-0">
<div class="col-lg-10 col-md-9 mh-100 ps-0 ps-md-2 pe-0">
<div class="mh-100" style="overflow-y: auto;" :class="message ? 'd-none' : ''" id="message-page">
<div class="list-group my-2" v-if="items.length">
<a v-for="message in items" :href="'#' + message.ID"
@ -962,7 +976,8 @@ export default {
<div class="col-lg-6 col-xxl-7 mt-2 mt-lg-0">
<div><b>{{ message.Subject != "" ? message.Subject : "[ no subject ]" }}</b></div>
<div>
<span class="badge text-bg-secondary me-1" v-for="t in message.Tags"
<span class="badge me-1" v-for="t in message.Tags"
:style="showTagColors ? { backgroundColor: colorHash(t) } : { backgroundColor: '#6c757d' }"
:title="'Filter messages tagged with ' + t" v-on:click="tagSearch($event, t)">
{{ t }}
</span>

View File

@ -5,3 +5,4 @@ $font-family-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetic
$link-decoration: none;
$primary: #2c3e50;
$list-group-disabled-color: #adb5bd;
$enable-negative-margins: true;

View File

@ -1,22 +1,32 @@
import axios from 'axios';
import { Modal } from 'bootstrap';
import moment from 'moment';
import axios from 'axios'
import { Modal } from 'bootstrap'
import moment from 'moment'
import ColorHash from 'color-hash'
// FakeModal is used to return a fake Bootstrap modal
// if the ID returns nothing
// if the ID returns nothing to prevent errors.
function FakeModal() { }
FakeModal.prototype.hide = function () { alert('close fake modal') }
FakeModal.prototype.show = function () { alert('open fake modal') }
FakeModal.prototype.hide = function () { }
FakeModal.prototype.show = function () { }
// Set up the color hash generator lightness and hue to ensure darker colors
const colorHash = new ColorHash({ lightness: 0.3, saturation: [0.35, 0.5, 0.65] });
/* Common mixin functions used in apps */
const commonMixins = {
data() {
return {
loading: 0
loading: 0,
tagColorCache: {},
showTagColors: true
}
},
mounted() {
this.showTagColors = localStorage.getItem('showTagsColors')
},
methods: {
getFileSize: function (bytes) {
var i = Math.floor(Math.log(bytes) / Math.log(1024));
@ -201,6 +211,27 @@ const commonMixins = {
}
return 'bi-file-arrow-down-fill';
},
// Returns a hex color based on a string.
// Values are stored in an array for faster lookup / processing.
colorHash: function (s) {
if (this.tagColorCache[s] != undefined) {
return this.tagColorCache[s]
}
this.tagColorCache[s] = colorHash.hex(s)
return this.tagColorCache[s]
},
toggleTagColors: function () {
if (this.showTagColors) {
localStorage.removeItem('showTagsColors')
this.showTagColors = false
} else {
localStorage.setItem('showTagsColors', '1')
this.showTagColors = true
}
}
}
}