mirror of
https://github.com/axllent/mailpit.git
synced 2025-08-15 20:13:16 +02:00
Merge branch 'feature/query-parameters' into develop
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { mailbox } from '../stores/mailbox'
|
||||
import CommonMixins from '../mixins/CommonMixins'
|
||||
import dayjs from 'dayjs'
|
||||
import { pagination } from "../stores/pagination";
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
@@ -15,6 +16,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
mailbox,
|
||||
pagination,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -99,6 +101,20 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toTagUrl: function (t) {
|
||||
if (t.match(/ /)) {
|
||||
t = `"${t}"`
|
||||
}
|
||||
const p = {
|
||||
q: 'tag:' + t
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
}
|
||||
const params = new URLSearchParams(p)
|
||||
return '/search?' + params.toString()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -143,7 +159,8 @@ export default {
|
||||
{{ message.Snippet }}
|
||||
</div>
|
||||
<div v-if="message.Tags.length">
|
||||
<RouterLink class="badge me-1" v-for="t in message.Tags" :to="'/search?q=' + tagEncodeURI(t)"
|
||||
<RouterLink class="badge me-1" v-for="t in message.Tags" :to="toTagUrl(t)"
|
||||
v-on:click="pagination.start = 0"
|
||||
:style="mailbox.showTagColors ? { backgroundColor: colorHash(t) } : { backgroundColor: '#6c757d' }"
|
||||
:title="'Filter messages tagged with ' + t">
|
||||
{{ t }}
|
||||
|
@@ -32,6 +32,7 @@ export default {
|
||||
methods: {
|
||||
reloadInbox: function () {
|
||||
pagination.start = 0
|
||||
this.$router.push('/')
|
||||
this.loadMessages()
|
||||
},
|
||||
|
||||
|
@@ -11,25 +11,11 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
mailbox,
|
||||
pagination,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// if the current filter is active then reload view
|
||||
reloadFilter: function (tag) {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const query = urlParams.get('q')
|
||||
if (!query) {
|
||||
return false
|
||||
}
|
||||
|
||||
let re = new RegExp(`^tag:"?${tag}"?$`, 'i')
|
||||
if (query.match(re)) {
|
||||
pagination.start = 0
|
||||
this.$emit('loadMessages')
|
||||
}
|
||||
},
|
||||
|
||||
// test whether a tag is currently being searched for (in the URL)
|
||||
inSearch: function (tag) {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
@@ -67,9 +53,28 @@ export default {
|
||||
if (query == '') {
|
||||
this.$router.push('/')
|
||||
} else {
|
||||
this.$router.push('/search?q=' + encodeURIComponent(query))
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
start: pagination.start.toString(),
|
||||
limit: pagination.limit.toString(),
|
||||
})
|
||||
this.$router.push('/search?' + params.toString())
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toTagUrl(t) {
|
||||
if (t.match(/ /)) {
|
||||
t = `"${t}"`
|
||||
}
|
||||
const p = {
|
||||
q: 'tag:' + t
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
}
|
||||
const params = new URLSearchParams(p)
|
||||
return '/search?' + params.toString()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -91,8 +96,8 @@ export default {
|
||||
</ul>
|
||||
</div>
|
||||
<div class="list-group mt-1 mb-5 pb-3">
|
||||
<RouterLink v-for="tag in mailbox.tags" :to="'/search?q=' + tagEncodeURI(tag)" @click="hideNav"
|
||||
v-on:click="reloadFilter(tag)" v-on:click.ctrl="toggleTag($event, tag)"
|
||||
<RouterLink v-for="tag in mailbox.tags" :to="toTagUrl(tag)" @click="hideNav"
|
||||
v-on:click="pagination.start = 0" v-on:click.ctrl="toggleTag($event, tag)"
|
||||
:style="mailbox.showTagColors ? { borderLeftColor: colorHash(tag), borderLeftWidth: '4px' } : ''"
|
||||
class="list-group-item list-group-item-action small px-2" :class="inSearch(tag) ? 'active' : ''">
|
||||
<i class="bi bi-tag-fill" v-if="inSearch(tag)"></i>
|
||||
|
@@ -15,7 +15,8 @@ export default {
|
||||
reconnectRefresh: false,
|
||||
socketURI: false,
|
||||
pauseNotifications: false, // prevent spamming
|
||||
version: false
|
||||
version: false,
|
||||
paginationDelayed: false, // for delayed pagination URL changes
|
||||
}
|
||||
},
|
||||
|
||||
@@ -60,6 +61,8 @@ export default {
|
||||
} else {
|
||||
// update pagination offset
|
||||
pagination.start++
|
||||
// prevent "Too many calls to Location or History APIs within a short timeframe"
|
||||
self.delayedPaginationUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +124,38 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// This will only update the pagination offset at a maximum of 2x per second
|
||||
// when viewing the inbox on > page 1, while receiving an influx of new messages.
|
||||
delayedPaginationUpdate: function () {
|
||||
if (this.paginationDelayed) {
|
||||
return
|
||||
}
|
||||
|
||||
this.paginationDelayed = true
|
||||
|
||||
window.setTimeout(() => {
|
||||
const path = this.$route.path
|
||||
const p = {
|
||||
...this.$route.query
|
||||
}
|
||||
if (pagination.start > 0) {
|
||||
p.start = pagination.start.toString()
|
||||
} else {
|
||||
delete p.start
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
} else {
|
||||
delete p.limit
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(p)
|
||||
this.$router.push(path + '?' + params.toString())
|
||||
|
||||
this.paginationDelayed = false
|
||||
}, 500)
|
||||
},
|
||||
|
||||
browserNotify: function (title, message) {
|
||||
if (!("Notification" in window)) {
|
||||
return
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import CommonMixins from '../mixins/CommonMixins'
|
||||
import { mailbox } from '../stores/mailbox'
|
||||
import { pagination } from '../stores/pagination'
|
||||
import { limitOptions, pagination } from '../stores/pagination'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -17,6 +17,7 @@ export default {
|
||||
return {
|
||||
pagination,
|
||||
mailbox,
|
||||
limitOptions,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -44,11 +45,13 @@ export default {
|
||||
changeLimit: function () {
|
||||
pagination.start = 0
|
||||
this.$emit('loadMessages')
|
||||
this.updateQueryParams()
|
||||
},
|
||||
|
||||
viewNext: function () {
|
||||
pagination.start = parseInt(pagination.start, 10) + parseInt(pagination.limit, 10)
|
||||
this.$emit('loadMessages')
|
||||
this.updateQueryParams()
|
||||
},
|
||||
|
||||
viewPrev: function () {
|
||||
@@ -58,6 +61,26 @@ export default {
|
||||
}
|
||||
pagination.start = s
|
||||
this.$emit('loadMessages')
|
||||
this.updateQueryParams()
|
||||
},
|
||||
|
||||
updateQueryParams: function () {
|
||||
const path = this.$route.path
|
||||
const p = {
|
||||
...this.$route.query
|
||||
}
|
||||
if (pagination.start > 0) {
|
||||
p.start = pagination.start.toString()
|
||||
} else {
|
||||
delete p.start
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
} else {
|
||||
delete p.limit
|
||||
}
|
||||
const params = new URLSearchParams(p)
|
||||
this.$router.push(path + '?' + params.toString())
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -66,10 +89,7 @@ export default {
|
||||
<template>
|
||||
<select v-model="pagination.limit" @change="changeLimit" class="form-select form-select-sm d-inline w-auto me-2"
|
||||
:disabled="total == 0">
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="200">200</option>
|
||||
<option v-for="option in limitOptions" :key="option" :value="option">{{ option }}</option>
|
||||
</select>
|
||||
|
||||
<small>
|
||||
|
@@ -40,7 +40,18 @@ export default {
|
||||
pagination.start = 0
|
||||
this.$emit('loadMessages')
|
||||
}
|
||||
this.$router.push('/search?q=' + encodeURIComponent(this.search))
|
||||
const p = {
|
||||
q: this.search
|
||||
}
|
||||
if (pagination.start > 0) {
|
||||
p.start = pagination.start.toString()
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(p)
|
||||
this.$router.push('/search?' + params.toString())
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
@@ -2,6 +2,7 @@ import axios from 'axios'
|
||||
import dayjs from 'dayjs'
|
||||
import ColorHash from 'color-hash'
|
||||
import { Modal, Offcanvas } from 'bootstrap'
|
||||
import {limitOptions} from "../stores/pagination";
|
||||
|
||||
// BootstrapElement is used to return a fake Bootstrap element
|
||||
// if the ID returns nothing to prevent errors.
|
||||
@@ -66,14 +67,28 @@ export default {
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const q = urlParams.get('q').trim()
|
||||
if (q == '') {
|
||||
const q = urlParams.get('q')?.trim()
|
||||
if (!q) {
|
||||
return false
|
||||
}
|
||||
|
||||
return q
|
||||
},
|
||||
|
||||
getPaginationParams: function () {
|
||||
if (!window.location.search) {
|
||||
return null
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const start = parseInt(urlParams.get('start')?.trim(), 10)
|
||||
const limit = parseInt(urlParams.get('limit')?.trim(), 10)
|
||||
return {
|
||||
start: Number.isInteger(start) && start >= 0 ? start : null,
|
||||
limit: limitOptions.includes(limit) ? limit : null,
|
||||
}
|
||||
},
|
||||
|
||||
// generic modal get/set function
|
||||
modal: function (id) {
|
||||
let e = document.getElementById(id)
|
||||
|
@@ -3,6 +3,9 @@ import { reactive } from 'vue'
|
||||
export const pagination = reactive({
|
||||
start: 0, // pagination offset
|
||||
limit: 50, // per page
|
||||
defaultLimit: 50, // used to shorten URL's if current limit == defaultLimit
|
||||
total: 0, // total results of current view / filter
|
||||
count: 0, // number of messages currently displayed
|
||||
})
|
||||
|
||||
export const limitOptions = [25, 50, 100, 200]
|
||||
|
@@ -9,6 +9,7 @@ import NavTags from '../components/NavTags.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
import SearchForm from '../components/SearchForm.vue'
|
||||
import { mailbox } from '../stores/mailbox'
|
||||
import {pagination} from "../stores/pagination";
|
||||
|
||||
export default {
|
||||
mixins: [CommonMixins, MessagesMixins],
|
||||
@@ -30,6 +31,14 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paginationParams = this.getPaginationParams()
|
||||
if (paginationParams?.start) {
|
||||
pagination.start = paginationParams.start
|
||||
}
|
||||
if (paginationParams?.limit) {
|
||||
pagination.limit = paginationParams.limit
|
||||
}
|
||||
|
||||
mailbox.searching = false
|
||||
this.apiURI = this.resolve(`/api/v1/messages`)
|
||||
this.loadMessages()
|
||||
|
@@ -184,9 +184,27 @@ export default {
|
||||
mailbox.lastMessage = this.$route.params.id
|
||||
|
||||
if (mailbox.searching) {
|
||||
this.$router.push('/search?q=' + encodeURIComponent(mailbox.searching))
|
||||
const p = {
|
||||
q: mailbox.searching
|
||||
}
|
||||
if (pagination.start > 0) {
|
||||
p.start = pagination.start.toString()
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
}
|
||||
const params = new URLSearchParams(p)
|
||||
this.$router.push('/search?' + params.toString())
|
||||
} else {
|
||||
this.$router.push('/')
|
||||
const p = {}
|
||||
if (pagination.start > 0) {
|
||||
p.start = pagination.start.toString()
|
||||
}
|
||||
if (pagination.limit != pagination.defaultLimit) {
|
||||
p.limit = pagination.limit.toString()
|
||||
}
|
||||
const params = new URLSearchParams(p)
|
||||
this.$router.push('/?' + params.toString())
|
||||
}
|
||||
},
|
||||
|
||||
@@ -221,7 +239,8 @@ export default {
|
||||
<i class="bi bi-eye-slash"></i> <span class="d-none d-md-inline">Mark unread</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-light me-1 me-sm-2" title="Release message"
|
||||
v-if="mailbox.uiConfig.MessageRelay && mailbox.uiConfig.MessageRelay.Enabled" v-on:click="initReleaseModal">
|
||||
v-if="mailbox.uiConfig.MessageRelay && mailbox.uiConfig.MessageRelay.Enabled"
|
||||
v-on:click="initReleaseModal">
|
||||
<i class="bi bi-send"></i> <span class="d-none d-md-inline">Release</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-light me-1 me-sm-2" title="Delete message" v-on:click="deleteMessage">
|
||||
@@ -341,6 +360,7 @@ export default {
|
||||
|
||||
<AboutMailpit modals />
|
||||
<AjaxLoader :loading="loading" />
|
||||
<Release v-if="mailbox.uiConfig.MessageRelay && message" ref="ReleaseRef" :message="message" @delete="deleteMessage" />
|
||||
<Release v-if="mailbox.uiConfig.MessageRelay && message" ref="ReleaseRef" :message="message"
|
||||
@delete="deleteMessage" />
|
||||
<Screenshot v-if="message" ref="ScreenshotRef" :message="message" />
|
||||
</template>
|
||||
|
@@ -33,17 +33,25 @@ export default {
|
||||
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
this.doSearch(true)
|
||||
this.doSearch()
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const paginationParams = this.getPaginationParams()
|
||||
if (paginationParams?.start) {
|
||||
pagination.start = paginationParams.start
|
||||
}
|
||||
if (paginationParams?.limit) {
|
||||
pagination.limit = paginationParams.limit
|
||||
}
|
||||
|
||||
mailbox.searching = this.getSearch()
|
||||
this.doSearch(false)
|
||||
this.doSearch()
|
||||
},
|
||||
|
||||
methods: {
|
||||
doSearch: function (resetPagination) {
|
||||
doSearch: function () {
|
||||
let s = this.getSearch()
|
||||
|
||||
if (!s) {
|
||||
@@ -54,10 +62,6 @@ export default {
|
||||
|
||||
mailbox.searching = s
|
||||
|
||||
if (resetPagination) {
|
||||
pagination.start = 0
|
||||
}
|
||||
|
||||
this.apiURI = this.resolve(`/api/v1/search`) + '?query=' + encodeURIComponent(s)
|
||||
if (mailbox.timeZone != '' && (s.indexOf('after:') != -1 || s.indexOf('before:') != -1)) {
|
||||
this.apiURI += '&tz=' + encodeURIComponent(mailbox.timeZone)
|
||||
|
Reference in New Issue
Block a user