mirror of
https://github.com/axllent/mailpit.git
synced 2025-04-23 12:18:56 +02:00
Bugfix: Fix JavaScript error when adding the first tag manually
Caused when passing updated prov values via Vue components, see #68
This commit is contained in:
parent
fc753677f6
commit
2d07683a28
@ -128,9 +128,8 @@ export default {
|
|||||||
self.start = response.data.start;
|
self.start = response.data.start;
|
||||||
self.items = response.data.messages;
|
self.items = response.data.messages;
|
||||||
self.tags = response.data.tags;
|
self.tags = response.data.tags;
|
||||||
if (!self.existingTags.length) {
|
self.existingTags = JSON.parse(JSON.stringify(self.tags));
|
||||||
self.existingTags = JSON.parse(JSON.stringify(self.tags));
|
|
||||||
}
|
|
||||||
// if pagination > 0 && results == 0 reload first page (prune)
|
// if pagination > 0 && results == 0 reload first page (prune)
|
||||||
if (response.data.count == 0 && response.data.start > 0) {
|
if (response.data.count == 0 && response.data.start > 0) {
|
||||||
self.start = 0;
|
self.start = 0;
|
||||||
@ -590,13 +589,13 @@ export default {
|
|||||||
<span v-if="!total" class="ms-2">Mailpit</span>
|
<span v-if="!total" class="ms-2">Mailpit</span>
|
||||||
</a>
|
</a>
|
||||||
<div v-if="total" class="ms-md-2 d-flex bg-white border rounded-start flex-fill position-relative">
|
<div v-if="total" class="ms-md-2 d-flex bg-white border rounded-start flex-fill position-relative">
|
||||||
<input type="text" class="form-control border-0" v-model.trim="search"
|
<input type="text" class="form-control border-0" v-model.trim="search" placeholder="Search mailbox">
|
||||||
placeholder="Search mailbox">
|
|
||||||
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search"
|
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search"
|
||||||
v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<button v-if="total" class="btn btn-outline-light" type="submit"><i
|
<button v-if="total" class="btn btn-outline-light" type="submit">
|
||||||
class="bi bi-search"></i></button>
|
<i class="bi bi-search"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -626,8 +625,8 @@ export default {
|
|||||||
{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }} <small>of</small>
|
{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }} <small>of</small>
|
||||||
{{ formatNumber(total) }}
|
{{ formatNumber(total) }}
|
||||||
</small>
|
</small>
|
||||||
<button class="btn btn-outline-light ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev"
|
<button class="btn btn-outline-light ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev" v-if="!searching"
|
||||||
v-if="!searching" :title="'View previous ' + limit + ' messages'">
|
:title="'View previous ' + limit + ' messages'">
|
||||||
<i class="bi bi-caret-left-fill"></i>
|
<i class="bi bi-caret-left-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-light" :disabled="!canNext" v-on:click="viewNext" v-if="!searching"
|
<button class="btn btn-outline-light" :disabled="!canNext" v-on:click="viewNext" v-if="!searching"
|
||||||
@ -735,13 +734,13 @@ export default {
|
|||||||
<div class="text-truncate d-lg-none privacy">
|
<div class="text-truncate d-lg-none privacy">
|
||||||
<span v-if="message.From" :title="message.From.Address">{{
|
<span v-if="message.From" :title="message.From.Address">{{
|
||||||
message.From.Name ?
|
message.From.Name ?
|
||||||
message.From.Name : message.From.Address
|
message.From.Name : message.From.Address
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-truncate d-none d-lg-block privacy">
|
<div class="text-truncate d-none d-lg-block privacy">
|
||||||
<b v-if="message.From" :title="message.From.Address">{{
|
<b v-if="message.From" :title="message.From.Address">{{
|
||||||
message.From.Name ?
|
message.From.Name ?
|
||||||
message.From.Name : message.From.Address
|
message.From.Name : message.From.Address
|
||||||
}}</b>
|
}}</b>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-lg-block text-truncate text-muted small privacy">
|
<div class="d-none d-lg-block text-truncate text-muted small privacy">
|
||||||
@ -810,8 +809,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel"
|
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -882,8 +880,7 @@ export default {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki"
|
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki" target="_blank">
|
||||||
target="_blank">
|
|
||||||
Documentation
|
Documentation
|
||||||
<i class="bi bi-box-arrow-up-right"></i>
|
<i class="bi bi-box-arrow-up-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import commonMixins from '../mixins.js';
|
import commonMixins from '../mixins.js';
|
||||||
import Prism from "prismjs";
|
import Prism from "prismjs";
|
||||||
|
import Tags from "bootstrap5-tags";
|
||||||
import Attachments from './Attachments.vue';
|
import Attachments from './Attachments.vue';
|
||||||
import MessageTags from './MessageTags.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@ -12,8 +12,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Attachments,
|
Attachments
|
||||||
MessageTags
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [commonMixins],
|
mixins: [commonMixins],
|
||||||
@ -22,36 +21,54 @@ export default {
|
|||||||
return {
|
return {
|
||||||
srcURI: false,
|
srcURI: false,
|
||||||
iframes: [], // for resizing
|
iframes: [], // for resizing
|
||||||
tagComponent: false, // to force rerendering of component
|
showTags: false, // to force rerendering of component
|
||||||
|
messageTags: [],
|
||||||
|
allTags: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
message: {
|
message: {
|
||||||
handler(newQuestion) {
|
handler() {
|
||||||
let self = this;
|
let self = this;
|
||||||
self.tagComponent = false;
|
self.showTags = false;
|
||||||
|
self.messageTags = self.message.Tags;
|
||||||
|
self.allTags = self.existingTags;
|
||||||
// delay to select first tab and add HTML highlighting (prev/next)
|
// delay to select first tab and add HTML highlighting (prev/next)
|
||||||
self.$nextTick(function () {
|
self.$nextTick(function () {
|
||||||
self.renderUI();
|
self.renderUI();
|
||||||
self.tagComponent = true;
|
self.showTags = true;
|
||||||
|
self.$nextTick(function () {
|
||||||
|
Tags.init("select[multiple]");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// force eager callback execution
|
// force eager callback execution
|
||||||
immediate: true
|
immediate: true
|
||||||
|
},
|
||||||
|
messageTags() {
|
||||||
|
// save changed to tags
|
||||||
|
if (this.showTags) {
|
||||||
|
this.saveTags();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
let self = this;
|
let self = this;
|
||||||
self.tagComponent = false;
|
self.showTags = false;
|
||||||
|
self.allTags = self.existingTags;
|
||||||
window.addEventListener("resize", self.resizeIframes);
|
window.addEventListener("resize", self.resizeIframes);
|
||||||
self.renderUI();
|
self.renderUI();
|
||||||
var tabEl = document.getElementById('nav-raw-tab');
|
var tabEl = document.getElementById('nav-raw-tab');
|
||||||
tabEl.addEventListener('shown.bs.tab', function (event) {
|
tabEl.addEventListener('shown.bs.tab', function (event) {
|
||||||
self.srcURI = 'api/v1/message/' + self.message.ID + '/raw';
|
self.srcURI = 'api/v1/message/' + self.message.ID + '/raw';
|
||||||
});
|
});
|
||||||
self.tagComponent = true;
|
|
||||||
|
self.showTags = true;
|
||||||
|
self.$nextTick(function () {
|
||||||
|
Tags.init("select[multiple]");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted: function () {
|
unmounted: function () {
|
||||||
@ -105,6 +122,20 @@ export default {
|
|||||||
if (s) {
|
if (s) {
|
||||||
s.style.height = s.contentWindow.document.body.scrollHeight + 50 + 'px';
|
s.style.height = s.contentWindow.document.body.scrollHeight + 50 + 'px';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveTags: function () {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
ids: [this.message.ID],
|
||||||
|
tags: this.messageTags
|
||||||
|
}
|
||||||
|
|
||||||
|
self.put('api/v1/tags', data, function (response) {
|
||||||
|
self.scrollInPlace = true;
|
||||||
|
self.$emit('loadMessages');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,15 +193,27 @@ export default {
|
|||||||
<th class="small">Date</th>
|
<th class="small">Date</th>
|
||||||
<td>{{ messageDate(message.Date) }}</td>
|
<td>{{ messageDate(message.Date) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<MessageTags :message="message" :existingTags="existingTags"
|
|
||||||
@load-messages="$emit('loadMessages')" v-if="tagComponent">
|
<tr class="small" v-if="showTags">
|
||||||
</MessageTags>
|
<th>Tags</th>
|
||||||
|
<td>
|
||||||
|
<select class="form-select small tag-selector" v-model="messageTags" multiple
|
||||||
|
data-allow-new="true" data-clear-end="true" data-allow-clear="true"
|
||||||
|
data-placeholder="Add tags..." data-badge-style="secondary"
|
||||||
|
data-regex="^([a-zA-Z0-9\-\ \_]){3,}$" data-separator="|,|">
|
||||||
|
<option value="">Type a tag...</option>
|
||||||
|
<!-- you need at least one option with the placeholder -->
|
||||||
|
<option v-for="t in allTags" :value="t">{{ t }}</option>
|
||||||
|
</select>
|
||||||
|
<div class="invalid-feedback">Please select a valid tag.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-auto text-md-end mt-md-3">
|
<div class="col-md-auto text-md-end mt-md-3">
|
||||||
<!-- <p class="text-muted small d-none d-md-block mb-2"><small>{{ messageDate(message.Date) }}</small></p>
|
<!-- <p class="text-muted small d-none d-md-block mb-2"><small>{{ messageDate(message.Date) }}</small></p>
|
||||||
<p class="text-muted small d-none d-md-block"><small>Size: {{ getFileSize(message.Size) }}</small></p> -->
|
<p class="text-muted small d-none d-md-block"><small>Size: {{ getFileSize(message.Size) }}</small></p> -->
|
||||||
<div class="dropdown mt-2 mt-md-0" v-if="allAttachments(message)">
|
<div class="dropdown mt-2 mt-md-0" v-if="allAttachments(message)">
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||||
aria-expanded="false">
|
aria-expanded="false">
|
||||||
@ -196,8 +239,8 @@ export default {
|
|||||||
<button class="nav-link" id="nav-html-tab" data-bs-toggle="tab" data-bs-target="#nav-html" type="button"
|
<button class="nav-link" id="nav-html-tab" data-bs-toggle="tab" data-bs-target="#nav-html" type="button"
|
||||||
role="tab" aria-controls="nav-html" aria-selected="true" v-if="message.HTML">HTML</button>
|
role="tab" aria-controls="nav-html" aria-selected="true" v-if="message.HTML">HTML</button>
|
||||||
<button class="nav-link" id="nav-html-source-tab" data-bs-toggle="tab" data-bs-target="#nav-html-source"
|
<button class="nav-link" id="nav-html-source-tab" data-bs-toggle="tab" data-bs-target="#nav-html-source"
|
||||||
type="button" role="tab" aria-controls="nav-html-source" aria-selected="false"
|
type="button" role="tab" aria-controls="nav-html-source" aria-selected="false" v-if="message.HTML">HTML
|
||||||
v-if="message.HTML">HTML Source</button>
|
Source</button>
|
||||||
<button class="nav-link" id="nav-plain-text-tab" data-bs-toggle="tab" data-bs-target="#nav-plain-text"
|
<button class="nav-link" id="nav-plain-text-tab" data-bs-toggle="tab" data-bs-target="#nav-plain-text"
|
||||||
type="button" role="tab" aria-controls="nav-plain-text" aria-selected="false"
|
type="button" role="tab" aria-controls="nav-plain-text" aria-selected="false"
|
||||||
:class="message.HTML == '' ? 'show' : ''">Text</button>
|
:class="message.HTML == '' ? 'show' : ''">Text</button>
|
||||||
@ -208,8 +251,8 @@ export default {
|
|||||||
<div class="tab-content mb-5" id="nav-tabContent">
|
<div class="tab-content mb-5" id="nav-tabContent">
|
||||||
<div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel"
|
<div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel"
|
||||||
aria-labelledby="nav-html-tab" tabindex="0">
|
aria-labelledby="nav-html-tab" tabindex="0">
|
||||||
<iframe target-blank="" class="tab-pane" id="preview-html" :srcdoc="message.HTML"
|
<iframe target-blank="" class="tab-pane" id="preview-html" :srcdoc="message.HTML" v-on:load="resizeIframe"
|
||||||
v-on:load="resizeIframe" seamless frameborder="0" style="width: 100%; height: 100%;">
|
seamless frameborder="0" style="width: 100%; height: 100%;">
|
||||||
</iframe>
|
</iframe>
|
||||||
<Attachments v-if="allAttachments(message).length" :message="message"
|
<Attachments v-if="allAttachments(message).length" :message="message"
|
||||||
:attachments="allAttachments(message)"></Attachments>
|
:attachments="allAttachments(message)"></Attachments>
|
||||||
@ -218,8 +261,8 @@ export default {
|
|||||||
tabindex="0" v-if="message.HTML">
|
tabindex="0" v-if="message.HTML">
|
||||||
<pre><code class="language-html">{{ message.HTML }}</code></pre>
|
<pre><code class="language-html">{{ message.HTML }}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel" aria-labelledby="nav-plain-text-tab"
|
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel" aria-labelledby="nav-plain-text-tab" tabindex="0"
|
||||||
tabindex="0" :class="message.HTML == '' ? 'show' : ''">
|
:class="message.HTML == '' ? 'show' : ''">
|
||||||
<div class="text-view">{{ message.Text }}</div>
|
<div class="text-view">{{ message.Text }}</div>
|
||||||
<Attachments v-if="allAttachments(message).length" :message="message"
|
<Attachments v-if="allAttachments(message).length" :message="message"
|
||||||
:attachments="allAttachments(message)"></Attachments>
|
:attachments="allAttachments(message)"></Attachments>
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
|
|
||||||
<script>
|
|
||||||
import commonMixins from '../mixins.js';
|
|
||||||
import Tags from "bootstrap5-tags";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: Object,
|
|
||||||
existingTags: Array
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [commonMixins],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
messageTags: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
let self = this;
|
|
||||||
self.loaded = false;
|
|
||||||
self.messageTags = self.message.Tags;
|
|
||||||
// delay until vue has rendered
|
|
||||||
self.$nextTick(function () {
|
|
||||||
Tags.init("select[multiple]");
|
|
||||||
self.$nextTick(function () {
|
|
||||||
self.loaded = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
messageTags() {
|
|
||||||
if (this.loaded) {
|
|
||||||
this.saveTags();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
saveTags: function () {
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
ids: [this.message.ID],
|
|
||||||
tags: this.messageTags
|
|
||||||
}
|
|
||||||
|
|
||||||
self.put('api/v1/tags', data, function (response) {
|
|
||||||
self.scrollInPlace = true;
|
|
||||||
self.$emit('loadMessages');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<tr class="small">
|
|
||||||
<th>Tags</th>
|
|
||||||
<td>
|
|
||||||
<select class="form-select small tag-selector" v-model="messageTags" multiple data-allow-new="true"
|
|
||||||
data-clear-end="true" data-allow-clear="true" data-placeholder="Add tags..."
|
|
||||||
data-badge-style="secondary" data-regex="^([a-zA-Z0-9\-\ \_]){3,}$" data-separator="|,|">
|
|
||||||
<option value="">Type a tag...</option><!-- you need at least one option with the placeholder -->
|
|
||||||
<option v-for="t in existingTags" :value="t">{{ t }}</option>
|
|
||||||
</select>
|
|
||||||
<div class="invalid-feedback">Please select a valid tag.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
Loading…
x
Reference in New Issue
Block a user