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

Feature: Add pagination & limits to URL parameters (#303)

* Set search conditions to query parameters

* Fixed by review

* Update query parameters when new message notified
This commit is contained in:
Yuuki Takahashi
2024-06-01 20:32:11 +09:00
committed by Ralph Slooten
parent 31390e4b82
commit e87b98b73b
11 changed files with 106 additions and 21 deletions

View File

@ -2,6 +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";
export default { export default {
mixins: [ mixins: [
@ -99,6 +100,14 @@ export default {
} }
} }
}, },
toTagUrl: function (t) {
const params = new URLSearchParams({
start: String(0),
limit: pagination.limit.toString(),
})
return '/search?q=' + this.tagEncodeURI(t) + '&' + params.toString()
},
} }
} }
</script> </script>
@ -143,7 +152,7 @@ export default {
{{ message.Snippet }} {{ message.Snippet }}
</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="'/search?q=' + tagEncodeURI(t)" <RouterLink class="badge me-1" v-for="t in message.Tags" :to="toTagUrl(t)"
: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

@ -32,6 +32,7 @@ export default {
methods: { methods: {
reloadInbox: function () { reloadInbox: function () {
pagination.start = 0 pagination.start = 0
this.$router.push('/')
this.loadMessages() this.loadMessages()
}, },

View File

@ -67,9 +67,22 @@ export default {
if (query == '') { if (query == '') {
this.$router.push('/') this.$router.push('/')
} else { } 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(tag) {
const params = new URLSearchParams({
start: String(0),
limit: pagination.limit.toString(),
})
return '/search?q=' + this.tagEncodeURI(tag) + '&' + params.toString()
},
} }
} }
</script> </script>
@ -91,7 +104,7 @@ export default {
</ul> </ul>
</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="'/search?q=' + tagEncodeURI(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="reloadFilter(tag)" 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' : ''">

View File

@ -60,6 +60,13 @@ export default {
} else { } else {
// update pagination offset // update pagination offset
pagination.start++ pagination.start++
const path = self.$route.path
const params = new URLSearchParams({
...self.$route.query,
start: pagination.start.toString(),
limit: pagination.limit.toString(),
})
self.$router.push(path + '?' + params.toString())
} }
} }

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 { pagination } from '../stores/pagination' import {limitOptions, pagination} from '../stores/pagination'
export default { export default {
@ -17,6 +17,7 @@ export default {
return { return {
pagination, pagination,
mailbox, mailbox,
limitOptions,
} }
}, },
@ -44,11 +45,13 @@ export default {
changeLimit: function () { changeLimit: function () {
pagination.start = 0 pagination.start = 0
this.$emit('loadMessages') this.$emit('loadMessages')
this.updateQueryParams()
}, },
viewNext: function () { viewNext: function () {
pagination.start = parseInt(pagination.start, 10) + parseInt(pagination.limit, 10) pagination.start = parseInt(pagination.start, 10) + parseInt(pagination.limit, 10)
this.$emit('loadMessages') this.$emit('loadMessages')
this.updateQueryParams()
}, },
viewPrev: function () { viewPrev: function () {
@ -58,6 +61,17 @@ export default {
} }
pagination.start = s pagination.start = s
this.$emit('loadMessages') this.$emit('loadMessages')
this.updateQueryParams()
},
updateQueryParams: function () {
const path = this.$route.path
const params = new URLSearchParams({
...this.$route.query,
start: pagination.start.toString(),
limit: pagination.limit.toString(),
})
this.$router.push(path + '?' + params.toString())
}, },
} }
} }
@ -66,10 +80,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 value="25">25</option> <option v-for="option in limitOptions" :key="option" :value="option">{{option}}</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
</select> </select>
<small> <small>

View File

@ -40,7 +40,12 @@ export default {
pagination.start = 0 pagination.start = 0
this.$emit('loadMessages') this.$emit('loadMessages')
} }
this.$router.push('/search?q=' + encodeURIComponent(this.search)) const params = new URLSearchParams({
q: this.search,
start: pagination.start.toString(),
limit: pagination.limit.toString(),
})
this.$router.push('/search?' + params.toString())
} }
e.preventDefault() e.preventDefault()

View File

@ -2,6 +2,7 @@ import axios from 'axios'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ColorHash from 'color-hash' import ColorHash from 'color-hash'
import { Modal, Offcanvas } from 'bootstrap' import { Modal, Offcanvas } from 'bootstrap'
import {limitOptions} from "../stores/pagination";
// BootstrapElement is used to return a fake Bootstrap element // BootstrapElement is used to return a fake Bootstrap element
// if the ID returns nothing to prevent errors. // if the ID returns nothing to prevent errors.
@ -66,14 +67,28 @@ export default {
} }
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
const q = urlParams.get('q').trim() const q = urlParams.get('q')?.trim()
if (q == '') { if (!q) {
return false return false
} }
return q 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 // generic modal get/set function
modal: function (id) { modal: function (id) {
let e = document.getElementById(id) let e = document.getElementById(id)

View File

@ -6,3 +6,5 @@ export const pagination = reactive({
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
}) })
export const limitOptions = [25, 50, 100, 200]

View File

@ -9,6 +9,7 @@ import NavTags from '../components/NavTags.vue'
import Pagination from '../components/Pagination.vue' import Pagination from '../components/Pagination.vue'
import SearchForm from '../components/SearchForm.vue' import SearchForm from '../components/SearchForm.vue'
import { mailbox } from '../stores/mailbox' import { mailbox } from '../stores/mailbox'
import {pagination} from "../stores/pagination";
export default { export default {
mixins: [CommonMixins, MessagesMixins], mixins: [CommonMixins, MessagesMixins],
@ -30,6 +31,14 @@ export default {
}, },
mounted() { mounted() {
const paginationParams = this.getPaginationParams()
if (paginationParams?.start) {
pagination.start = paginationParams.start
}
if (paginationParams?.limit) {
pagination.limit = paginationParams.limit
}
mailbox.searching = false mailbox.searching = false
this.apiURI = this.resolve(`/api/v1/messages`) this.apiURI = this.resolve(`/api/v1/messages`)
this.loadMessages() this.loadMessages()

View File

@ -184,9 +184,18 @@ export default {
mailbox.lastMessage = this.$route.params.id mailbox.lastMessage = this.$route.params.id
if (mailbox.searching) { if (mailbox.searching) {
this.$router.push('/search?q=' + encodeURIComponent(mailbox.searching)) const params = new URLSearchParams({
q: mailbox.searching,
start: pagination.start.toString(),
limit: pagination.limit.toString(),
})
this.$router.push('/search?' + params.toString())
} else { } else {
this.$router.push('/') const params = new URLSearchParams({
start: pagination.start.toString(),
limit: pagination.limit.toString(),
})
this.$router.push('/?' + params.toString())
} }
}, },

View File

@ -33,17 +33,25 @@ export default {
watch: { watch: {
$route(to, from) { $route(to, from) {
this.doSearch(true) this.doSearch()
} }
}, },
mounted() { mounted() {
const paginationParams = this.getPaginationParams()
if (paginationParams?.start) {
pagination.start = paginationParams.start
}
if (paginationParams?.limit) {
pagination.limit = paginationParams.limit
}
mailbox.searching = this.getSearch() mailbox.searching = this.getSearch()
this.doSearch(false) this.doSearch()
}, },
methods: { methods: {
doSearch: function (resetPagination) { doSearch: function () {
let s = this.getSearch() let s = this.getSearch()
if (!s) { if (!s) {
@ -54,10 +62,6 @@ export default {
mailbox.searching = s mailbox.searching = s
if (resetPagination) {
pagination.start = 0
}
this.apiURI = this.resolve(`/api/v1/search`) + '?query=' + encodeURIComponent(s) this.apiURI = this.resolve(`/api/v1/search`) + '?query=' + encodeURIComponent(s)
if (mailbox.timeZone != '' && (s.indexOf('after:') != -1 || s.indexOf('before:') != -1)) { if (mailbox.timeZone != '' && (s.indexOf('after:') != -1 || s.indexOf('before:') != -1)) {
this.apiURI += '&tz=' + encodeURIComponent(mailbox.timeZone) this.apiURI += '&tz=' + encodeURIComponent(mailbox.timeZone)