1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-06-15 00:05:15 +02:00

Improve pagination & limit URL parameter handling

This commit is contained in:
Ralph Slooten
2024-06-02 16:07:26 +12:00
parent e87b98b73b
commit aa3f94457c
7 changed files with 114 additions and 59 deletions

View File

@ -2,7 +2,7 @@
import { mailbox } from '../stores/mailbox' import { mailbox } from '../stores/mailbox'
import CommonMixins from '../mixins/CommonMixins' import CommonMixins from '../mixins/CommonMixins'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {pagination} from "../stores/pagination"; import { pagination } from "../stores/pagination";
export default { export default {
mixins: [ mixins: [
@ -16,6 +16,7 @@ export default {
data() { data() {
return { return {
mailbox, mailbox,
pagination,
} }
}, },
@ -102,11 +103,17 @@ export default {
}, },
toTagUrl: function (t) { toTagUrl: function (t) {
const params = new URLSearchParams({ if (t.match(/ /)) {
start: String(0), t = `"${t}"`
limit: pagination.limit.toString(), }
}) const p = {
return '/search?q=' + this.tagEncodeURI(t) + '&' + params.toString() q: 'tag:' + t
}
if (pagination.limit != pagination.defaultLimit) {
p.limit = pagination.limit.toString()
}
const params = new URLSearchParams(p)
return '/search?' + params.toString()
}, },
} }
} }
@ -153,6 +160,7 @@ export default {
</div> </div>
<div v-if="message.Tags.length"> <div v-if="message.Tags.length">
<RouterLink class="badge me-1" v-for="t in message.Tags" :to="toTagUrl(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' }" :style="mailbox.showTagColors ? { backgroundColor: colorHash(t) } : { backgroundColor: '#6c757d' }"
:title="'Filter messages tagged with ' + t"> :title="'Filter messages tagged with ' + t">
{{ t }} {{ t }}

View File

@ -11,25 +11,11 @@ export default {
data() { data() {
return { return {
mailbox, mailbox,
pagination,
} }
}, },
methods: { 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) // test whether a tag is currently being searched for (in the URL)
inSearch: function (tag) { inSearch: function (tag) {
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
@ -76,12 +62,18 @@ export default {
} }
}, },
toTagUrl(tag) { toTagUrl(t) {
const params = new URLSearchParams({ if (t.match(/ /)) {
start: String(0), t = `"${t}"`
limit: pagination.limit.toString(), }
}) const p = {
return '/search?q=' + this.tagEncodeURI(tag) + '&' + params.toString() q: 'tag:' + t
}
if (pagination.limit != pagination.defaultLimit) {
p.limit = pagination.limit.toString()
}
const params = new URLSearchParams(p)
return '/search?' + params.toString()
}, },
} }
} }
@ -105,7 +97,7 @@ export default {
</div> </div>
<div class="list-group mt-1 mb-5 pb-3"> <div class="list-group mt-1 mb-5 pb-3">
<RouterLink v-for="tag in mailbox.tags" :to="toTagUrl(tag)" @click="hideNav" <RouterLink v-for="tag in mailbox.tags" :to="toTagUrl(tag)" @click="hideNav"
v-on:click="reloadFilter(tag)" v-on:click.ctrl="toggleTag($event, tag)" v-on:click="pagination.start = 0" v-on:click.ctrl="toggleTag($event, tag)"
:style="mailbox.showTagColors ? { borderLeftColor: colorHash(tag), borderLeftWidth: '4px' } : ''" :style="mailbox.showTagColors ? { borderLeftColor: colorHash(tag), borderLeftWidth: '4px' } : ''"
class="list-group-item list-group-item-action small px-2" :class="inSearch(tag) ? 'active' : ''"> 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> <i class="bi bi-tag-fill" v-if="inSearch(tag)"></i>

View File

@ -15,7 +15,8 @@ export default {
reconnectRefresh: false, reconnectRefresh: false,
socketURI: false, socketURI: false,
pauseNotifications: false, // prevent spamming pauseNotifications: false, // prevent spamming
version: false version: false,
paginationDelayed: false, // for delayed pagination URL changes
} }
}, },
@ -60,13 +61,8 @@ export default {
} else { } else {
// update pagination offset // update pagination offset
pagination.start++ pagination.start++
const path = self.$route.path // prevent "Too many calls to Location or History APIs within a short timeframe"
const params = new URLSearchParams({ self.delayedPaginationUpdate()
...self.$route.query,
start: pagination.start.toString(),
limit: pagination.limit.toString(),
})
self.$router.push(path + '?' + params.toString())
} }
} }
@ -128,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) { browserNotify: function (title, message) {
if (!("Notification" in window)) { if (!("Notification" in window)) {
return return

View File

@ -1,7 +1,7 @@
<script> <script>
import CommonMixins from '../mixins/CommonMixins' import CommonMixins from '../mixins/CommonMixins'
import { mailbox } from '../stores/mailbox' import { mailbox } from '../stores/mailbox'
import {limitOptions, pagination} from '../stores/pagination' import { limitOptions, pagination } from '../stores/pagination'
export default { export default {
@ -66,11 +66,20 @@ export default {
updateQueryParams: function () { updateQueryParams: function () {
const path = this.$route.path const path = this.$route.path
const params = new URLSearchParams({ const p = {
...this.$route.query, ...this.$route.query
start: pagination.start.toString(), }
limit: pagination.limit.toString(), 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.$router.push(path + '?' + params.toString())
}, },
} }
@ -80,7 +89,7 @@ export default {
<template> <template>
<select v-model="pagination.limit" @change="changeLimit" class="form-select form-select-sm d-inline w-auto me-2" <select v-model="pagination.limit" @change="changeLimit" class="form-select form-select-sm d-inline w-auto me-2"
:disabled="total == 0"> :disabled="total == 0">
<option v-for="option in limitOptions" :key="option" :value="option">{{option}}</option> <option v-for="option in limitOptions" :key="option" :value="option">{{ option }}</option>
</select> </select>
<small> <small>

View File

@ -40,11 +40,17 @@ export default {
pagination.start = 0 pagination.start = 0
this.$emit('loadMessages') this.$emit('loadMessages')
} }
const params = new URLSearchParams({ const p = {
q: this.search, q: this.search
start: pagination.start.toString(), }
limit: pagination.limit.toString(), 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()) this.$router.push('/search?' + params.toString())
} }

View File

@ -3,6 +3,7 @@ import { reactive } from 'vue'
export const pagination = reactive({ export const pagination = reactive({
start: 0, // pagination offset start: 0, // pagination offset
limit: 50, // per page limit: 50, // per page
defaultLimit: 50, // used to shorten URL's if current limit == defaultLimit
total: 0, // total results of current view / filter total: 0, // total results of current view / filter
count: 0, // number of messages currently displayed count: 0, // number of messages currently displayed
}) })

View File

@ -184,17 +184,26 @@ export default {
mailbox.lastMessage = this.$route.params.id mailbox.lastMessage = this.$route.params.id
if (mailbox.searching) { if (mailbox.searching) {
const params = new URLSearchParams({ const p = {
q: mailbox.searching, q: mailbox.searching
start: pagination.start.toString(), }
limit: pagination.limit.toString(), 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()) this.$router.push('/search?' + params.toString())
} else { } else {
const params = new URLSearchParams({ const p = {}
start: pagination.start.toString(), if (pagination.start > 0) {
limit: pagination.limit.toString(), 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()) this.$router.push('/?' + params.toString())
} }
}, },
@ -230,7 +239,8 @@ export default {
<i class="bi bi-eye-slash"></i> <span class="d-none d-md-inline">Mark unread</span> <i class="bi bi-eye-slash"></i> <span class="d-none d-md-inline">Mark unread</span>
</button> </button>
<button class="btn btn-outline-light me-1 me-sm-2" title="Release message" <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> <i class="bi bi-send"></i> <span class="d-none d-md-inline">Release</span>
</button> </button>
<button class="btn btn-outline-light me-1 me-sm-2" title="Delete message" v-on:click="deleteMessage"> <button class="btn btn-outline-light me-1 me-sm-2" title="Delete message" v-on:click="deleteMessage">
@ -350,6 +360,7 @@ export default {
<AboutMailpit modals /> <AboutMailpit modals />
<AjaxLoader :loading="loading" /> <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" /> <Screenshot v-if="message" ref="ScreenshotRef" :message="message" />
</template> </template>