Skip to content

Commit

Permalink
UX: add bulk-select to mobile topic lists (#15386)
Browse files Browse the repository at this point in the history
  • Loading branch information
awesomerobot committed Jul 26, 2022
1 parent 5894e7d commit b5c1132
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 26 deletions.
@@ -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();
},
});
12 changes: 12 additions & 0 deletions app/assets/javascripts/discourse/app/components/d-navigation.js
Expand Up @@ -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, {
Expand Down Expand Up @@ -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);
Expand Down
@@ -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";
Expand Down Expand Up @@ -34,8 +34,6 @@ export default Component.extend(LoadMore, {
return !!this.changeSort;
},

skipHeader: reads("site.mobileView"),

@discourseComputed("order")
showLikes(order) {
return order === "likes";
Expand Down
@@ -0,0 +1 @@
<DButton @class={{"bulk-select"}} @action={{action "toggleBulkSelect"}} @icon={{"list"}} />
Expand Up @@ -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
Expand Down
@@ -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
Expand Down
Expand Up @@ -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>

Expand Down
@@ -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'>
{{!--
Expand Down
Expand Up @@ -13,7 +13,7 @@
</span>
{{/if ~}}
{{/if ~}}
{{view.localizedName}}
<span>{{view.localizedName}}</span>
{{~#if view.isSorting}}
{{d-icon view.sortIcon}}
{{/if ~}}
Expand Down
Expand Up @@ -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>

Expand Down
@@ -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"
);
});
});
57 changes: 57 additions & 0 deletions app/assets/stylesheets/mobile/topic-list.scss
Expand Up @@ -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;
}
}

0 comments on commit b5c1132

Please sign in to comment.