diff --git a/package-lock.json b/package-lock.json index f01e66e..eefaf5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,9 +36,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", - "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -47,9 +47,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.3.tgz", - "integrity": "sha512-6bdmknScYKmt8I9VjsJuKKGr+TwUb555FTf6tT1P/ANlCjTHCiYLhiQ4X/O7J731w5NOqu8c1aYHEVuOwPz7jA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.5.tgz", + "integrity": "sha512-TNPDN6aBFaUox2Lu+H/Y1dKKQgr4ucz/FGyCz67RVYLsBpVpUFf1dDngzg+Od8aqbrqwyztkaZjtWCZEUOT8zA==", "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.13.11" @@ -421,9 +421,9 @@ "integrity": "sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==" }, "node_modules/@lit/reactive-element": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.1.tgz", - "integrity": "sha512-va15kYZr7KZNNPZdxONGQzpUr+4sxVu7V/VG7a8mRfPPXUyhEYj5RzXCQmGrlP3tAh0L3HHm5AjBMFYRqlM9SA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.2.tgz", + "integrity": "sha512-rDfl+QnCYjuIGf5xI2sVJWdYIi56CTCwWa+nidKYX6oIuBYwUbT/vX4qbUDlHiZKJ/3FRNQ/tWJui44p6/stSA==", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.0.0" } @@ -1063,9 +1063,9 @@ ] }, "node_modules/bootstrap5-tags": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/bootstrap5-tags/-/bootstrap5-tags-1.5.22.tgz", - "integrity": "sha512-wRpPtBBql6FvXKdSKUgFBkmmkGgsZJCzZ0bC9DvfHKylnQ4gS2pHpXB2UkIEWa/c+vENbBIyUDMSAvcZ4ujyQg==" + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/bootstrap5-tags/-/bootstrap5-tags-1.5.24.tgz", + "integrity": "sha512-+mH9WfNnz81CFoyui8G166YLe0ASEiSPb17DMdejCVdIOu7YGuSmYFfCAz/oqdD09vKege9yU6/YyXPOlGURGQ==" }, "node_modules/brace-expansion": { "version": "2.0.1", @@ -1201,9 +1201,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.30.2.tgz", - "integrity": "sha512-p/npFUJXXBkCCTIlEGBdghofn00jWG6ZOtdoIXSJmAu2QBvN0IqpZXWweOytcwE6cfx8ZvVUy1vw8zxhe4Y2vg==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.31.0.tgz", + "integrity": "sha512-/AnE9Y4OsJZicCzIe97JP5XoPKQJfTuEG43aEVLFJGOJpyqELod+pE6LEl63DfG1Mp8wX97LDaDpy1GmLEUxlg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -1683,9 +1683,9 @@ } }, "node_modules/lit": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.4.tgz", - "integrity": "sha512-cgD7xrZoYr21mbrkZIuIrj98YTMw/snJPg52deWVV4A8icLyNHI3bF70xsJeAgwTuiq5Kkd+ZR8gybSJDCPB7g==", + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.7.5.tgz", + "integrity": "sha512-i/cH7Ye6nBDUASMnfwcictBnsTN91+aBjXoTHF2xARghXScKxpD4F4WYI+VLXg9lqbMinDfvoI7VnZXjyHgdfQ==", "dependencies": { "@lit/reactive-element": "^1.6.0", "lit-element": "^3.3.0", @@ -2062,9 +2062,9 @@ } }, "node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "node_modules/qs": { "version": "6.11.2", @@ -2080,15 +2080,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/ramda": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", @@ -2209,9 +2200,9 @@ "optional": true }, "node_modules/sass": { - "version": "1.62.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", - "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", + "version": "1.63.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.4.tgz", + "integrity": "sha512-Sx/+weUmK+oiIlI+9sdD0wZHsqpbgQg8wSwSnGBjwb5GwqFhYNwwnI+UWZtLjKvKyFlKkatRK235qQ3mokyPoQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -2526,12 +2517,12 @@ "integrity": "sha512-tdOvLfRzHolwYcHS6HIX860MkK9LQ4+oLuNwFYL7bpgTEO64PZrcQxkisgwJYCfF8sKiWLwwu1c83DvMkbefIQ==" }, "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", + "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "punycode": "^1.4.1", + "qs": "^6.11.0" } }, "node_modules/util-deprecate": { diff --git a/server/ui-src/templates/Message.vue b/server/ui-src/templates/Message.vue index a0919d6..f212f4b 100644 --- a/server/ui-src/templates/Message.vue +++ b/server/ui-src/templates/Message.vue @@ -163,6 +163,27 @@ export default { self.scrollInPlace = true; self.$emit('loadMessages'); }); + }, + + // Convert plain text to HTML including anchor links + textToHTML: function (s) { + // escape to HTML + let html = s + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + + // full links with http(s) + let re = /(\b(https?|ftp):\/\/[\-\w@:%_\+.~#?,&\/\/=;]+)\b/gim + html = html.replace(re, '<a href="$&" target="_blank" rel="noopener">$&</a>') + + // plain www links without https?:// prefix + let re2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim + html = html.replace(re2, '$1<a href="http://$2" target="_blank" rel="noopener">$2</a>') + + return html } } } @@ -272,28 +293,28 @@ export default { role="tab" aria-controls="nav-html" aria-selected="true" v-if="message.HTML" v-on:click="showMobileBtns = true; resizeIframes()">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 "> + 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> + :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 "> + 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> + v-on:click=" showMobileBtns = false">Raw</button> - <div class="d-none d-lg-block ms-auto me-2" v-if=" showMobileBtns "> + <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 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> @@ -301,31 +322,31 @@ export default { </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" + <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%;"> + <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> + <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 "> + 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> + :class="message.HTML == '' ? 'show' : ''"> + <div class="text-view" v-html="textToHTML(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> + <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" + <iframe v-if="srcURI" :src="srcURI" v-on:load="resizeIframe" seamless frameborder="0" style="width: 100%; height: 300px;" id="message-src"></iframe> </div> </div>