DEV: introducing user-chooser (#8910)

This commit is contained in:
Joffrey JAFFEUX 2020-02-11 15:54:56 +01:00 committed by GitHub
parent 3a906ff0e6
commit 9d50e1b40f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 251 additions and 37 deletions

View File

@ -1,4 +1,6 @@
import { makeArray } from "discourse-common/lib/helpers";
import { alias, gte, or } from "@ember/object/computed";
import { action, computed } from "@ember/object";
import Controller from "@ember/controller";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { popupAjaxError } from "discourse/lib/ajax-error";
@ -8,19 +10,36 @@ export default Controller.extend(PreferencesTabController, {
userIsMemberOrAbove: gte("model.trust_level", 2),
ignoredEnabled: or("userIsMemberOrAbove", "model.staff"),
mutedUsernames: computed("model.muted_usernames", {
get() {
let usernames = this.model.muted_usernames;
if (typeof usernames === "string") {
usernames = usernames.split(",").filter(Boolean);
}
return makeArray(usernames).uniq();
}
}),
init() {
this._super(...arguments);
this.saveAttrNames = ["muted_usernames", "ignored_usernames"];
},
actions: {
save() {
this.set("saved", false);
return this.model
.save(this.saveAttrNames)
.then(() => this.set("saved", true))
.catch(popupAjaxError);
}
@action
onChangeMutedUsernames(usernames) {
this.model.set("muted_usernames", usernames.uniq().join(","));
},
@action
save() {
this.set("saved", false);
return this.model
.save(this.saveAttrNames)
.then(() => this.set("saved", true))
.catch(popupAjaxError);
}
});

View File

@ -27,7 +27,7 @@ function performSearch(
return;
}
const eagerComplete = term === "" && !!(topicId || categoryId);
const eagerComplete = eagerCompleteSearch(term, topicId || categoryId);
if (term === "" && !eagerComplete) {
// The server returns no results in this case, so no point checking
@ -138,7 +138,7 @@ function organizeResults(r, options) {
// we also ignore if we notice a double space or a string that is only a space
const ignoreRegex = /([\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*,\/:;<=>?\[\]^`{|}~])|\s\s|^\s$|^[^+]*\+[^@]*$/;
function skipSearch(term, allowEmails) {
export function skipSearch(term, allowEmails) {
if (term.indexOf("@") > -1 && !allowEmails) {
return true;
}
@ -146,6 +146,10 @@ function skipSearch(term, allowEmails) {
return !!term.match(ignoreRegex);
}
export function eagerCompleteSearch(term, scopedId) {
return term === "" && !!scopedId;
}
export default function userSearch(options) {
if (options.term && options.term.length > 0 && options.term[0] === "@") {
options.term = options.term.substring(1);

View File

@ -14,7 +14,13 @@
{{d-icon "d-muted" class="icon"}}
<span>{{i18n 'user.muted_users'}}</span>
</label>
{{user-selector excludeCurrentUser=true usernames=model.muted_usernames class="user-selector"}}
{{user-chooser
value=mutedUsernames
onChange=(action "onChangeMutedUsernames")
options=(hash
excludeCurrentUser=true
)
}}
</div>
<div class="instructions">{{i18n 'user.muted_users_instructions'}}</div>
</div>

View File

@ -18,6 +18,7 @@ export default ComboBox.extend(TagsMixin, {
maxTagSearchResults: setting("max_tag_search_results"),
maxTagsPerTopic: setting("max_tags_per_topic"),
highlightedTag: null,
singleSelect: false,
collections: computed(
"mainCollection.[]",

View File

@ -1,7 +1,7 @@
import deprecated from "discourse-common/lib/deprecated";
import SelectKitComponent from "select-kit/components/select-kit";
import { computed } from "@ember/object";
const { makeArray } = Ember;
import { makeArray } from "discourse-common/lib/helpers";
export default SelectKitComponent.extend({
pluginApiIdentifiers: ["multi-select"],

View File

@ -58,6 +58,8 @@ export default Component.extend(
options: null,
valueProperty: "id",
nameProperty: "name",
singleSelect: false,
multiSelect: false,
init() {
this._super(...arguments);
@ -401,6 +403,28 @@ export default Component.extend(
items = [];
}
value = makeArray(value);
items = makeArray(items);
if (this.multiSelect) {
items = items.filter(
i =>
i !== this.newItem &&
i !== this.noneItem &&
this.getValue(i) !== null
);
if (this.selectKit.options.maximum === 1) {
value = value.slice(0, 1);
items = items.slice(0, 1);
}
}
if (this.singleSelect) {
value = value.firstObject || null;
items = items.firstObject || null;
}
this._boundaryActionHandler("onChange", value, items);
resolve(items);
}).finally(() => {

View File

@ -0,0 +1,85 @@
import MultiSelectComponent from "select-kit/components/multi-select";
import { computed } from "@ember/object";
import {
default as userSearch,
skipSearch,
eagerCompleteSearch
} from "discourse/lib/user-search";
import { makeArray } from "discourse-common/lib/helpers";
export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["user-chooser"],
classNames: ["user-chooser"],
valueProperty: "username",
modifyComponentForRow() {
return "user-chooser/user-row";
},
selectKitOptions: {
topicId: undefined,
categoryId: undefined,
includeGroups: false,
allowedUsers: false,
includeMentionableGroups: false,
includeMessageableGroups: false,
allowEmails: false,
groupMembersOf: undefined
},
content: computed("value.[]", function() {
return Ember.makeArray(this.value).map(x => this.defaultItem(x, x));
}),
excludedUsers: computed(
"value",
"currentUser",
"selectKit.options.{excludeCurrentUser,excludedUsernames}",
{
get() {
const options = this.selectKit.options;
let usernames = makeArray(this.value);
if (this.currentUser && options.excludeCurrentUser) {
usernames.concat([this.currentUser.username]);
}
return usernames.concat(options.excludedUsernames || []);
}
}
),
search(filter = "") {
filter = filter || "";
const options = this.selectKit.options;
// prevents doing ajax request for nothing
const skippedSearch = skipSearch(filter, options.allowEmails);
const eagerComplete = eagerCompleteSearch(
filter,
options.topicId || options.categoryId
);
if (skippedSearch || (filter === "" && !eagerComplete)) {
return;
}
return userSearch({
term: filter,
topicId: options.topicId,
categoryId: options.categoryId,
exclude: this.excludedUsers,
includeGroups: options.includeGroups,
allowedUsers: options.allowedUsers,
includeMentionableGroups: options.includeMentionableGroups,
includeMessageableGroups: options.includeMessageableGroups,
groupMembersOf: options.groupMembersOf,
allowEmails: options.allowEmails
}).then(result => {
if (typeof result === "string") {
// do nothing promise probably got cancelled
} else {
return result;
}
});
}
});

View File

@ -0,0 +1,6 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
export default SelectKitRowComponent.extend({
layoutName: "select-kit/templates/components/user-chooser/user-row",
classNames: ["user-row"]
});

View File

@ -0,0 +1,3 @@
{{avatar item imageSize="tiny"}}
<span class="username">{{format-username item.username}}</span>
<span class="name">{{item.name}}</span>

View File

@ -5,30 +5,7 @@
@import "common/foundation/mixins";
@import "common/foundation/variables";
@import "common/foundation/spacing";
@import "common/select-kit/categories-admin-dropdown";
@import "common/select-kit/category-chooser";
@import "common/select-kit/category-drop";
@import "common/select-kit/category-row";
@import "common/select-kit/category-selector";
@import "common/select-kit/combo-box";
@import "common/select-kit/composer-actions";
@import "common/select-kit/dropdown-select-box";
@import "common/select-kit/future-date-input-selector";
@import "common/select-kit/list-setting";
@import "common/select-kit/mini-tag-chooser";
@import "common/select-kit/multi-select";
@import "common/select-kit/notifications-button";
@import "common/select-kit/period-chooser";
@import "common/select-kit/pinned-button";
@import "common/select-kit/select-kit";
@import "common/select-kit/single-select";
@import "common/select-kit/tag-chooser";
@import "common/select-kit/tag-drop";
@import "common/select-kit/icon-picker";
@import "common/select-kit/toolbar-popup-menu-options";
@import "common/select-kit/topic-notifications-button";
@import "common/select-kit/user-notifications-dropdown";
@import "common/select-kit/color-palettes";
@import "common/select-kit/*";
@import "common/components/*";
@import "common/input_tip";
@import "common/topic-entrance";

View File

@ -0,0 +1,22 @@
.user-chooser {
.select-kit-row.user-row {
.avatar {
margin-left: 0;
margin-right: 0.5em;
}
.username {
color: $primary;
white-space: nowrap;
}
.name {
color: $primary-high;
font-size: $font-down-1;
margin-left: 0.5em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}

View File

@ -239,7 +239,8 @@
.category-selector,
.tag-chooser,
textarea,
input.user-selector {
input.user-selector,
.user-chooser {
width: 100%;
}

View File

@ -0,0 +1,58 @@
import componentTest from "helpers/component-test";
import { testSelectKitModule } from "./select-kit-test-helper";
testSelectKitModule("user-chooser");
function template() {
return `{{user-chooser value=value}}`;
}
componentTest("displays usernames", {
template: template(),
beforeEach() {
this.set("value", ["bob", "martin"]);
},
async test(assert) {
assert.equal(this.subject.header().name(), "bob,martin");
}
});
componentTest("can remove a username", {
template: template(),
beforeEach() {
this.set("value", ["bob", "martin"]);
},
async test(assert) {
await this.subject.deselectItem("bob");
assert.equal(this.subject.header().name(), "martin");
}
});
componentTest("can add a username", {
template: template(),
beforeEach() {
this.set("value", ["bob", "martin"]);
const response = object => {
return [200, { "Content-Type": "application/json" }, object];
};
// prettier-ignore
server.get("/u/search/users", () => { //eslint-disable-line
return response({users:[{username: "maja", name: "Maja"}]});
});
},
async test(assert) {
await this.subject.expand();
await this.subject.fillInFilter("maja");
await this.subject.keyboard("enter");
assert.equal(this.subject.header().name(), "bob,martin,maja");
}
});

View File

@ -263,6 +263,14 @@ export default function selectKit(selector) {
return rowHelper(find(selector).find(".select-kit-row.is-highlighted"));
},
async deselectItem(value) {
await click(
find(selector)
.find(".select-kit-header")
.find(`[data-value=${value}]`)
);
},
exists() {
return exists(selector);
}