diff --git a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs index 3688355a92c..6075400d907 100644 --- a/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs +++ b/app/assets/javascripts/discourse/templates/components/composer-action-title.hbs @@ -8,4 +8,6 @@ action=model.action}} {{/if}} -{{actionTitle}} + + {{actionTitle}} + diff --git a/app/assets/javascripts/select-kit/components/composer-actions.js.es6 b/app/assets/javascripts/select-kit/components/composer-actions.js.es6 index 2ca9ad99023..aef58aed610 100644 --- a/app/assets/javascripts/select-kit/components/composer-actions.js.es6 +++ b/app/assets/javascripts/select-kit/components/composer-actions.js.es6 @@ -1,6 +1,15 @@ import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; import { default as computed } from "ember-addons/ember-computed-decorators"; -import { default as Composer, REPLY, EDIT } from "discourse/models/composer"; +import { default as Composer, PRIVATE_MESSAGE, CREATE_TOPIC, REPLY, EDIT } from "discourse/models/composer"; + +// Component can get destroyed and lose state +let _topicSnapshot = null; +let _postSnapshot = null; + +export function _clearSnapshots() { + _topicSnapshot = null; + _postSnapshot = null; +} export default DropdownSelectBoxComponent.extend({ composerController: Ember.inject.controller("composer"), @@ -13,10 +22,27 @@ export default DropdownSelectBoxComponent.extend({ allowAutoSelectFirst: false, showFullTitle: false, + didReceiveAttrs() { + this._super(); + + // if we change topic we want to change both snapshots + if (this.get("composerModel.topic") && (!_topicSnapshot || this.get("composerModel.topic.id") !== _topicSnapshot.get("id"))) { + _topicSnapshot = this.get("composerModel.topic"); + _postSnapshot = this.get("composerModel.post"); + } + + // if we hit reply on a different post we want to change postSnapshot + if (this.get("composerModel.post") && (!_postSnapshot || this.get("composerModel.post.id") !== _postSnapshot.get("id"))) { + _postSnapshot = this.get("composerModel.post"); + } + }, + computeHeaderContent() { let content = this.baseHeaderComputedContent(); switch (this.get("action")) { + case PRIVATE_MESSAGE: + case CREATE_TOPIC: case REPLY: content.icon = "mail-forward"; break; @@ -28,18 +54,32 @@ export default DropdownSelectBoxComponent.extend({ return content; }, - @computed("options", "canWhisper", "composerModel.post.username") - content(options, canWhisper, postUsername) { - let items = [ - { + @computed("options", "canWhisper", "action") + content(options, canWhisper, action) { + let items = []; + + if (action !== CREATE_TOPIC) { + items.push({ name: I18n.t("composer.composer_actions.reply_as_new_topic.label"), description: I18n.t("composer.composer_actions.reply_as_new_topic.desc"), icon: "plus", id: "reply_as_new_topic" - } - ]; + }); + } - if (postUsername && postUsername !== this.currentUser.get("username")) { + if ((action !== REPLY && _postSnapshot) || (action === REPLY && _postSnapshot && !(options.userAvatar && options.userLink))) { + items.push({ + name: I18n.t("composer.composer_actions.reply_to_post.label", { + postNumber: _postSnapshot.get("post_number"), + postUsername: _postSnapshot.get("username") + }), + description: I18n.t("composer.composer_actions.reply_to_post.desc"), + icon: "mail-forward", + id: "reply_to_post" + }); + } + + if (action !== PRIVATE_MESSAGE) { items.push({ name: I18n.t("composer.composer_actions.reply_as_private_message.label"), description: I18n.t("composer.composer_actions.reply_as_private_message.desc"), @@ -48,7 +88,7 @@ export default DropdownSelectBoxComponent.extend({ }); } - if (Ember.get(options, "postLink")) { + if ((action !== REPLY && _topicSnapshot) || (action === REPLY && _topicSnapshot && (options.userAvatar && options.userLink && options.topicLink))) { items.push({ name: I18n.t("composer.composer_actions.reply_to_topic.label"), description: I18n.t("composer.composer_actions.reply_to_topic.desc"), @@ -57,7 +97,8 @@ export default DropdownSelectBoxComponent.extend({ }); } - if (canWhisper) { + // if answered post is a whisper, we can only answer with a whisper so no need for toggle + if (canWhisper && (_postSnapshot && _postSnapshot.post_type !== this.site.post_types.whisper)) { items.push({ name: I18n.t("composer.composer_actions.toggle_whisper.label"), description: I18n.t("composer.composer_actions.toggle_whisper.desc"), @@ -69,46 +110,85 @@ export default DropdownSelectBoxComponent.extend({ return items; }, - _replyFromExisting(options) { - const topicTitle = this.get("composerModel.topic.title"); - let url = this.get("composerModel.post.url") || this.get("composerModel.topic.url"); + _replyFromExisting(options, post, topic) { + const reply = this.get("composerModel.reply"); + let url; + if (post) url = post.get("url"); + if (!post && topic) url = topic.get("url"); + + let topicTitle; + if (topic) topicTitle = topic.get("title"); + + this.get("composerController").close(); this.get("composerController").open(options).then(() => { + if (!url || ! topicTitle) return; + url = `${location.protocol}//${location.host}${url}`; const link = `[${Handlebars.escapeExpression(topicTitle)}](${url})`; - this.get("composerController").get("model").prependText(`${I18n.t("post.continue_discussion", { postLink: link })}`, {new_line: true}); + const continueDiscussion = I18n.t("post.continue_discussion", { postLink: link }); + + if (!reply.includes(continueDiscussion)) { + this.get("composerController") + .get("model") + .prependText(continueDiscussion, {new_line: true}); + } }); }, actions: { onSelect(value) { + let options = { + draftKey: this.get("composerModel.draftKey"), + draftSequence: this.get("composerModel.draftSequence"), + reply: this.get("composerModel.reply") + }; + switch(value) { case "toggle_whisper": this.set("composerModel.whisper", !this.get("composerModel.whisper")); break; + case "reply_to_post": + options.action = Composer.REPLY; + options.post = _postSnapshot; + + this.get("composerController").close(); + this.get("composerController").open(options); + break; + case "reply_to_topic": - this.set("composerModel.post", null); - this.get("composerController").save(); + options.action = Composer.REPLY; + options.topic = _topicSnapshot; + + this.get("composerController").close(); + this.get("composerController").open(options); break; case "reply_as_new_topic": - const replyAsNewTopicOpts = { - action: Composer.CREATE_TOPIC, - draftKey: Composer.REPLY_AS_NEW_TOPIC_KEY, - categoryId: this.get("composerModel.topic.category.id") - }; - this._replyFromExisting(replyAsNewTopicOpts); + options.action = Composer.CREATE_TOPIC; + options.categoryId = this.get("composerModel.topic.category.id"); + + this.get("composerController").close(); + this.get("composerController").open(options); break; case "reply_as_private_message": - const replyAsPrivateMsgOpts = { - action: Composer.PRIVATE_MESSAGE, - archetypeId: "private_message", - draftKey: Composer.REPLY_AS_NEW_PRIVATE_MESSAGE_KEY, - usernames: this.get("composerModel.post.username") - }; - this._replyFromExisting(replyAsPrivateMsgOpts); + let usernames; + + if (_postSnapshot && !_postSnapshot.get("yours")) { + const postUsername = _postSnapshot.get("username"); + if (postUsername) { + usernames = postUsername; + } + } + + options.action = Composer.PRIVATE_MESSAGE; + options.usernames = usernames; + options.archetypeId = "private_message"; + options.draftKey = Composer.NEW_PRIVATE_MESSAGE_KEY; + + this._replyFromExisting(options, _postSnapshot, _topicSnapshot); break; } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2726821c855..e95cd09c447 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1292,6 +1292,9 @@ en: admin_options_title: "Optional staff settings for this topic" composer_actions: + reply_to_post: + label: Reply to post %{postNumber} by %{postUsername} + desc: Reply to a specific post reply_as_new_topic: label: Reply as linked topic desc: Create a new topic diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 index a16c2d2293a..14654a0fcea 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 +++ b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 @@ -68,6 +68,10 @@ export default Ember.Component.extend({ this.clear(); + if (this.get('action') !== 'reply' || this.get('action') !== 'edit') { + return; + } + this.publish({ response_needed: true, previous: this.get('previousState'), diff --git a/test/javascripts/acceptance/composer-actions-test.js.es6 b/test/javascripts/acceptance/composer-actions-test.js.es6 index 3893b2ac99d..f15498e119e 100644 --- a/test/javascripts/acceptance/composer-actions-test.js.es6 +++ b/test/javascripts/acceptance/composer-actions-test.js.es6 @@ -20,6 +20,7 @@ QUnit.test('replying to post', assert => { assert.equal(composerActions.rowByIndex(1).value(), 'reply_as_private_message'); assert.equal(composerActions.rowByIndex(2).value(), 'reply_to_topic'); assert.equal(composerActions.rowByIndex(3).value(), 'toggle_whisper'); + assert.equal(composerActions.rowByIndex(4).value(), undefined); }); }); @@ -42,12 +43,13 @@ QUnit.test('replying to post - reply_to_topic', assert => { visit('/t/internationalization-localization/280'); click('article#post_3 button.reply'); - fillIn('.d-editor-input', 'test replying to topic when intially replied to post'); + fillIn('.d-editor-input', 'test replying to topic when initially replied to post'); composerActions.expand().selectRowByValue('reply_to_topic'); andThen(() => { - assert.equal(find('.topic-post:last .cooked p').html().trim(), 'test replying to topic when intially replied to post'); - assert.notOk(exists(find('.topic-post:last .reply-to-tab'))); + assert.equal(find('.action-title .topic-link').text().trim(), 'Internationalization / localization'); + assert.equal(find('.action-title .topic-link').attr("href"), '/t/internationalization-localization/280'); + assert.equal(find('.d-editor-input').val(), 'test replying to topic when initially replied to post'); }); }); @@ -56,7 +58,7 @@ QUnit.test('replying to post - toggle_whisper', assert => { visit('/t/internationalization-localization/280'); click('article#post_3 button.reply'); - fillIn('.d-editor-input', 'test replying as whisper to topic when intially not a whisper'); + fillIn('.d-editor-input', 'test replying as whisper to topic when initially not a whisper'); composerActions.expand().selectRowByValue('toggle_whisper'); andThen(() => { @@ -78,10 +80,73 @@ QUnit.test('replying to post - reply_as_new_topic', assert => { click('#topic-title .submit-edit'); click('article#post_3 button.reply'); + fillIn('.d-editor-input', 'test replying as new topic when initially replied to post'); composerActions.expand().selectRowByValue('reply_as_new_topic'); andThen(() => { assert.equal(categoryChooserReplyArea.header().name(), 'faq'); - assert.ok(find('.d-editor-input').val().indexOf('Continuing the discussion') >= 0); + assert.equal(find('.action-title').text().trim(), I18n.t("topic.create_long")); + assert.equal(find('.d-editor-input').val(), 'test replying as new topic when initially replied to post'); + }); +}); + + +QUnit.test('interactions', assert => { + const composerActions = selectKit('.composer-actions'); + const quote = 'Life is like riding a bicycle.'; + + visit('/t/internationalization-localization/280'); + click('article#post_3 button.reply'); + fillIn('.d-editor-input', quote); + composerActions.expand().selectRowByValue('reply_to_topic'); + + andThen(() => { + assert.equal(find('.action-title').text().trim(), "Internationalization / localization"); + assert.equal(find('.d-editor-input').val(), quote); + }); + + composerActions.expand(); + + andThen(() => { + assert.equal(composerActions.rowByIndex(0).value(), 'reply_as_new_topic'); + assert.equal(composerActions.rowByIndex(1).value(), 'reply_to_post'); + assert.equal(composerActions.rowByIndex(2).value(), 'reply_as_private_message'); + assert.equal(composerActions.rowByIndex(3).value(), 'toggle_whisper'); + assert.equal(composerActions.rowByIndex(4).value(), undefined); + }); + + composerActions.selectRowByValue('reply_to_post').expand(); + + andThen(() => { + assert.ok(exists(find('.action-title img.avatar'))); + assert.equal(find('.action-title .user-link').text().trim(), "codinghorror"); + assert.equal(find('.d-editor-input').val(), quote); + assert.equal(composerActions.rowByIndex(0).value(), 'reply_as_new_topic'); + assert.equal(composerActions.rowByIndex(1).value(), 'reply_as_private_message'); + assert.equal(composerActions.rowByIndex(2).value(), 'reply_to_topic'); + assert.equal(composerActions.rowByIndex(3).value(), 'toggle_whisper'); + assert.equal(composerActions.rowByIndex(4).value(), undefined); + }); + + composerActions.selectRowByValue('reply_as_new_topic').expand(); + + andThen(() => { + assert.equal(find('.action-title').text().trim(), I18n.t("topic.create_long")); + assert.equal(find('.d-editor-input').val(), quote); + assert.equal(composerActions.rowByIndex(0).value(), 'reply_to_post'); + assert.equal(composerActions.rowByIndex(1).value(), 'reply_as_private_message'); + assert.equal(composerActions.rowByIndex(2).value(), 'reply_to_topic'); + assert.equal(composerActions.rowByIndex(3).value(), undefined); + }); + + composerActions.selectRowByValue('reply_as_private_message').expand(); + + andThen(() => { + assert.equal(find('.action-title').text().trim(), I18n.t("topic.private_message")); + assert.ok(find('.d-editor-input').val().indexOf("Continuing the discussion") === 0); + assert.equal(composerActions.rowByIndex(0).value(), 'reply_as_new_topic'); + assert.equal(composerActions.rowByIndex(1).value(), 'reply_to_post'); + assert.equal(composerActions.rowByIndex(2).value(), 'reply_to_topic'); + assert.equal(composerActions.rowByIndex(3).value(), undefined); }); });