mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 08:43:25 +08:00
DEV: introducing user-chooser (#8910)
This commit is contained in:
parent
3a906ff0e6
commit
9d50e1b40f
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.[]",
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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"]
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
{{avatar item imageSize="tiny"}}
|
||||
<span class="username">{{format-username item.username}}</span>
|
||||
<span class="name">{{item.name}}</span>
|
|
@ -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";
|
||||
|
|
22
app/assets/stylesheets/common/select-kit/user-row.scss
Normal file
22
app/assets/stylesheets/common/select-kit/user-row.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -239,7 +239,8 @@
|
|||
.category-selector,
|
||||
.tag-chooser,
|
||||
textarea,
|
||||
input.user-selector {
|
||||
input.user-selector,
|
||||
.user-chooser {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user