mirror of
https://github.com/discourse/discourse.git
synced 2025-05-24 03:36:18 +08:00
DEV: {{user-selector}} replacement (#11726)
This PR is the first step towards replacing our `{{user-selector}}` and eventually deprecating and removing it from our codebase. Some of `{{user-selector}}` problems are:
1. It's called `{{user-selector}}`, but in reality in can also select groups and emails.
2. It's an Ember component, yet it doesn't have a handlebars template and uses jQuery to render itself and modify the DOM. An example of this problem is when you want to clear the selected users programmatically, see [this](6c155dba77/app/assets/javascripts/discourse/app/components/user-selector.js (L179-L185)
).
3. We now have select kit which does very similar things but a lot better.
This PR introduces `{{email-group-user-chooser}}` which is meant to replace `{{user-selector}}`. It extends select kit and has the same features that `{{user-selector}}` has. `{{user-selector}}` is still used in a few places in core, but they'll all be replaced with the new component in a separate commit.
Once `{{user-selector}}` is not used anywhere in core, it'll be deprecated and then removed after the 2.7 release.
This commit is contained in:
@ -3,6 +3,7 @@ import I18n from "I18n";
|
|||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import { isBlank } from "@ember/utils";
|
import { isBlank } from "@ember/utils";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { get } from "@ember/object";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
@ -30,6 +31,10 @@ export default Controller.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
updateUsername(selected) {
|
||||||
|
this.set("model.username", get(selected, "firstObject"));
|
||||||
|
},
|
||||||
|
|
||||||
changeUserMode(value) {
|
changeUserMode(value) {
|
||||||
if (value === "all") {
|
if (value === "all") {
|
||||||
this.model.set("username", null);
|
this.model.set("username", null);
|
||||||
|
@ -2,6 +2,7 @@ import { empty, notEmpty, or } from "@ember/object/computed";
|
|||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import EmailPreview from "admin/models/email-preview";
|
import EmailPreview from "admin/models/email-preview";
|
||||||
import bootbox from "bootbox";
|
import bootbox from "bootbox";
|
||||||
|
import { get } from "@ember/object";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
@ -14,6 +15,10 @@ export default Controller.extend({
|
|||||||
htmlEmpty: empty("model.html_content"),
|
htmlEmpty: empty("model.html_content"),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
updateUsername(selected) {
|
||||||
|
this.set("username", get(selected, "firstObject"));
|
||||||
|
},
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
const model = this.model;
|
const model = this.model;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Controller, { inject as controller } from "@ember/controller";
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
import { action } from "@ember/object";
|
import { action, get } from "@ember/object";
|
||||||
import { alias } from "@ember/object/computed";
|
import { alias } from "@ember/object/computed";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
@ -27,4 +27,9 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
close() {
|
close() {
|
||||||
this.send("closeModal");
|
this.send("closeModal");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateUsername(selected) {
|
||||||
|
this.set("targetUsername", get(selected, "firstObject"));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -25,10 +25,14 @@
|
|||||||
|
|
||||||
{{#if showUserSelector}}
|
{{#if showUserSelector}}
|
||||||
{{#admin-form-row label="admin.api.user"}}
|
{{#admin-form-row label="admin.api.user"}}
|
||||||
{{user-selector single="true"
|
{{email-group-user-chooser
|
||||||
usernames=model.username
|
value=model.username
|
||||||
placeholderKey="admin.api.user_placeholder"
|
onChange=(action "updateUsername")
|
||||||
}}
|
options=(hash
|
||||||
|
maximum=1
|
||||||
|
filterPlaceholder="admin.api.user_placeholder"
|
||||||
|
)
|
||||||
|
}}
|
||||||
{{/admin-form-row}}
|
{{/admin-form-row}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -5,7 +5,13 @@
|
|||||||
<label for="last-seen">{{i18n "admin.email.last_seen_user"}}</label>
|
<label for="last-seen">{{i18n "admin.email.last_seen_user"}}</label>
|
||||||
{{date-picker-past value=lastSeen id="last-seen"}}
|
{{date-picker-past value=lastSeen id="last-seen"}}
|
||||||
<label>{{i18n "admin.email.user"}}:</label>
|
<label>{{i18n "admin.email.user"}}:</label>
|
||||||
{{user-selector single="true" usernames=username canReceiveUpdates=true}}
|
{{email-group-user-chooser
|
||||||
|
value=username
|
||||||
|
onChange=(action "updateUsername")
|
||||||
|
options=(hash
|
||||||
|
maximum=1
|
||||||
|
)
|
||||||
|
}}
|
||||||
{{d-button
|
{{d-button
|
||||||
class="btn-primary digest-refresh-button"
|
class="btn-primary digest-refresh-button"
|
||||||
action=(action "refresh")
|
action=(action "refresh")
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
<div>
|
<div>
|
||||||
{{#d-modal-body rawTitle=(i18n "admin.user.merge.prompt.title" username=username)}}
|
{{#d-modal-body rawTitle=(i18n "admin.user.merge.prompt.title" username=username)}}
|
||||||
<p>{{html-safe (i18n "admin.user.merge.prompt.description" username=username)}}</p>
|
<p>{{html-safe (i18n "admin.user.merge.prompt.description" username=username)}}</p>
|
||||||
{{user-selector single=true
|
{{email-group-user-chooser
|
||||||
placeholderKey="admin.user.merge.prompt.target_username_placeholder"
|
value=targetUsername
|
||||||
usernames=targetUsername
|
autocomplete="discourse"
|
||||||
autocomplete="discourse"}}
|
onChange=(action "updateUsername")
|
||||||
|
options=(hash
|
||||||
|
maximum=1
|
||||||
|
filterPlaceholder="admin.user.merge.prompt.target_username_placeholder"
|
||||||
|
)
|
||||||
|
}}
|
||||||
{{/d-modal-body}}
|
{{/d-modal-body}}
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import I18n from "I18n";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
||||||
import { schedule } from "@ember/runloop";
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
showSelector: true,
|
init() {
|
||||||
shouldHide: false,
|
this._super(...arguments);
|
||||||
defaultUsernameCount: 0,
|
this.set("_groups", []);
|
||||||
|
},
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
@ -17,78 +16,34 @@ export default Component.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes("usernames")
|
@discourseComputed("recipients")
|
||||||
_checkWidth() {
|
splitRecipients(recipients) {
|
||||||
let width = 0;
|
return recipients ? recipients.split(",").filter(Boolean) : [];
|
||||||
const $acWrap = $(this.element).find(".ac-wrap");
|
|
||||||
const limit = $acWrap.width();
|
|
||||||
this.set("defaultUsernameCount", 0);
|
|
||||||
|
|
||||||
$acWrap
|
|
||||||
.find(".item")
|
|
||||||
.toArray()
|
|
||||||
.forEach((item) => {
|
|
||||||
width += $(item).outerWidth(true);
|
|
||||||
const result = width < limit;
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
this.incrementProperty("defaultUsernameCount");
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (width >= limit) {
|
|
||||||
this.set("shouldHide", true);
|
|
||||||
} else {
|
|
||||||
this.set("shouldHide", false);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@observes("shouldHide")
|
_updateGroups(selected, newGroups) {
|
||||||
_setFocus() {
|
const groups = [];
|
||||||
const selector =
|
this._groups.forEach((existing) => {
|
||||||
"#reply-control #reply-title, #reply-control .d-editor-input";
|
if (selected.includes(existing)) {
|
||||||
|
groups.addObject(existing);
|
||||||
if (this.shouldHide) {
|
}
|
||||||
$(selector).on("focus.composer-user-selector", () => {
|
});
|
||||||
this.set("showSelector", false);
|
newGroups.forEach((newGroup) => {
|
||||||
this.appEvents.trigger("composer:resize");
|
if (!groups.includes(newGroup)) {
|
||||||
});
|
groups.addObject(newGroup);
|
||||||
} else {
|
}
|
||||||
$(selector).off("focus.composer-user-selector");
|
});
|
||||||
}
|
this.setProperties({
|
||||||
},
|
_groups: groups,
|
||||||
|
hasGroups: groups.length > 0,
|
||||||
@discourseComputed("usernames")
|
});
|
||||||
splitUsernames(usernames) {
|
|
||||||
return usernames.split(",");
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("splitUsernames", "defaultUsernameCount")
|
|
||||||
limitedUsernames(splitUsernames, count) {
|
|
||||||
return splitUsernames.slice(0, count).join(", ");
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("splitUsernames", "defaultUsernameCount")
|
|
||||||
hiddenUsersCount(splitUsernames, count) {
|
|
||||||
return `${splitUsernames.length - count} ${I18n.t("more")}`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleSelector() {
|
updateRecipients(selected, content) {
|
||||||
this.set("showSelector", true);
|
const newGroups = content.filterBy("isGroup").mapBy("id");
|
||||||
|
this._updateGroups(selected, newGroups);
|
||||||
schedule("afterRender", () => {
|
this.set("recipients", selected.join(","));
|
||||||
$(this.element).find("input").focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
triggerResize() {
|
|
||||||
this.appEvents.trigger("composer:resize");
|
|
||||||
const $this = $(this.element).find(".ac-wrap");
|
|
||||||
if ($this.height() >= 150) {
|
|
||||||
$this.scrollTop($this.height());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -20,9 +20,9 @@ export default Component.extend({
|
|||||||
isStaff: readOnly("currentUser.staff"),
|
isStaff: readOnly("currentUser.staff"),
|
||||||
isAdmin: readOnly("currentUser.admin"),
|
isAdmin: readOnly("currentUser.admin"),
|
||||||
|
|
||||||
// If this isn't defined, it will proxy to the user topic on the preferences
|
// invitee is either a user, group or email
|
||||||
// page which is wrong.
|
invitee: null,
|
||||||
emailOrUsername: null,
|
isInviteeGroup: false,
|
||||||
hasCustomMessage: false,
|
hasCustomMessage: false,
|
||||||
customMessage: null,
|
customMessage: null,
|
||||||
inviteIcon: "envelope",
|
inviteIcon: "envelope",
|
||||||
@ -41,7 +41,7 @@ export default Component.extend({
|
|||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
"isAdmin",
|
"isAdmin",
|
||||||
"emailOrUsername",
|
"invitee",
|
||||||
"invitingToTopic",
|
"invitingToTopic",
|
||||||
"isPrivateTopic",
|
"isPrivateTopic",
|
||||||
"groupIds",
|
"groupIds",
|
||||||
@ -50,7 +50,7 @@ export default Component.extend({
|
|||||||
)
|
)
|
||||||
disabled(
|
disabled(
|
||||||
isAdmin,
|
isAdmin,
|
||||||
emailOrUsername,
|
invitee,
|
||||||
invitingToTopic,
|
invitingToTopic,
|
||||||
isPrivateTopic,
|
isPrivateTopic,
|
||||||
groupIds,
|
groupIds,
|
||||||
@ -60,24 +60,22 @@ export default Component.extend({
|
|||||||
if (saving) {
|
if (saving) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (isEmpty(emailOrUsername)) {
|
if (isEmpty(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailTrimmed = emailOrUsername.trim();
|
|
||||||
|
|
||||||
// when inviting to forum, email must be valid
|
// when inviting to forum, email must be valid
|
||||||
if (!invitingToTopic && !emailValid(emailTrimmed)) {
|
if (!invitingToTopic && !emailValid(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal users (not admin) can't invite users to private topic via email
|
// normal users (not admin) can't invite users to private topic via email
|
||||||
if (!isAdmin && isPrivateTopic && emailValid(emailTrimmed)) {
|
if (!isAdmin && isPrivateTopic && emailValid(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when inviting to private topic via email, group name must be specified
|
// when inviting to private topic via email, group name must be specified
|
||||||
if (isPrivateTopic && isEmpty(groupIds) && emailValid(emailTrimmed)) {
|
if (isPrivateTopic && isEmpty(groupIds) && emailValid(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +88,7 @@ export default Component.extend({
|
|||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
"isAdmin",
|
"isAdmin",
|
||||||
"emailOrUsername",
|
"invitee",
|
||||||
"inviteModel.saving",
|
"inviteModel.saving",
|
||||||
"isPrivateTopic",
|
"isPrivateTopic",
|
||||||
"groupIds",
|
"groupIds",
|
||||||
@ -98,7 +96,7 @@ export default Component.extend({
|
|||||||
)
|
)
|
||||||
disabledCopyLink(
|
disabledCopyLink(
|
||||||
isAdmin,
|
isAdmin,
|
||||||
emailOrUsername,
|
invitee,
|
||||||
saving,
|
saving,
|
||||||
isPrivateTopic,
|
isPrivateTopic,
|
||||||
groupIds,
|
groupIds,
|
||||||
@ -110,24 +108,22 @@ export default Component.extend({
|
|||||||
if (saving) {
|
if (saving) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (isEmpty(emailOrUsername)) {
|
if (isEmpty(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = emailOrUsername.trim();
|
|
||||||
|
|
||||||
// email must be valid
|
// email must be valid
|
||||||
if (!emailValid(email)) {
|
if (!emailValid(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal users (not admin) can't invite users to private topic via email
|
// normal users (not admin) can't invite users to private topic via email
|
||||||
if (!isAdmin && isPrivateTopic && emailValid(email)) {
|
if (!isAdmin && isPrivateTopic && emailValid(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when inviting to private topic via email, group name must be specified
|
// when inviting to private topic via email, group name must be specified
|
||||||
if (isPrivateTopic && isEmpty(groupIds) && emailValid(email)) {
|
if (isPrivateTopic && isEmpty(groupIds) && emailValid(invitee)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +175,7 @@ export default Component.extend({
|
|||||||
// Show Groups? (add invited user to private group)
|
// Show Groups? (add invited user to private group)
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
"isGroupOwnerOrAdmin",
|
"isGroupOwnerOrAdmin",
|
||||||
"emailOrUsername",
|
"invitee",
|
||||||
"isPrivateTopic",
|
"isPrivateTopic",
|
||||||
"isPM",
|
"isPM",
|
||||||
"invitingToTopic",
|
"invitingToTopic",
|
||||||
@ -187,7 +183,7 @@ export default Component.extend({
|
|||||||
)
|
)
|
||||||
showGroups(
|
showGroups(
|
||||||
isGroupOwnerOrAdmin,
|
isGroupOwnerOrAdmin,
|
||||||
emailOrUsername,
|
invitee,
|
||||||
isPrivateTopic,
|
isPrivateTopic,
|
||||||
isPM,
|
isPM,
|
||||||
invitingToTopic,
|
invitingToTopic,
|
||||||
@ -197,20 +193,20 @@ export default Component.extend({
|
|||||||
isGroupOwnerOrAdmin &&
|
isGroupOwnerOrAdmin &&
|
||||||
canInviteViaEmail &&
|
canInviteViaEmail &&
|
||||||
!isPM &&
|
!isPM &&
|
||||||
(emailValid(emailOrUsername) || isPrivateTopic || !invitingToTopic)
|
(emailValid(invitee) || isPrivateTopic || !invitingToTopic)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("emailOrUsername")
|
@discourseComputed("invitee")
|
||||||
showCustomMessage(emailOrUsername) {
|
showCustomMessage(invitee) {
|
||||||
return this.inviteModel === this.currentUser || emailValid(emailOrUsername);
|
return this.inviteModel === this.currentUser || emailValid(invitee);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Instructional text for the modal.
|
// Instructional text for the modal.
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
"isPM",
|
"isPM",
|
||||||
"invitingToTopic",
|
"invitingToTopic",
|
||||||
"emailOrUsername",
|
"invitee",
|
||||||
"isPrivateTopic",
|
"isPrivateTopic",
|
||||||
"isAdmin",
|
"isAdmin",
|
||||||
"canInviteViaEmail"
|
"canInviteViaEmail"
|
||||||
@ -218,7 +214,7 @@ export default Component.extend({
|
|||||||
inviteInstructions(
|
inviteInstructions(
|
||||||
isPM,
|
isPM,
|
||||||
invitingToTopic,
|
invitingToTopic,
|
||||||
emailOrUsername,
|
invitee,
|
||||||
isPrivateTopic,
|
isPrivateTopic,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
canInviteViaEmail
|
canInviteViaEmail
|
||||||
@ -236,9 +232,9 @@ export default Component.extend({
|
|||||||
return I18n.t("topic.invite_reply.to_username");
|
return I18n.t("topic.invite_reply.to_username");
|
||||||
} else {
|
} else {
|
||||||
// when inviting to a topic, display instructions based on provided entity
|
// when inviting to a topic, display instructions based on provided entity
|
||||||
if (isEmpty(emailOrUsername)) {
|
if (isEmpty(invitee)) {
|
||||||
return I18n.t("topic.invite_reply.to_topic_blank");
|
return I18n.t("topic.invite_reply.to_topic_blank");
|
||||||
} else if (emailValid(emailOrUsername)) {
|
} else if (emailValid(invitee)) {
|
||||||
this.set("inviteIcon", "envelope");
|
this.set("inviteIcon", "envelope");
|
||||||
return I18n.t("topic.invite_reply.to_topic_email");
|
return I18n.t("topic.invite_reply.to_topic_email");
|
||||||
} else {
|
} else {
|
||||||
@ -257,18 +253,18 @@ export default Component.extend({
|
|||||||
return isPrivateTopic ? "required" : "optional";
|
return isPrivateTopic ? "required" : "optional";
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("isPM", "emailOrUsername", "invitingExistingUserToTopic")
|
@discourseComputed("isPM", "invitee", "invitingExistingUserToTopic")
|
||||||
successMessage(isPM, emailOrUsername, invitingExistingUserToTopic) {
|
successMessage(isPM, invitee, invitingExistingUserToTopic) {
|
||||||
if (this.hasGroups) {
|
if (this.isInviteeGroup) {
|
||||||
return I18n.t("topic.invite_private.success_group");
|
return I18n.t("topic.invite_private.success_group");
|
||||||
} else if (isPM) {
|
} else if (isPM) {
|
||||||
return I18n.t("topic.invite_private.success");
|
return I18n.t("topic.invite_private.success");
|
||||||
} else if (invitingExistingUserToTopic) {
|
} else if (invitingExistingUserToTopic) {
|
||||||
return I18n.t("topic.invite_reply.success_existing_email", {
|
return I18n.t("topic.invite_reply.success_existing_email", {
|
||||||
emailOrUsername,
|
invitee,
|
||||||
});
|
});
|
||||||
} else if (emailValid(emailOrUsername)) {
|
} else if (emailValid(invitee)) {
|
||||||
return I18n.t("topic.invite_reply.success_email", { emailOrUsername });
|
return I18n.t("topic.invite_reply.success_email", { invitee });
|
||||||
} else {
|
} else {
|
||||||
return I18n.t("topic.invite_reply.success_username");
|
return I18n.t("topic.invite_reply.success_username");
|
||||||
}
|
}
|
||||||
@ -295,7 +291,8 @@ export default Component.extend({
|
|||||||
// Reset the modal to allow a new user to be invited.
|
// Reset the modal to allow a new user to be invited.
|
||||||
reset() {
|
reset() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
emailOrUsername: null,
|
invitee: null,
|
||||||
|
isInviteeGroup: false,
|
||||||
hasCustomMessage: false,
|
hasCustomMessage: false,
|
||||||
customMessage: null,
|
customMessage: null,
|
||||||
invitingExistingUserToTopic: false,
|
invitingExistingUserToTopic: false,
|
||||||
@ -346,9 +343,9 @@ export default Component.extend({
|
|||||||
model.setProperties({ saving: false, error: true });
|
model.setProperties({ saving: false, error: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.hasGroups) {
|
if (this.isInviteeGroup) {
|
||||||
return this.inviteModel
|
return this.inviteModel
|
||||||
.createGroupInvite(this.emailOrUsername.trim())
|
.createGroupInvite(this.invitee.trim())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
model.setProperties({ saving: false, finished: true });
|
model.setProperties({ saving: false, finished: true });
|
||||||
this.get("inviteModel.details.allowed_groups").pushObject(
|
this.get("inviteModel.details.allowed_groups").pushObject(
|
||||||
@ -359,7 +356,7 @@ export default Component.extend({
|
|||||||
.catch(onerror);
|
.catch(onerror);
|
||||||
} else {
|
} else {
|
||||||
return this.inviteModel
|
return this.inviteModel
|
||||||
.createInvite(this.emailOrUsername.trim(), groupIds, this.customMessage)
|
.createInvite(this.invitee.trim(), groupIds, this.customMessage)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
model.setProperties({ saving: false, finished: true });
|
model.setProperties({ saving: false, finished: true });
|
||||||
if (!this.invitingToTopic && userInvitedController) {
|
if (!this.invitingToTopic && userInvitedController) {
|
||||||
@ -379,7 +376,7 @@ export default Component.extend({
|
|||||||
this.appEvents.trigger("post-stream:refresh", { force: true });
|
this.appEvents.trigger("post-stream:refresh", { force: true });
|
||||||
} else if (
|
} else if (
|
||||||
this.invitingToTopic &&
|
this.invitingToTopic &&
|
||||||
emailValid(this.emailOrUsername.trim()) &&
|
emailValid(this.invitee.trim()) &&
|
||||||
result &&
|
result &&
|
||||||
result.user
|
result.user
|
||||||
) {
|
) {
|
||||||
@ -407,7 +404,7 @@ export default Component.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return model
|
return model
|
||||||
.generateInviteLink(this.emailOrUsername.trim(), groupIds, topicId)
|
.generateInviteLink(this.invitee.trim(), groupIds, topicId)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
model.setProperties({
|
model.setProperties({
|
||||||
saving: false,
|
saving: false,
|
||||||
@ -465,7 +462,23 @@ export default Component.extend({
|
|||||||
@action
|
@action
|
||||||
searchContact() {
|
searchContact() {
|
||||||
getNativeContact(this.capabilities, ["email"], false).then((result) => {
|
getNativeContact(this.capabilities, ["email"], false).then((result) => {
|
||||||
this.set("emailOrUsername", result[0].email[0]);
|
this.set("invitee", result[0].email[0]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateInvitee(selected, content) {
|
||||||
|
const invitee = content.findBy("id", selected[0]);
|
||||||
|
if (invitee) {
|
||||||
|
this.setProperties({
|
||||||
|
invitee: invitee.id.trim(),
|
||||||
|
isInviteeGroup: invitee.isGroup || false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setProperties({
|
||||||
|
invitee: null,
|
||||||
|
isInviteeGroup: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
{{#if showSelector}}
|
{{email-group-user-chooser
|
||||||
{{user-selector
|
id="private-message-users"
|
||||||
|
tabindex="1"
|
||||||
|
autocomplete="discourse"
|
||||||
|
value=splitRecipients
|
||||||
|
onChange=(action "updateRecipients")
|
||||||
|
options=(hash
|
||||||
topicId=topicId
|
topicId=topicId
|
||||||
onChangeCallback=(action "triggerResize")
|
filterPlaceholder="composer.users_placeholder"
|
||||||
id="private-message-users"
|
includeMessageableGroups=true
|
||||||
includeMessageableGroups="true"
|
allowEmails=true
|
||||||
placeholderKey="composer.users_placeholder"
|
autoWrap=true
|
||||||
tabindex="1"
|
)
|
||||||
usernames=usernames
|
}}
|
||||||
hasGroups=hasGroups
|
|
||||||
allowEmails="true"
|
|
||||||
autocomplete="discourse"
|
|
||||||
canReceiveUpdates=true
|
|
||||||
}}
|
|
||||||
{{else}}
|
|
||||||
<a href {{action "toggleSelector"}}>
|
|
||||||
<div class="ac-wrap composer-user-selector-limited">
|
|
||||||
<span>{{limitedUsernames}}</span>
|
|
||||||
<span class="btn btn-primary">{{hiddenUsersCount}}</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{{/if}}
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="body">
|
<div class="body">
|
||||||
{{#if inviteModel.finished}}
|
{{#if inviteModel.finished}}
|
||||||
{{#if inviteModel.inviteLink}}
|
{{#if inviteModel.inviteLink}}
|
||||||
{{generated-invite-link link=inviteModel.inviteLink email=emailOrUsername}}
|
{{generated-invite-link link=inviteModel.inviteLink email=invitee}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="success-message">
|
<div class="success-message">
|
||||||
{{html-safe successMessage}}
|
{{html-safe successMessage}}
|
||||||
@ -18,24 +18,23 @@
|
|||||||
<label class="instructions">{{inviteInstructions}}</label>
|
<label class="instructions">{{inviteInstructions}}</label>
|
||||||
<div class="invite-user-input-wrapper">
|
<div class="invite-user-input-wrapper">
|
||||||
{{#if allowExistingMembers}}
|
{{#if allowExistingMembers}}
|
||||||
{{user-selector
|
{{email-group-user-chooser
|
||||||
fullWidthWrap=true
|
|
||||||
single=true
|
|
||||||
allowAny=true
|
|
||||||
excludeCurrentUser=true
|
|
||||||
includeMessageableGroups=isPM
|
|
||||||
hasGroups=hasGroups
|
|
||||||
usernames=emailOrUsername
|
|
||||||
placeholderKey=placeholderKey
|
|
||||||
allowEmails=canInviteViaEmail
|
|
||||||
class="invite-user-input"
|
class="invite-user-input"
|
||||||
autocomplete="discourse"
|
autocomplete="discourse"
|
||||||
value=emailOrUsername
|
value=invitee
|
||||||
|
onChange=(action "updateInvitee")
|
||||||
|
options=(hash
|
||||||
|
maximum=1
|
||||||
|
allowEmails=canInviteViaEmail
|
||||||
|
excludeCurrentUser=true
|
||||||
|
includeMessageableGroups=isPM
|
||||||
|
filterPlaceholder=placeholderKey
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{text-field
|
{{text-field
|
||||||
class="email-or-username-input"
|
class="email-or-username-input"
|
||||||
value=emailOrUsername
|
value=invitee
|
||||||
placeholderKey="topic.invite_reply.email_placeholder"}}
|
placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if capabilities.hasContactPicker}}
|
{{#if capabilities.hasContactPicker}}
|
||||||
|
@ -52,11 +52,13 @@
|
|||||||
{{#if model.canEditTitle}}
|
{{#if model.canEditTitle}}
|
||||||
{{#if model.creatingPrivateMessage}}
|
{{#if model.creatingPrivateMessage}}
|
||||||
<div class="user-selector">
|
<div class="user-selector">
|
||||||
{{composer-user-selector topicId=topicModel.id
|
{{composer-user-selector
|
||||||
usernames=model.targetRecipients
|
topicId=topicModel.id
|
||||||
hasGroups=model.hasTargetGroups
|
recipients=model.targetRecipients
|
||||||
focusTarget=focusTarget
|
hasGroups=model.hasTargetGroups
|
||||||
class="users-input"}}
|
focusTarget=focusTarget
|
||||||
|
class="users-input"
|
||||||
|
}}
|
||||||
{{#if showWarning}}
|
{{#if showWarning}}
|
||||||
<label class="add-warning">
|
<label class="add-warning">
|
||||||
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
|
{{input type="checkbox" checked=model.isWarning tabindex="3"}}
|
||||||
|
@ -62,7 +62,9 @@ acceptance("Composer Actions", function (needs) {
|
|||||||
await composerActions.selectRowByValue("reply_as_private_message");
|
await composerActions.selectRowByValue("reply_as_private_message");
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
queryAll(".users-input .item:nth-of-type(1)").text(),
|
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||||
|
.text()
|
||||||
|
.trim(),
|
||||||
"codinghorror"
|
"codinghorror"
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
@ -164,7 +166,7 @@ acceptance("Composer Actions", function (needs) {
|
|||||||
await composerActions.selectRowByValue("reply_as_new_group_message");
|
await composerActions.selectRowByValue("reply_as_new_group_message");
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
queryAll(".users-input .item").each((_, item) =>
|
queryAll("#private-message-users .selected-name").each((_, item) =>
|
||||||
items.push(item.textContent.trim())
|
items.push(item.textContent.trim())
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -348,7 +350,9 @@ acceptance("Composer Actions", function (needs) {
|
|||||||
await composerActions.selectRowByValue("reply_as_private_message");
|
await composerActions.selectRowByValue("reply_as_private_message");
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
queryAll(".users-input .item:nth-of-type(1)").text(),
|
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||||
|
.text()
|
||||||
|
.trim(),
|
||||||
"uwe_keim"
|
"uwe_keim"
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
|
@ -713,7 +713,9 @@ acceptance("Composer", function (needs) {
|
|||||||
await click(".modal .btn-default");
|
await click(".modal .btn-default");
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
queryAll(".users-input .item:nth-of-type(1)").text(),
|
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||||
|
.text()
|
||||||
|
.trim(),
|
||||||
"codinghorror"
|
"codinghorror"
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -215,7 +215,7 @@ acceptance("Group - Authenticated", function (needs) {
|
|||||||
|
|
||||||
assert.ok(count("#reply-control") === 1, "it opens the composer");
|
assert.ok(count("#reply-control") === 1, "it opens the composer");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
queryAll(".ac-wrap .item").text(),
|
queryAll("#private-message-users .selected-name").text().trim(),
|
||||||
"discourse",
|
"discourse",
|
||||||
"it prefills the group name"
|
"it prefills the group name"
|
||||||
);
|
);
|
||||||
|
@ -36,7 +36,9 @@ acceptance("New Message - Authenticated", function (needs) {
|
|||||||
"it pre-fills message body"
|
"it pre-fills message body"
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
queryAll(".users-input .item:nth-of-type(1)").text().trim(),
|
queryAll("#private-message-users .selected-name:nth-of-type(1)")
|
||||||
|
.text()
|
||||||
|
.trim(),
|
||||||
"charlie",
|
"charlie",
|
||||||
"it selects correct username"
|
"it selects correct username"
|
||||||
);
|
);
|
||||||
|
@ -67,22 +67,25 @@ acceptance("Topic", function (needs) {
|
|||||||
"it fills composer with the ring string"
|
"it fills composer with the ring string"
|
||||||
);
|
);
|
||||||
|
|
||||||
const targets = queryAll(".item span", ".composer-fields");
|
const targets = queryAll(
|
||||||
|
"#private-message-users .selected-name",
|
||||||
|
".composer-fields"
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$(targets[0]).text(),
|
$(targets[0]).text().trim(),
|
||||||
"someguy",
|
"someguy",
|
||||||
"it fills up the composer with the right user to start the PM to"
|
"it fills up the composer with the right user to start the PM to"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$(targets[1]).text(),
|
$(targets[1]).text().trim(),
|
||||||
"test",
|
"test",
|
||||||
"it fills up the composer with the right user to start the PM to"
|
"it fills up the composer with the right user to start the PM to"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
$(targets[2]).text(),
|
$(targets[2]).text().trim(),
|
||||||
"Group",
|
"Group",
|
||||||
"it fills up the composer with the right group to start the PM to"
|
"it fills up the composer with the right group to start the PM to"
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import MultiSelectHeaderComponent from "select-kit/components/multi-select/multi-select-header";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import { gt } from "@ember/object/computed";
|
||||||
|
import { isTesting } from "discourse-common/config/environment";
|
||||||
|
import layout from "select-kit/templates/components/email-group-user-chooser-header";
|
||||||
|
|
||||||
|
export default MultiSelectHeaderComponent.extend({
|
||||||
|
layout,
|
||||||
|
classNames: ["email-group-user-chooser-header"],
|
||||||
|
hasHiddenItems: gt("hiddenItemsCount", 0),
|
||||||
|
|
||||||
|
shownItems: computed("hiddenItemsCount", function () {
|
||||||
|
if (
|
||||||
|
this.selectKit.noneItem === this.selectedContent ||
|
||||||
|
this.hiddenItemsCount === 0
|
||||||
|
) {
|
||||||
|
return this.selectedContent;
|
||||||
|
}
|
||||||
|
return this.selectedContent.slice(
|
||||||
|
0,
|
||||||
|
this.selectedContent.length - this.hiddenItemsCount
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
hiddenItemsCount: computed(
|
||||||
|
"selectedContent.[]",
|
||||||
|
"selectKit.options.autoWrap",
|
||||||
|
"selectKit.isExpanded",
|
||||||
|
function () {
|
||||||
|
if (
|
||||||
|
!this.selectKit.options.autoWrap ||
|
||||||
|
this.selectKit.isExpanded ||
|
||||||
|
this.selectedContent === this.selectKit.noneItem ||
|
||||||
|
this.selectedContent.length <= 1 ||
|
||||||
|
isTesting()
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
const selectKitHeaderWidth = this.element.offsetWidth;
|
||||||
|
const choices = this.element.querySelectorAll(".selected-name.choice");
|
||||||
|
const input = this.element.querySelector(".filter-input");
|
||||||
|
const alreadyHidden = this.element.querySelector(".x-more-item");
|
||||||
|
if (alreadyHidden) {
|
||||||
|
const hiddenCount = parseInt(
|
||||||
|
alreadyHidden.getAttribute("data-hidden-count"),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
hiddenCount +
|
||||||
|
(this.selectedContent.length - (choices.length + hiddenCount))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (choices.length === 0 && this.selectedContent.length > 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let total = choices[0].offsetWidth + input.offsetWidth;
|
||||||
|
let shownItemsCount = 1;
|
||||||
|
let shouldHide = false;
|
||||||
|
for (let i = 1; i < choices.length - 1; i++) {
|
||||||
|
const currentWidth = choices[i].offsetWidth;
|
||||||
|
const nextWidth = choices[i + 1].offsetWidth;
|
||||||
|
const ratio =
|
||||||
|
(total + currentWidth + nextWidth) / selectKitHeaderWidth;
|
||||||
|
if (ratio >= 0.95) {
|
||||||
|
shouldHide = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
shownItemsCount++;
|
||||||
|
total += currentWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shouldHide ? choices.length - shownItemsCount : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
|
||||||
|
import layout from "select-kit/templates/components/email-group-user-chooser-row";
|
||||||
|
|
||||||
|
export default SelectKitRowComponent.extend({
|
||||||
|
layout,
|
||||||
|
classNames: ["email-group-user-chooser-row"],
|
||||||
|
});
|
@ -0,0 +1,46 @@
|
|||||||
|
import UserChooserComponent from "select-kit/components/user-chooser";
|
||||||
|
|
||||||
|
export default UserChooserComponent.extend({
|
||||||
|
pluginApiIdentifiers: ["email-group-user-chooser"],
|
||||||
|
classNames: ["email-group-user-chooser"],
|
||||||
|
valueProperty: "id",
|
||||||
|
nameProperty: "name",
|
||||||
|
|
||||||
|
modifyComponentForRow() {
|
||||||
|
return "email-group-user-chooser-row";
|
||||||
|
},
|
||||||
|
|
||||||
|
selectKitOptions: {
|
||||||
|
headerComponent: "email-group-user-chooser-header",
|
||||||
|
autoWrap: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
search() {
|
||||||
|
const superPromise = this._super(...arguments);
|
||||||
|
if (!superPromise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return superPromise.then((results) => {
|
||||||
|
if (!results || results.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return results.map((item) => {
|
||||||
|
const reconstructed = {};
|
||||||
|
if (item.username) {
|
||||||
|
reconstructed.id = item.username;
|
||||||
|
if (item.username.includes("@")) {
|
||||||
|
reconstructed.isEmail = true;
|
||||||
|
} else {
|
||||||
|
reconstructed.isUser = true;
|
||||||
|
reconstructed.name = item.name;
|
||||||
|
}
|
||||||
|
} else if (item.name) {
|
||||||
|
reconstructed.id = item.name;
|
||||||
|
reconstructed.name = item.full_name;
|
||||||
|
reconstructed.isGroup = true;
|
||||||
|
}
|
||||||
|
return Object.assign({}, item, reconstructed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -87,6 +87,7 @@ export default Component.extend(
|
|||||||
isHidden: false,
|
isHidden: false,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
isFilterExpanded: false,
|
isFilterExpanded: false,
|
||||||
|
enterDisabled: false,
|
||||||
hasSelection: false,
|
hasSelection: false,
|
||||||
hasNoContent: true,
|
hasNoContent: true,
|
||||||
highlighted: null,
|
highlighted: null,
|
||||||
@ -570,64 +571,75 @@ export default Component.extend(
|
|||||||
|
|
||||||
_searchWrapper(filter) {
|
_searchWrapper(filter) {
|
||||||
this.clearErrors();
|
this.clearErrors();
|
||||||
this.setProperties({ mainCollection: [], "selectKit.isLoading": true });
|
this.setProperties({
|
||||||
|
mainCollection: [],
|
||||||
|
"selectKit.isLoading": true,
|
||||||
|
"selectKit.enterDisabled": true,
|
||||||
|
});
|
||||||
this._safeAfterRender(() => this.popper && this.popper.update());
|
this._safeAfterRender(() => this.popper && this.popper.update());
|
||||||
|
|
||||||
let content = [];
|
let content = [];
|
||||||
|
|
||||||
return Promise.resolve(this.search(filter)).then((result) => {
|
return Promise.resolve(this.search(filter))
|
||||||
content = content.concat(makeArray(result));
|
.then((result) => {
|
||||||
content = this.selectKit.modifyContent(content).filter(Boolean);
|
content = content.concat(makeArray(result));
|
||||||
|
content = this.selectKit.modifyContent(content).filter(Boolean);
|
||||||
|
|
||||||
if (this.selectKit.valueProperty) {
|
if (this.selectKit.valueProperty) {
|
||||||
content = content.uniqBy(this.selectKit.valueProperty);
|
content = content.uniqBy(this.selectKit.valueProperty);
|
||||||
} else {
|
} else {
|
||||||
content = content.uniq();
|
content = content.uniq();
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectKit.options.limitMatches) {
|
|
||||||
content = content.slice(0, this.selectKit.options.limitMatches);
|
|
||||||
}
|
|
||||||
|
|
||||||
const noneItem = this.selectKit.noneItem;
|
|
||||||
if (
|
|
||||||
this.selectKit.options.allowAny &&
|
|
||||||
filter &&
|
|
||||||
this.getName(noneItem) !== filter
|
|
||||||
) {
|
|
||||||
filter = this.createContentFromInput(filter);
|
|
||||||
if (this.validateCreate(filter, content)) {
|
|
||||||
this.selectKit.set("newItem", this.defaultItem(filter, filter));
|
|
||||||
content.unshift(this.selectKit.newItem);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const hasNoContent = isEmpty(content);
|
if (this.selectKit.options.limitMatches) {
|
||||||
|
content = content.slice(0, this.selectKit.options.limitMatches);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
const noneItem = this.selectKit.noneItem;
|
||||||
this.selectKit.hasSelection &&
|
if (
|
||||||
noneItem &&
|
this.selectKit.options.allowAny &&
|
||||||
this.selectKit.options.autoInsertNoneItem
|
filter &&
|
||||||
) {
|
this.getName(noneItem) !== filter
|
||||||
content.unshift(noneItem);
|
) {
|
||||||
}
|
filter = this.createContentFromInput(filter);
|
||||||
|
if (this.validateCreate(filter, content)) {
|
||||||
|
this.selectKit.set("newItem", this.defaultItem(filter, filter));
|
||||||
|
content.unshift(this.selectKit.newItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.set("mainCollection", content);
|
const hasNoContent = isEmpty(content);
|
||||||
|
|
||||||
this.selectKit.setProperties({
|
if (
|
||||||
highlighted:
|
this.selectKit.hasSelection &&
|
||||||
this.singleSelect && this.value
|
noneItem &&
|
||||||
? this.itemForValue(this.value, this.mainCollection)
|
this.selectKit.options.autoInsertNoneItem
|
||||||
: this.mainCollection.firstObject,
|
) {
|
||||||
isLoading: false,
|
content.unshift(noneItem);
|
||||||
hasNoContent,
|
}
|
||||||
|
|
||||||
|
this.set("mainCollection", content);
|
||||||
|
|
||||||
|
this.selectKit.setProperties({
|
||||||
|
highlighted:
|
||||||
|
this.singleSelect && this.value
|
||||||
|
? this.itemForValue(this.value, this.mainCollection)
|
||||||
|
: this.mainCollection.firstObject,
|
||||||
|
isLoading: false,
|
||||||
|
hasNoContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._safeAfterRender(() => {
|
||||||
|
this.popper && this.popper.update();
|
||||||
|
this._focusFilter();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (this.isDestroyed || this.isDestroying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.set("selectKit.enterDisabled", false);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._safeAfterRender(() => {
|
|
||||||
this.popper && this.popper.update();
|
|
||||||
this._focusFilter();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_safeAfterRender(fn) {
|
_safeAfterRender(fn) {
|
||||||
@ -854,11 +866,12 @@ export default Component.extend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const popperElement = data.state.elements.popper;
|
const popperElement = data.state.elements.popper;
|
||||||
if (
|
const topPlacement =
|
||||||
popperElement &&
|
popperElement &&
|
||||||
popperElement.getAttribute("data-popper-placement") ===
|
popperElement
|
||||||
"top-start"
|
.getAttribute("data-popper-placement")
|
||||||
) {
|
.startsWith("top-");
|
||||||
|
if (topPlacement) {
|
||||||
this.element.classList.remove("is-under");
|
this.element.classList.remove("is-under");
|
||||||
this.element.classList.add("is-above");
|
this.element.classList.add("is-above");
|
||||||
} else {
|
} else {
|
||||||
@ -868,6 +881,20 @@ export default Component.extend(
|
|||||||
|
|
||||||
wrapper.style.width = `${this.element.offsetWidth}px`;
|
wrapper.style.width = `${this.element.offsetWidth}px`;
|
||||||
wrapper.style.height = `${height}px`;
|
wrapper.style.height = `${height}px`;
|
||||||
|
if (placementStrategy === "fixed") {
|
||||||
|
const rects = this.element.getClientRects()[0];
|
||||||
|
const bodyRects = body && body.getClientRects()[0];
|
||||||
|
wrapper.style.position = "fixed";
|
||||||
|
wrapper.style.left = `${rects.left}px`;
|
||||||
|
if (topPlacement && bodyRects) {
|
||||||
|
wrapper.style.top = `${rects.top - bodyRects.height}px`;
|
||||||
|
} else {
|
||||||
|
wrapper.style.top = `${rects.top}px`;
|
||||||
|
}
|
||||||
|
if (isDocumentRTL()) {
|
||||||
|
wrapper.style.right = "unset";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -56,6 +56,16 @@ export default Component.extend(UtilsMixin, {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onKeyup(event) {
|
||||||
|
if (event.keyCode === 13 && this.selectKit.enterDisabled) {
|
||||||
|
this.element.querySelector("input").focus();
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
onKeydown(event) {
|
onKeydown(event) {
|
||||||
if (!this.selectKit.onKeydown(event)) {
|
if (!this.selectKit.onKeydown(event)) {
|
||||||
return false;
|
return false;
|
||||||
@ -93,8 +103,15 @@ export default Component.extend(UtilsMixin, {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.keyCode === 13 && !this.selectKit.highlighted) {
|
if (
|
||||||
|
event.keyCode === 13 &&
|
||||||
|
(!this.selectKit.highlighted || this.selectKit.enterDisabled)
|
||||||
|
) {
|
||||||
this.element.querySelector("input").focus();
|
this.element.querySelector("input").focus();
|
||||||
|
if (this.selectKit.enterDisabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +126,7 @@ export default Component.extend(UtilsMixin, {
|
|||||||
this.selectKit.close(event);
|
this.selectKit.close(event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.selectKit.set("highlighted", null);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -24,6 +24,7 @@ export default MultiSelectComponent.extend({
|
|||||||
includeMessageableGroups: false,
|
includeMessageableGroups: false,
|
||||||
allowEmails: false,
|
allowEmails: false,
|
||||||
groupMembersOf: undefined,
|
groupMembersOf: undefined,
|
||||||
|
excludeCurrentUser: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
content: computed("value.[]", function () {
|
content: computed("value.[]", function () {
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
<div class="choices">
|
||||||
|
{{#each shownItems as |item|}}
|
||||||
|
{{component selectKit.options.selectedNameComponent
|
||||||
|
tabindex=tabindex
|
||||||
|
item=item
|
||||||
|
selectKit=selectKit
|
||||||
|
}}
|
||||||
|
{{/each}}
|
||||||
|
{{#if hasHiddenItems}}
|
||||||
|
<div class="x-more-item" data-hidden-count={{hiddenItemsCount}}>
|
||||||
|
{{i18n "x_more" count=hiddenItemsCount}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#unless hasReachedMaximumSelection}}
|
||||||
|
<div class="choice input-wrapper">
|
||||||
|
{{component selectKit.options.filterComponent
|
||||||
|
selectKit=selectKit
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
@ -0,0 +1,12 @@
|
|||||||
|
{{#if item.isUser}}
|
||||||
|
{{avatar item imageSize="tiny"}}
|
||||||
|
<span class="identifier">{{format-username item.id}}</span>
|
||||||
|
<span class="name">{{item.name}}</span>
|
||||||
|
{{else if item.isGroup}}
|
||||||
|
{{d-icon "users"}}
|
||||||
|
<span class="identifier">{{item.id}}</span>
|
||||||
|
<span class="name">{{item.full_name}}</span>
|
||||||
|
{{else}}
|
||||||
|
{{d-icon "envelope"}}
|
||||||
|
<span class="identifier">{{item.id}}</span>
|
||||||
|
{{/if}}
|
@ -11,6 +11,7 @@
|
|||||||
input=(action "onInput")
|
input=(action "onInput")
|
||||||
paste=(action "onPaste")
|
paste=(action "onPaste")
|
||||||
keyDown=(action "onKeydown")
|
keyDown=(action "onKeydown")
|
||||||
|
keyUp=(action "onKeyup")
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{#if selectKit.options.filterIcon}}
|
{{#if selectKit.options.filterIcon}}
|
||||||
|
@ -100,7 +100,8 @@ table.api-keys {
|
|||||||
|
|
||||||
.value-list,
|
.value-list,
|
||||||
.select-kit,
|
.select-kit,
|
||||||
input[type="text"] {
|
input[type="text"],
|
||||||
|
input[type="text"].filter-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
@import "combo-box";
|
@import "combo-box";
|
||||||
@import "composer-actions";
|
@import "composer-actions";
|
||||||
@import "dropdown-select-box";
|
@import "dropdown-select-box";
|
||||||
|
@import "email-group-user-chooser";
|
||||||
@import "future-date-input-selector";
|
@import "future-date-input-selector";
|
||||||
@import "icon-picker";
|
@import "icon-picker";
|
||||||
@import "list-setting";
|
@import "list-setting";
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
.select-kit.email-group-user-chooser {
|
||||||
|
.select-kit-row.email-group-user-chooser-row {
|
||||||
|
.identifier {
|
||||||
|
color: var(--primary);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
color: var(--primary-high);
|
||||||
|
font-size: $font-down-1;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.avatar,
|
||||||
|
.d-icon {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.select-kit-header {
|
||||||
|
.x-more-item {
|
||||||
|
background: var(--primary-low);
|
||||||
|
padding: 0.25em;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 2px 0 0px 3px;
|
||||||
|
float: left;
|
||||||
|
height: 30px;
|
||||||
|
color: inherit;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -245,6 +245,9 @@ en:
|
|||||||
now: "just now"
|
now: "just now"
|
||||||
read_more: "read more"
|
read_more: "read more"
|
||||||
more: "More"
|
more: "More"
|
||||||
|
x_more:
|
||||||
|
one: "%{count} More"
|
||||||
|
other: "%{count} More"
|
||||||
less: "Less"
|
less: "Less"
|
||||||
never: "never"
|
never: "never"
|
||||||
every_30_minutes: "every 30 minutes"
|
every_30_minutes: "every 30 minutes"
|
||||||
|
Reference in New Issue
Block a user