diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6 index e6170b81edd..85e5c61dd7b 100644 --- a/app/assets/javascripts/discourse/components/user-selector.js.es6 +++ b/app/assets/javascripts/discourse/components/user-selector.js.es6 @@ -9,6 +9,25 @@ export default TextField.extend({ autocapitalize: false, name: "user-selector", + init() { + this._super(); + this._paste = e => { + let pastedText = ""; + if (window.clipboardData && window.clipboardData.getData) { + // IE + pastedText = window.clipboardData.getData("Text"); + } else if (e.clipboardData && e.clipboardData.getData) { + pastedText = e.clipboardData.getData("text/plain"); + } + + if (pastedText.length > 0) { + this.importText(pastedText); + e.preventDefault(); + return false; + } + }; + }, + @observes("usernames") _update() { if (this.canReceiveUpdates === "true") { @@ -19,6 +38,7 @@ export default TextField.extend({ @on("willDestroyElement") _destroyAutocompleteInstance() { $(this.element).autocomplete("destroy"); + this.element.addEventListener("paste", this._paste); }, @on("didInsertElement") @@ -52,6 +72,8 @@ export default TextField.extend({ return usernames; }; + this.element.addEventListener("paste", this._paste); + const userSelectorComponent = this; $(this.element) @@ -128,6 +150,24 @@ export default TextField.extend({ }); }, + 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) { + usernames.push(val); + } + }); + this.set("usernames", usernames.uniq().join(",")); + if (this.canReceiveUpdates !== "true") { + this._createAutocompleteInstance({ updateData: true }); + } + }, + // THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT @observes("usernames") _clearInput() { diff --git a/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs b/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs index 9a9b1cfcf87..21c69bda7ad 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-user-selector.hbs @@ -1,6 +1,6 @@ {{#if showSelector}} {{user-selector topicId=topicId - onChangeCallback=(action "triggerResize") + onChangeCallback=(action "triggerResize") id="private-message-users" includeMessageableGroups='true' placeholderKey="composer.users_placeholder" diff --git a/test/javascripts/components/user-selector-test.js.es6 b/test/javascripts/components/user-selector-test.js.es6 new file mode 100644 index 00000000000..407cbd67969 --- /dev/null +++ b/test/javascripts/components/user-selector-test.js.es6 @@ -0,0 +1,38 @@ +import componentTest from "helpers/component-test"; + +moduleForComponent("user-selector", { integration: true }); + +componentTest("pasting a list of usernames", { + template: `{{user-selector usernames=usernames class="test-selector"}}`, + + beforeEach() { + this.set("usernames", "evil,trout"); + }, + + test(assert) { + let element = find(".test-selector")[0]; + let paste = text => { + let e = new Event("paste"); + e.clipboardData = { getData: () => text }; + element.dispatchEvent(e); + }; + + assert.equal(this.get("usernames"), "evil,trout"); + paste("zip,zap,zoom"); + assert.equal(this.get("usernames"), "evil,trout,zip,zap,zoom"); + paste("evil,abc,abc,abc"); + assert.equal(this.get("usernames"), "evil,trout,zip,zap,zoom,abc"); + + this.set("usernames", ""); + paste("names with spaces"); + assert.equal(this.get("usernames"), "names,with,spaces"); + + this.set("usernames", null); + paste("@eviltrout,@codinghorror sam"); + assert.equal(this.get("usernames"), "eviltrout,codinghorror,sam"); + + this.set("usernames", null); + paste("eviltrout\nsam\ncodinghorror"); + assert.equal(this.get("usernames"), "eviltrout,sam,codinghorror"); + } +});