mirror of
https://github.com/discourse/discourse.git
synced 2025-03-09 14:34:35 +00:00
UX: add bulk-select to mobile topic lists (#15386)
This commit is contained in:
parent
5894e7d30f
commit
b5c1132546
@ -0,0 +1,17 @@
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
|
||||
export default Component.extend({
|
||||
parentController: null,
|
||||
|
||||
@action
|
||||
toggleBulkSelect() {
|
||||
const controller = getOwner(this).lookup(
|
||||
`controller:${this.parentController}`
|
||||
);
|
||||
const selection = controller.selected;
|
||||
controller.toggleProperty("bulkSelectEnabled");
|
||||
selection.clear();
|
||||
},
|
||||
});
|
@ -4,6 +4,7 @@ import NavItem from "discourse/models/nav-item";
|
||||
import bootbox from "bootbox";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default Component.extend(FilterModeMixin, {
|
||||
@ -128,6 +129,17 @@ export default Component.extend(FilterModeMixin, {
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("filterType")
|
||||
notCategoriesRoute(filterType) {
|
||||
return filterType !== "categories";
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
canBulk() {
|
||||
const controller = getOwner(this).lookup("controller:discovery/topics");
|
||||
return controller.canBulkSelect;
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeCategoryNotificationLevel(notificationLevel) {
|
||||
this.category.setNotification(notificationLevel);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { alias, and, reads } from "@ember/object/computed";
|
||||
import { alias, and } from "@ember/object/computed";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import LoadMore from "discourse/mixins/load-more";
|
||||
@ -34,8 +34,6 @@ export default Component.extend(LoadMore, {
|
||||
return !!this.changeSort;
|
||||
},
|
||||
|
||||
skipHeader: reads("site.mobileView"),
|
||||
|
||||
@discourseComputed("order")
|
||||
showLikes(order) {
|
||||
return order === "likes";
|
||||
|
@ -0,0 +1 @@
|
||||
<DButton @class={{"bulk-select"}} @action={{action "toggleBulkSelect"}} @icon={{"list"}} />
|
@ -6,6 +6,9 @@
|
||||
{{/unless}}
|
||||
|
||||
<div class="navigation-controls">
|
||||
{{#if (and this.notCategoriesRoute this.site.mobileView this.canBulk)}}
|
||||
<BulkSelectToggle @parentController={{"discovery/topics"}} @tagName=""/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showCategoryAdmin}}
|
||||
<CategoriesAdminDropdown @onChange={{action "selectCategoryAdminDropdownAction"}} @options={{hash
|
||||
|
@ -1,21 +1,18 @@
|
||||
{{#unless this.skipHeader}}
|
||||
<thead class="topic-list-header">
|
||||
{{raw "topic-list-header"
|
||||
canBulkSelect=this.canBulkSelect
|
||||
toggleInTitle=this.toggleInTitle
|
||||
hideCategory=this.hideCategory
|
||||
showPosters=this.showPosters
|
||||
showLikes=this.showLikes
|
||||
showOpLikes=this.showOpLikes
|
||||
order=this.order
|
||||
ascending=this.ascending
|
||||
sortable=this.sortable
|
||||
listTitle=this.listTitle
|
||||
bulkSelectEnabled=this.bulkSelectEnabled
|
||||
canDoBulkActions=this.canDoBulkActions
|
||||
}}
|
||||
</thead>
|
||||
{{/unless}}
|
||||
<thead class="topic-list-header">
|
||||
{{raw "topic-list-header"
|
||||
canBulkSelect=this.canBulkSelect
|
||||
toggleInTitle=this.toggleInTitle
|
||||
hideCategory=this.hideCategory
|
||||
showPosters=this.showPosters
|
||||
showLikes=this.showLikes
|
||||
showOpLikes=this.showOpLikes
|
||||
order=this.order
|
||||
ascending=this.ascending
|
||||
sortable=this.sortable
|
||||
listTitle=this.listTitle
|
||||
bulkSelectEnabled=this.bulkSelectEnabled
|
||||
canDoBulkActions=this.canDoBulkActions}}
|
||||
</thead>
|
||||
|
||||
<PluginOutlet @name="before-topic-list-body" @args={{hash
|
||||
topics=this.topics
|
||||
|
@ -14,7 +14,24 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if this.hasTopics}}
|
||||
<TopicList @highlightLastVisited={{true}} @showPosters={{true}} @hideCategory={{this.model.hideCategory}} @order={{this.order}} @ascending={{this.ascending}} @topics={{this.model.topics}} @expandGloballyPinned={{this.expandGloballyPinned}} @expandAllPinned={{this.expandAllPinned}} @scrollOnLoad={{true}} @onScroll={{discoveryTopicList.saveScrollPosition}} @category={{this.category}} />
|
||||
<TopicList
|
||||
@ascending={{this.ascending}}
|
||||
@highlightLastVisited={{true}}
|
||||
@showPosters={{true}}
|
||||
@canBulkSelect={{this.canBulkSelect}}
|
||||
@toggleBulkSelect={{action "toggleBulkSelect"}}
|
||||
@updateAutoAddTopicsToBulkSelect={{action "updateAutoAddTopicsToBulkSelect"}}
|
||||
@hideCategory={{this.model.hideCategory}}
|
||||
@order={{this.order}}
|
||||
@bulkSelectEnabled={{this.bulkSelectEnabled}}
|
||||
@bulkSelectAction={{action "refresh"}}
|
||||
@selected={{this.selected}}
|
||||
@expandGloballyPinned={{this.expandGloballyPinned}}
|
||||
@expandAllPinned={{this.expandAllPinned}}
|
||||
@category={{this.category}}
|
||||
@topics={{this.model.topics}}
|
||||
@scrollOnLoad={{true}}
|
||||
@onScroll={{discoveryTopicList.saveScrollPosition}}/>
|
||||
{{/if}}
|
||||
</DiscoveryTopicsList>
|
||||
|
||||
|
@ -1,7 +1,13 @@
|
||||
<td class="topic-list-data">
|
||||
{{~raw-plugin-outlet name="topic-list-before-columns"}}
|
||||
<div class='pull-left'>
|
||||
<a href="{{topic.lastPostUrl}}" data-user-card="{{topic.lastPosterUser.username}}">{{avatar topic.lastPosterUser imageSize="large"}}</a>
|
||||
{{#if bulkSelectEnabled}}
|
||||
<label for="bulk-select-{{topic.id}}">
|
||||
<input type="checkbox" class="bulk-select" id="bulk-select-{{topic.id}}">
|
||||
</label>
|
||||
{{else}}
|
||||
<a href="{{topic.lastPostUrl}}" data-user-card="{{topic.lastPosterUser.username}}">{{avatar topic.lastPosterUser imageSize="large"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='right'>
|
||||
{{!--
|
||||
|
@ -13,7 +13,7 @@
|
||||
</span>
|
||||
{{/if ~}}
|
||||
{{/if ~}}
|
||||
{{view.localizedName}}
|
||||
<span>{{view.localizedName}}</span>
|
||||
{{~#if view.isSorting}}
|
||||
{{d-icon view.sortIcon}}
|
||||
{{/if ~}}
|
||||
|
@ -101,8 +101,13 @@
|
||||
|
||||
<section class="user-content">
|
||||
<div class="list-actions">
|
||||
{{#if (and this.site.mobileView this.showNewPM)}}
|
||||
<DButton @class="btn-primary new-private-message" @action={{route-action "composePrivateMessage"}} @icon="envelope" @label="user.new_private_message" />
|
||||
{{#if this.site.mobileView}}
|
||||
{{#if this.showNewPM}}
|
||||
<DButton @class="btn-primary new-private-message" @action={{route-action "composePrivateMessage"}} @icon="envelope" @label="user.new_private_message" />
|
||||
{{/if}}
|
||||
{{#if this.currentUser.admin}}
|
||||
<BulkSelectToggle @parentController={{"user-topics-list"}} @tagName=""/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
@ -0,0 +1,132 @@
|
||||
import {
|
||||
acceptance,
|
||||
invisible,
|
||||
query,
|
||||
queryAll,
|
||||
updateCurrentUser,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import I18n from "I18n";
|
||||
|
||||
acceptance("Topic - Bulk Actions - Mobile", function (needs) {
|
||||
needs.user();
|
||||
needs.mobileView();
|
||||
|
||||
needs.settings({ tagging_enabled: true });
|
||||
needs.pretender((server, helper) => {
|
||||
server.put("/topics/bulk", () => {
|
||||
return helper.response({
|
||||
topic_ids: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("bulk select - modal", async function (assert) {
|
||||
updateCurrentUser({ moderator: true, enable_defer: true });
|
||||
await visit("/latest");
|
||||
await click("button.bulk-select");
|
||||
|
||||
await click(queryAll("input.bulk-select")[0]);
|
||||
await click(queryAll("input.bulk-select")[1]);
|
||||
|
||||
await click(".bulk-select-actions");
|
||||
|
||||
assert.ok(
|
||||
query("#discourse-modal-title").innerHTML.includes(
|
||||
I18n.t("topics.bulk.actions")
|
||||
),
|
||||
"it opens bulk-select modal"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.change_category")
|
||||
),
|
||||
"it shows an option to change category"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.close_topics")
|
||||
),
|
||||
"it shows an option to close topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.archive_topics")
|
||||
),
|
||||
"it shows an option to archive topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.notification_level")
|
||||
),
|
||||
"it shows an option to update notification level"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.defer")),
|
||||
"it shows an option to reset read"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.unlist_topics")
|
||||
),
|
||||
"it shows an option to unlist topics"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.reset_bump_dates")
|
||||
),
|
||||
"it shows an option to reset bump dates"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.change_tags")
|
||||
),
|
||||
"it shows an option to replace tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.append_tags")
|
||||
),
|
||||
"it shows an option to append tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(
|
||||
I18n.t("topics.bulk.remove_tags")
|
||||
),
|
||||
"it shows an option to remove all tags"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".bulk-buttons").innerHTML.includes(I18n.t("topics.bulk.delete")),
|
||||
"it shows an option to delete topics"
|
||||
);
|
||||
});
|
||||
|
||||
test("bulk select - delete topics", async function (assert) {
|
||||
updateCurrentUser({ moderator: true });
|
||||
await visit("/latest");
|
||||
await click("button.bulk-select");
|
||||
|
||||
await click(queryAll("input.bulk-select")[0]);
|
||||
await click(queryAll("input.bulk-select")[1]);
|
||||
|
||||
await click(".bulk-select-actions");
|
||||
await click(".modal-body .delete-topics");
|
||||
|
||||
assert.ok(
|
||||
invisible(".topic-bulk-actions-modal"),
|
||||
"it closes the bulk select modal"
|
||||
);
|
||||
});
|
||||
});
|
@ -472,3 +472,60 @@ td .main-link {
|
||||
.muted-categories-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
// Bulk select
|
||||
|
||||
.topic-list-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topic-list.sticky-header {
|
||||
.topic-list-header {
|
||||
display: table-header-group;
|
||||
position: sticky;
|
||||
z-index: z("base") + 2;
|
||||
top: var(--header-offset);
|
||||
background: var(--secondary);
|
||||
tr {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.topic-list-data {
|
||||
display: none;
|
||||
&.bulk-select {
|
||||
display: inline-block;
|
||||
}
|
||||
&.default {
|
||||
display: flex;
|
||||
span:not(.bulk-select-topics) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.bulk-select {
|
||||
padding-left: 0.85em; // visual alignment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-select-topics {
|
||||
display: flex;
|
||||
padding-left: 0.85em;
|
||||
.btn {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-list-data .pull-left {
|
||||
label {
|
||||
// bulk select checkbox
|
||||
display: flex;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user