<script> import commonMixins from '../mixins.js'; import Prism from "prismjs"; import Tags from "bootstrap5-tags"; import Attachments from './Attachments.vue'; import Headers from './Headers.vue'; export default { props: { message: Object, existingTags: Array }, components: { Attachments, Headers }, mixins: [commonMixins], data() { return { srcURI: false, iframes: [], // for resizing showTags: false, // to force rerendering of component messageTags: [], allTags: [], loadHeaders: false, showMobileBtns: false, scaleHTMLPreview: 'display', // keys names match bootstrap icon names responsiveSizes: { phone: 'width: 322px; height: 570px', tablet: 'width: 768px; height: 1024px', display: 'width: 100%; height: 100%', }, } }, watch: { message: { handler() { let self = this; self.showTags = false; self.messageTags = self.message.Tags; self.allTags = self.existingTags; self.loadHeaders = false; self.scaleHTMLPreview = 'display';// default view // delay to select first tab and add HTML highlighting (prev/next) self.$nextTick(function () { self.renderUI(); self.showTags = true; self.$nextTick(function () { Tags.init("select[multiple]"); }); }); }, // force eager callback execution immediate: true }, messageTags() { // save changed to tags if (this.showTags) { this.saveTags(); } }, scaleHTMLPreview() { if (this.scaleHTMLPreview == 'display') { let self = this; window.setTimeout(function () { self.resizeIframes(); }, 500); } } }, mounted() { let self = this; self.showTags = false; self.allTags = self.existingTags; window.addEventListener("resize", self.resizeIframes); self.renderUI(); let headersTab = document.getElementById('nav-headers-tab'); headersTab.addEventListener('shown.bs.tab', function (event) { self.loadHeaders = true; }); let rawTab = document.getElementById('nav-raw-tab'); rawTab.addEventListener('shown.bs.tab', function (event) { self.srcURI = 'api/v1/message/' + self.message.ID + '/raw'; self.resizeIframes(); }); self.showTags = true; self.$nextTick(function () { Tags.init("select[multiple]"); }); }, unmounted: function () { window.removeEventListener("resize", this.resizeIframes); }, methods: { renderUI: function () { let self = this; // click the first non-disabled tab document.querySelector('#nav-tab button:not([disabled])').click(); document.activeElement.blur(); // blur focus document.getElementById('message-view').scrollTop = 0; // delay 0.2s until vue has rendered the iframe content window.setTimeout(function () { let p = document.getElementById('preview-html'); if (p) { // make links open in new window let anchorEls = p.contentWindow.document.body.querySelectorAll('a'); for (var i = 0; i < anchorEls.length; i++) { let anchorEl = anchorEls[i]; let href = anchorEl.getAttribute('href'); if (href && href.match(/^http/)) { anchorEl.setAttribute('target', '_blank'); } } self.resizeIframes(); } }, 200); // html highlighting window.Prism = window.Prism || {}; window.Prism.manual = true; Prism.highlightAll(); }, resizeIframe: function (el) { let i = el.target; i.style.height = i.contentWindow.document.body.scrollHeight + 50 + 'px'; }, resizeIframes: function () { if (this.scaleHTMLPreview != 'display') { return; } let h = document.getElementById('preview-html'); if (h) { h.style.height = h.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'); }); } } } </script> <template> <div v-if="message" id="message-view" class="mh-100" style="overflow-y: scroll;"> <div class="row w-100"> <div class="col-md"> <table class="messageHeaders"> <tbody> <tr> <th class="small">From</th> <td class="privacy"> <span v-if="message.From"> <span v-if="message.From.Name">{{ message.From.Name + " " }}</span> <span v-if="message.From.Address" class="small"> <{{ message.From.Address }}> </span> </span> <span v-else> [ Unknown ] </span> </td> </tr> <tr class="small"> <th>To</th> <td class="privacy"> <span v-if="message.To && message.To.length" v-for="(t, i) in message.To"> <template v-if="i > 0">, </template> <span class="text-nowrap">{{ t.Name + " <" + t.Address + ">" }}</span> </span> <span v-else>Undisclosed recipients</span> </td> </tr> <tr v-if="message.Cc && message.Cc.length" class="small"> <th>Cc</th> <td class="privacy"> <span v-for="(t, i) in message.Cc"> <template v-if="i > 0">,</template> {{ t.Name + " <" + t.Address + ">" }} </span> </td> </tr> <tr v-if="message.Bcc && message.Bcc.length" class="small"> <th>Bcc</th> <td class="privacy"> <span v-for="(t, i) in message.Bcc"> <template v-if="i > 0">,</template> {{ t.Name + " <" + t.Address + ">" }} </span> </td> </tr> <tr v-if="message.ReplyTo && message.ReplyTo.length" class="small"> <th class="text-nowrap">Reply-To</th> <td class="privacy text-muted"> <span v-for="(t, i) in message.ReplyTo"> <template v-if="i > 0">,</template> {{ t.Name + " <" + t.Address + ">" }} </span> </td> </tr> <tr> <th class="small">Subject</th> <td><strong>{{ message.Subject }}</strong></td> </tr> <tr class="d-md-none small"> <th class="small">Date</th> <td>{{ messageDate(message.Date) }}</td> </tr> <tr class="small" v-if="showTags"> <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> </table> </div> <div class="col-md-auto d-none d-md-block text-end mt-md-3"> <div class="mt-2 mt-md-0" v-if="allAttachments(message)"> <span class="badge rounded-pill text-bg-secondary p-2"> Attachment<span v-if="allAttachments(message).length > 1">s</span> ({{ allAttachments(message).length }}) </span> </div> </div> </div> <nav> <div class="nav nav-tabs my-3" id="nav-tab" role="tablist"> <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" v-on:click="showMobileBtns = true">HTML</button> <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" v-if="message.HTML" v-on:click="showMobileBtns = false"> HTML <span class="d-sm-none">Src</span><span class="d-none d-sm-inline">Source</span> </button> <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" :class="message.HTML == '' ? 'show' : ''" v-on:click="showMobileBtns = false">Text</button> <button class="nav-link" id="nav-headers-tab" data-bs-toggle="tab" data-bs-target="#nav-headers" type="button" role="tab" aria-controls="nav-headers" aria-selected="false" v-on:click="showMobileBtns = false"> <span class="d-sm-none">Hdrs</span><span class="d-none d-sm-inline">Headers</span> </button> <button class="nav-link" id="nav-raw-tab" data-bs-toggle="tab" data-bs-target="#nav-raw" type="button" role="tab" aria-controls="nav-raw" aria-selected="false" v-on:click="showMobileBtns = false">Raw</button> <div class="d-none d-lg-block ms-auto me-2" v-if="showMobileBtns"> <template v-for="vals, key in responsiveSizes"> <button class="btn" :class="scaleHTMLPreview == key ? 'btn-outline-primary' : ''" :disabled="scaleHTMLPreview == key" :title="'Switch to ' + key + ' view'" v-on:click="scaleHTMLPreview = key"> <i class="bi" :class="'bi-' + key"></i> </button> </template> </div> </div> </nav> <div class="tab-content mb-5" id="nav-tabContent"> <div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel" aria-labelledby="nav-html-tab" tabindex="0"> <div id="responsive-view" :class="scaleHTMLPreview" :style="responsiveSizes[scaleHTMLPreview]"> <iframe target-blank="" class="tab-pane d-block" id="preview-html" :srcdoc="message.HTML" v-on:load="resizeIframe" seamless frameborder="0" style="width: 100%; height: 100%;"> </iframe> </div> <Attachments v-if="allAttachments(message).length" :message="message" :attachments="allAttachments(message)"></Attachments> </div> <div class="tab-pane fade" id="nav-html-source" role="tabpanel" aria-labelledby="nav-html-source-tab" tabindex="0" v-if="message.HTML"> <pre><code class="language-html">{{ message.HTML }}</code></pre> </div> <div class="tab-pane fade" id="nav-plain-text" role="tabpanel" aria-labelledby="nav-plain-text-tab" tabindex="0" :class="message.HTML == '' ? 'show' : ''"> <div class="text-view">{{ message.Text }}</div> <Attachments v-if="allAttachments(message).length" :message="message" :attachments="allAttachments(message)"></Attachments> </div> <div class="tab-pane fade" id="nav-headers" role="tabpanel" aria-labelledby="nav-headers-tab" tabindex="0"> <Headers v-if="loadHeaders" :message="message"></Headers> </div> <div class="tab-pane fade" id="nav-raw" role="tabpanel" aria-labelledby="nav-raw-tab" tabindex="0"> <iframe v-if="srcURI" :src="srcURI" v-on:load="resizeIframe" seamless frameborder="0" style="width: 100%; height: 300px;" id="message-src"></iframe> </div> </div> </div> </template>