mirror of
https://github.com/knadh/listmonk.git
synced 2025-12-05 16:00:03 +01:00
Add bulk deletion (by id or query) to lists and campaigns.
- Like subscribers, select one-or more or 'all' items and delete them on the lists and campaigns UIs. - New `DELETE /api/lists` and `DELETE /api/campaigns` endpoints that take one or more `id` params or a single `query` param.
This commit is contained in:
@@ -153,6 +153,11 @@ export const deleteList = (id) => http.delete(
|
||||
{ loading: models.lists },
|
||||
);
|
||||
|
||||
export const deleteLists = (params) => http.delete(
|
||||
'/api/lists',
|
||||
{ params, loading: models.lists },
|
||||
);
|
||||
|
||||
// Subscribers.
|
||||
export const getSubscribers = async (params) => http.get(
|
||||
'/api/subscribers',
|
||||
@@ -353,6 +358,11 @@ export const deleteCampaign = async (id) => http.delete(
|
||||
{ loading: models.campaigns },
|
||||
);
|
||||
|
||||
export const deleteCampaigns = (params) => http.delete(
|
||||
'/api/campaigns',
|
||||
{ params, loading: models.campaigns },
|
||||
);
|
||||
|
||||
// Media.
|
||||
export const getMedia = async (params) => http.get(
|
||||
'/api/media',
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<b-table :data="campaigns.results" :loading="loading.campaigns" :row-class="highlightedRow" paginated
|
||||
backend-pagination pagination-position="both" @page-change="onPageChange" :current-page="queryParams.page"
|
||||
:per-page="campaigns.perPage" :total="campaigns.total" hoverable backend-sorting @sort="onSort">
|
||||
<b-table :data="campaigns.results" :loading="loading.campaigns" :row-class="highlightedRow"
|
||||
@check-all="onTableCheck" @check="onTableCheck" :checked-rows.sync="bulk.checked" paginated backend-pagination
|
||||
pagination-position="both" @page-change="onPageChange" :current-page="queryParams.page"
|
||||
:per-page="campaigns.perPage" :total="campaigns.total" hoverable checkable backend-sorting @sort="onSort">
|
||||
<template #top-left>
|
||||
<div class="columns">
|
||||
<div class="column is-6">
|
||||
@@ -36,6 +37,20 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions" v-if="bulk.checked.length > 0">
|
||||
<a class="a" href="#" @click.prevent="deleteCampaigns" data-cy="btn-delete-campaigns">
|
||||
<b-icon icon="trash-can-outline" size="is-small" /> Delete
|
||||
</a>
|
||||
<span class="a">
|
||||
{{ $tc('globals.messages.numSelected', numSelectedCampaigns, { num: numSelectedCampaigns }) }}
|
||||
<span v-if="!bulk.all && campaigns.total > campaigns.perPage">
|
||||
—
|
||||
<a href="#" @click.prevent="onSelectAll">
|
||||
{{ $tc('globals.messages.selectAll', campaigns.total, { num: campaigns.total }) }}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<b-table-column v-slot="props" cell-class="status" field="status" :label="$t('globals.fields.status')" width="10%"
|
||||
@@ -285,6 +300,12 @@ export default Vue.extend({
|
||||
},
|
||||
pollID: null,
|
||||
campaignStatsData: {},
|
||||
|
||||
// Table bulk row selection states.
|
||||
bulk: {
|
||||
checked: [],
|
||||
all: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -456,10 +477,57 @@ export default Vue.extend({
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: c.name }));
|
||||
});
|
||||
},
|
||||
|
||||
// Mark all campaigns in the query as selected.
|
||||
onSelectAll() {
|
||||
this.bulk.all = true;
|
||||
},
|
||||
|
||||
onTableCheck() {
|
||||
// Disable bulk.all selection if there are no rows checked in the table.
|
||||
if (this.bulk.checked.length !== this.campaigns.total) {
|
||||
this.bulk.all = false;
|
||||
}
|
||||
},
|
||||
|
||||
deleteCampaigns() {
|
||||
const name = this.$tc('globals.terms.campaign', this.numSelectedCampaigns);
|
||||
|
||||
const fn = () => {
|
||||
const params = {};
|
||||
if (!this.bulk.all && this.bulk.checked.length > 0) {
|
||||
// If 'all' is not selected, delete campaigns by IDs.
|
||||
params.id = this.bulk.checked.map((c) => c.id);
|
||||
} else {
|
||||
// 'All' is selected, delete by query.
|
||||
params.query = this.queryParams.query.replace(/[^\p{L}\p{N}\s]/gu, ' ');
|
||||
}
|
||||
|
||||
this.$api.deleteCampaigns(params)
|
||||
.then(() => {
|
||||
this.getCampaigns();
|
||||
this.$utils.toast(this.$tc(
|
||||
'globals.messages.deletedCount',
|
||||
this.numSelectedCampaigns,
|
||||
{ num: this.numSelectedCampaigns, name },
|
||||
));
|
||||
});
|
||||
};
|
||||
|
||||
this.$utils.confirm(this.$tc(
|
||||
'globals.messages.confirmDelete',
|
||||
this.numSelectedCampaigns,
|
||||
{ num: this.numSelectedCampaigns, name: name.toLowerCase() },
|
||||
), fn);
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['campaigns', 'loading']),
|
||||
|
||||
numSelectedCampaigns() {
|
||||
return this.bulk.all ? this.campaigns.total : this.bulk.checked.length;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<b-table :data="lists.results" :loading="loading.listsFull" hoverable default-sort="createdAt" paginated
|
||||
backend-pagination pagination-position="both" @page-change="onPageChange" :current-page="queryParams.page"
|
||||
:per-page="lists.perPage" :total="lists.total" backend-sorting @sort="onSort">
|
||||
<b-table :data="lists.results" :loading="loading.listsFull" @check-all="onTableCheck" @check="onTableCheck"
|
||||
:checked-rows.sync="bulk.checked" hoverable default-sort="createdAt" paginated backend-pagination
|
||||
pagination-position="both" @page-change="onPageChange" :current-page="queryParams.page" :per-page="lists.perPage"
|
||||
:total="lists.total" checkable backend-sorting @sort="onSort">
|
||||
<template #top-left>
|
||||
<div class="columns">
|
||||
<div class="column is-6">
|
||||
@@ -42,6 +43,20 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions" v-if="bulk.checked.length > 0">
|
||||
<a class="a" href="#" @click.prevent="deleteLists" data-cy="btn-delete-lists">
|
||||
<b-icon icon="trash-can-outline" size="is-small" /> Delete
|
||||
</a>
|
||||
<span class="a">
|
||||
{{ $tc('globals.messages.numSelected', numSelectedLists, { num: numSelectedLists }) }}
|
||||
<span v-if="!bulk.all && lists.total > lists.perPage">
|
||||
—
|
||||
<a href="#" @click.prevent="onSelectAll">
|
||||
{{ $tc('globals.messages.selectAll', lists.total, { num: lists.total }) }}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<b-table-column v-slot="props" field="name" :label="$t('globals.fields.name')" header-class="cy-name" sortable
|
||||
@@ -195,6 +210,12 @@ export default Vue.extend({
|
||||
order: 'asc',
|
||||
status: this.$route.query.status || 'active',
|
||||
},
|
||||
|
||||
// Table bulk row selection states.
|
||||
bulk: {
|
||||
checked: [],
|
||||
all: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -272,6 +293,49 @@ export default Vue.extend({
|
||||
);
|
||||
},
|
||||
|
||||
// Mark all lists in the query as selected.
|
||||
onSelectAll() {
|
||||
this.bulk.all = true;
|
||||
},
|
||||
|
||||
onTableCheck() {
|
||||
// Disable bulk.all selection if there are no rows checked in the table.
|
||||
if (this.bulk.checked.length !== this.lists.total) {
|
||||
this.bulk.all = false;
|
||||
}
|
||||
},
|
||||
|
||||
deleteLists() {
|
||||
const name = this.$tc('globals.terms.list', this.numSelectedCampaigns);
|
||||
|
||||
const fn = () => {
|
||||
const params = {};
|
||||
if (!this.bulk.all && this.bulk.checked.length > 0) {
|
||||
// If 'all' is not selected, delete lists by IDs.
|
||||
params.id = this.bulk.checked.map((l) => l.id);
|
||||
} else {
|
||||
// 'All' is selected, delete by query.
|
||||
params.query = this.queryParams.query.replace(/[^\p{L}\p{N}\s]/gu, ' ');
|
||||
}
|
||||
|
||||
this.$api.deleteLists(params)
|
||||
.then(() => {
|
||||
this.getLists();
|
||||
this.$utils.toast(this.$tc(
|
||||
'globals.messages.deletedCount',
|
||||
this.numSelectedLists,
|
||||
{ num: this.numSelectedLists, name },
|
||||
));
|
||||
});
|
||||
};
|
||||
|
||||
this.$utils.confirm(this.$tc(
|
||||
'globals.messages.confirmDelete',
|
||||
this.numSelectedLists,
|
||||
{ num: this.numSelectedLists, name: name.toLowerCase() },
|
||||
), fn);
|
||||
},
|
||||
|
||||
createOptinCampaign(list) {
|
||||
const data = {
|
||||
name: this.$t('lists.optinTo', { name: list.name }),
|
||||
@@ -292,6 +356,10 @@ export default Vue.extend({
|
||||
|
||||
computed: {
|
||||
...mapState(['loading', 'settings']),
|
||||
|
||||
numSelectedLists() {
|
||||
return this.bulk.all ? this.lists.total : this.bulk.checked.length;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
</span>
|
||||
<span v-if="currentList">
|
||||
» {{ currentList.name }}
|
||||
<span v-if="queryParams.subStatus" class="has-text-grey has-text-weight-normal is-capitalized">({{ queryParams.subStatus }})</span>
|
||||
<span v-if="queryParams.subStatus" class="has-text-grey has-text-weight-normal is-capitalized">({{
|
||||
queryParams.subStatus }})</span>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
@@ -92,11 +93,11 @@
|
||||
<b-icon icon="account-off-outline" size="is-small" /> Blocklist
|
||||
</a>
|
||||
<span class="a">
|
||||
{{ $t('subscribers.numSelected', { num: numSelectedSubscribers }) }}
|
||||
{{ $t('globals.messages.numSelected', { num: numSelectedSubscribers }) }}
|
||||
<span v-if="!bulk.all && subscribers.total > subscribers.perPage">
|
||||
—
|
||||
<a href="#" @click.prevent="selectAllSubscribers">
|
||||
{{ $t('subscribers.selectAll', { num: subscribers.total }) }}
|
||||
{{ $t('globals.messages.selectAll', { num: subscribers.total }) }}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user