mirror of
				https://github.com/axllent/mailpit.git
				synced 2025-10-31 00:07:43 +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:
		
							
								
								
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user