diff --git a/app/assets/javascripts/discourse/app/components/user-selector.js b/app/assets/javascripts/discourse/app/components/user-selector.js deleted file mode 100644 index 17c85727db2..00000000000 --- a/app/assets/javascripts/discourse/app/components/user-selector.js +++ /dev/null @@ -1,195 +0,0 @@ -import { bind, observes, on } from "discourse-common/utils/decorators"; -import TextField from "discourse/components/text-field"; -import { findRawTemplate } from "discourse-common/lib/raw-templates"; -import { isEmpty } from "@ember/utils"; -import userSearch from "discourse/lib/user-search"; -import deprecated from "discourse-common/lib/deprecated"; - -export default TextField.extend({ - autocorrect: false, - autocapitalize: false, - name: "user-selector", - canReceiveUpdates: false, - single: false, - fullWidthWrap: false, - - @on("init") - deprecateComponent() { - deprecated( - "The `` component is deprecated. Please use `` instead.", - { since: "2.7", dropFrom: "2.8", id: "discourse.user-selector-component" } - ); - }, - - @bind - _paste(event) { - let pastedText = ""; - - if (window.clipboardData && window.clipboardData.getData) { - // IE - pastedText = window.clipboardData.getData("Text"); - } else if (event.clipboardData && event.clipboardData.getData) { - pastedText = event.clipboardData.getData("text/plain"); - } - - if (pastedText.length > 0) { - this.importText(pastedText); - event.preventDefault(); - return false; - } - }, - - didUpdateAttrs() { - this._super(...arguments); - - if (this.canReceiveUpdates) { - this._createAutocompleteInstance({ updateData: true }); - } - }, - - @on("willDestroyElement") - _destroyAutocompleteInstance() { - $(this.element).autocomplete("destroy"); - this.element.removeEventListener("paste", this._paste); - }, - - @on("didInsertElement") - _createAutocompleteInstance(opts) { - const bool = (n) => { - const val = this[n]; - return val === true || val === "true"; - }; - - let selected = [], - groups = [], - currentUser = this.currentUser, - includeMentionableGroups = bool("includeMentionableGroups"), - includeMessageableGroups = bool("includeMessageableGroups"), - includeGroups = bool("includeGroups"), - allowedUsers = bool("allowedUsers"), - excludeCurrentUser = bool("excludeCurrentUser"), - single = bool("single"), - allowAny = bool("allowAny"), - disabled = bool("disabled"), - allowEmails = bool("allowEmails"), - fullWidthWrap = bool("fullWidthWrap"); - - const allExcludedUsernames = () => { - // hack works around some issues with allowAny eventing - let usernames = single ? [] : selected; - - if (currentUser && excludeCurrentUser) { - usernames = usernames.concat([currentUser.username]); - } - - return usernames.concat(this.excludedUsernames || []); - }; - - this.element.addEventListener("paste", this._paste); - - const userSelectorComponent = this; - - $(this.element) - .val(this.usernames) - .autocomplete({ - template: findRawTemplate("user-selector-autocomplete"), - disabled, - single, - allowAny, - updateData: opts && opts.updateData ? opts.updateData : false, - fullWidthWrap, - - dataSource(term) { - return userSearch({ - term, - topicId: userSelectorComponent.topicId, - exclude: allExcludedUsernames(), - includeGroups, - allowedUsers, - includeMentionableGroups, - includeMessageableGroups, - groupMembersOf: userSelectorComponent.groupMembersOf, - allowEmails, - }); - }, - - transformComplete(v) { - if (v.username || v.name) { - if (!v.username) { - groups.push(v.name); - } - return v.username || v.name; - } else { - const excludes = allExcludedUsernames(); - return v.usernames.filter((item) => !excludes.includes(item)); - } - }, - - onChangeItems(items) { - let hasGroups = false; - items = items.map((i) => { - if (groups.includes(i)) { - hasGroups = true; - } - return i.username ? i.username : i; - }); - - let previouslySelected = []; - if (Array.isArray(userSelectorComponent.usernames)) { - previouslySelected = userSelectorComponent.usernames; - } else { - if (userSelectorComponent.usernames) { - previouslySelected = userSelectorComponent.usernames.split(","); - } - } - - userSelectorComponent.setProperties({ - usernames: items.join(","), - hasGroups, - }); - selected = items; - - if (userSelectorComponent.onChangeCallback) { - userSelectorComponent.onChangeCallback( - previouslySelected, - selected - ); - } - }, - - reverseTransform(i) { - return { username: i }; - }, - }); - }, - - importText(text) { - let usernames = []; - if ((this.usernames || "").length > 0) { - usernames = this.usernames.split(","); - } - - (text || "").split(/[, \n]+/).forEach((val) => { - val = val.replace(/^@+/, "").trim(); - if ( - val.length > 0 && - (!this.excludedUsernames || !this.excludedUsernames.includes(val)) - ) { - usernames.push(val); - } - }); - this.set("usernames", usernames.uniq().join(",")); - - if (!this.canReceiveUpdates) { - this._createAutocompleteInstance({ updateData: true }); - } - }, - - // THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT - @observes("usernames") - _clearInput() { - if (arguments.length > 1 && isEmpty(this.usernames)) { - $(this.element).parent().find("a").click(); - } - }, -}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js index 386d5f1efcd..8425ea5d67e 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/email-group-user-chooser-test.js @@ -21,7 +21,73 @@ module( await this.subject.expand(); await paste(query(".filter-input"), "foo,bar"); - assert.equal(this.subject.header().value(), "foo,bar"); + assert.strictEqual(this.subject.header().value(), "foo,bar"); + + await paste(query(".filter-input"), "evil,trout"); + assert.strictEqual(this.subject.header().value(), "foo,bar,evil,trout"); + + await paste(query(".filter-input"), "names with spaces"); + assert.strictEqual( + this.subject.header().value(), + "foo,bar,evil,trout,names,with,spaces" + ); + + await paste(query(".filter-input"), "@osama,@sam"); + assert.strictEqual( + this.subject.header().value(), + "foo,bar,evil,trout,names,with,spaces,osama,sam" + ); + + await paste(query(".filter-input"), "new\nlines"); + assert.strictEqual( + this.subject.header().value(), + "foo,bar,evil,trout,names,with,spaces,osama,sam,new,lines" + ); + }); + + test("excluding usernames", async function (assert) { + pretender.get("/u/search/users", () => { + const users = [ + { + username: "osama", + avatar_template: + "https://avatars.discourse.org/v3/letter/t/41988e/{size}.png", + }, + { + username: "joshua", + avatar_template: + "https://avatars.discourse.org/v3/letter/t/41988e/{size}.png", + }, + { + username: "sam", + avatar_template: + "https://avatars.discourse.org/v3/letter/t/41988e/{size}.png", + }, + ]; + return response({ users }); + }); + + this.set("excludedUsernames", ["osama", "joshua"]); + await render( + hbs`` + ); + + await this.subject.expand(); + await this.subject.fillInFilter("a"); + + let suggestions = this.subject.displayedContent().mapBy("id"); + assert.deepEqual(suggestions, ["sam"]); + + this.set("excludedUsernames", ["osama"]); + await render( + hbs`` + ); + + await this.subject.expand(); + await this.subject.fillInFilter("a"); + + suggestions = this.subject.displayedContent().mapBy("id").sort(); + assert.deepEqual(suggestions, ["joshua", "sam"]); }); test("doesn't show user status by default", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-selector-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-selector-test.js deleted file mode 100644 index e0484728125..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/user-selector-test.js +++ /dev/null @@ -1,67 +0,0 @@ -import { module, test } from "qunit"; -import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import { render } from "@ember/test-helpers"; -import { query } from "discourse/tests/helpers/qunit-helpers"; -import { hbs } from "ember-cli-htmlbars"; -import { withSilencedDeprecationsAsync } from "discourse-common/lib/deprecated"; - -function paste(element, text) { - let e = new Event("paste"); - e.clipboardData = { getData: () => text }; - element.dispatchEvent(e); -} - -module("Integration | Component | user-selector", function (hooks) { - setupRenderingTest(hooks); - - test("pasting a list of usernames", async function (assert) { - this.set("usernames", "evil,trout"); - - await withSilencedDeprecationsAsync( - "discourse.user-selector-component", - async () => { - await render( - hbs`` - ); - } - ); - - let element = query(".test-selector"); - - assert.strictEqual(this.get("usernames"), "evil,trout"); - paste(element, "zip,zap,zoom"); - assert.strictEqual(this.get("usernames"), "evil,trout,zip,zap,zoom"); - paste(element, "evil,abc,abc,abc"); - assert.strictEqual(this.get("usernames"), "evil,trout,zip,zap,zoom,abc"); - - this.set("usernames", ""); - paste(element, "names with spaces"); - assert.strictEqual(this.get("usernames"), "names,with,spaces"); - - this.set("usernames", null); - paste(element, "@eviltrout,@codinghorror sam"); - assert.strictEqual(this.get("usernames"), "eviltrout,codinghorror,sam"); - - this.set("usernames", null); - paste(element, "eviltrout\nsam\ncodinghorror"); - assert.strictEqual(this.get("usernames"), "eviltrout,sam,codinghorror"); - }); - - test("excluding usernames", async function (assert) { - this.set("usernames", "mark"); - this.set("excludedUsernames", ["jeff", "sam", "robin"]); - - await withSilencedDeprecationsAsync( - "discourse.user-selector-component", - async () => { - await render( - hbs`` - ); - } - ); - - let element = query(".test-selector"); - paste(element, "roman,penar,jeff,robin"); - assert.strictEqual(this.get("usernames"), "mark,roman,penar"); - }); -}); diff --git a/app/assets/javascripts/select-kit/addon/components/user-chooser.js b/app/assets/javascripts/select-kit/addon/components/user-chooser.js index be1deb7c7f1..2b7d5a08e5d 100644 --- a/app/assets/javascripts/select-kit/addon/components/user-chooser.js +++ b/app/assets/javascripts/select-kit/addon/components/user-chooser.js @@ -29,6 +29,7 @@ export default MultiSelectComponent.extend({ groupMembersOf: undefined, excludeCurrentUser: false, customSearchOptions: undefined, + excludedUsernames: undefined, }, content: computed("value.[]", function () {