diff --git a/app/assets/javascripts/discourse/components/composer-message.js.es6 b/app/assets/javascripts/discourse/components/composer-message.js.es6 new file mode 100644 index 00000000000..74c11864e30 --- /dev/null +++ b/app/assets/javascripts/discourse/components/composer-message.js.es6 @@ -0,0 +1,21 @@ +import computed from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + classNameBindings: [':composer-popup', ':hidden', 'message.extraClass'], + + @computed('message.templateName') + defaultLayout(templateName) { + return this.container.lookup(`template:${templateName}`) + }, + + didInsertElement() { + this._super(); + this.$().show(); + }, + + actions: { + closeMessage() { + this.sendAction('closeMessage', this.get('message')); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/composer-messages.js.es6 b/app/assets/javascripts/discourse/components/composer-messages.js.es6 new file mode 100644 index 00000000000..8c343940e18 --- /dev/null +++ b/app/assets/javascripts/discourse/components/composer-messages.js.es6 @@ -0,0 +1,163 @@ +export default Ember.Component.extend({ + classNameBindings: [':composer-popup-container', 'hidden'], + checkedMessages: false, + messages: null, + messagesByTemplate: null, + queuedForTyping: null, + _lastSimilaritySearch: null, + _similarTopicsMessage: null, + similarTopics: null, + + hidden: Ember.computed.not('composer.viewOpen'), + + didInsertElement() { + this._super(); + this.reset(); + this.appEvents.on('composer:typed-reply', this, this._typedReply); + this.appEvents.on('composer:opened', this, this._findMessages); + this.appEvents.on('composer:find-similar', this, this._findSimilar); + this.appEvents.on('composer-messages:close', this, this._closeTop); + }, + + willDestroyElement() { + this.appEvents.off('composer:typed-reply', this, this._typedReply); + this.appEvents.off('composer:opened', this, this._findMessages); + this.appEvents.off('composer:find-similar', this, this._findSimilar); + this.appEvents.off('composer-messages:close', this, this._closeTop); + }, + + _closeTop() { + const messages = this.get('messages'); + messages.popObject(); + this.set('messageCount', messages.get('length')); + }, + + _removeMessage(message) { + const messages = this.get('messages'); + messages.removeObject(message); + this.set('messageCount', messages.get('length')); + }, + + actions: { + closeMessage(message) { + this._removeMessage(message); + }, + + hideMessage(message) { + this._removeMessage(message); + // kind of hacky but the visibility depends on this + this.get('messagesByTemplate')[message.get('templateName')] = undefined; + }, + + popup(message) { + const messagesByTemplate = this.get('messagesByTemplate'); + const templateName = message.get('templateName'); + + if (!messagesByTemplate[templateName]) { + const messages = this.get('messages'); + messages.pushObject(message); + this.set('messageCount', messages.get('length')); + messagesByTemplate[templateName] = message; + } + } + }, + + // Resets all active messages. + // For example if composing a new post. + reset() { + if (this.isDestroying || this.isDestroyed) { return; } + this.setProperties({ + messages: [], + messagesByTemplate: {}, + queuedForTyping: [], + checkedMessages: false, + similarTopics: [], + }); + }, + + // Called after the user has typed a reply. + // Some messages only get shown after being typed. + _typedReply() { + if (this.isDestroying || this.isDestroyed) { return; } + this.get('queuedForTyping').forEach(msg => this.send("popup", msg)); + }, + + groupsMentioned(groups) { + // reset existing messages, this should always win it is critical + this.reset(); + groups.forEach(group => { + const msg = I18n.t('composer.group_mentioned', { + group: "@" + group.name, + count: group.user_count, + group_link: Discourse.getURL(`/group/${group.name}/members`) + }); + this.send("popup", + Em.Object.create({ + templateName: 'composer/group-mentioned', + body: msg}) + ); + }); + }, + + _findSimilar() { + const composer = this.get('composer'); + + // We don't care about similar topics unless creating a topic + if (!composer.get('creatingTopic')) { return; } + + const origBody = composer.get('reply') || ''; + const title = composer.get('title') || ''; + + // Ensure the fields are of the minimum length + if (origBody.length < Discourse.SiteSettings.min_body_similar_length) { return; } + if (title.length < Discourse.SiteSettings.min_title_similar_length) { return; } + + // TODO pass the 200 in from somewhere + const body = origBody.substr(0, 200); + + // Don't search over and over + const concat = title + body; + if (concat === this._lastSimilaritySearch) { return; } + this._lastSimilaritySearch = concat; + + const similarTopics = this.get('similarTopics'); + const message = this._similarTopicsMessage || composer.store.createRecord('composer-message', { + id: 'similar_topics', + templateName: 'composer/similar-topics', + extraClass: 'similar-topics' + }); + + this._similarTopicsMessage = message; + + composer.store.find('similar-topic', {title, raw: body}).then(newTopics => { + similarTopics.clear(); + similarTopics.pushObjects(newTopics.get('content')); + + if (similarTopics.get('length') > 0) { + message.set('similarTopics', similarTopics); + this.send('popup', message); + } else if (message) { + this.send('hideMessage', message); + } + }); + }, + + // Figure out if there are any messages that should be displayed above the composer. + _findMessages() { + if (this.get('checkedMessages')) { return; } + + const composer = this.get('composer'); + const args = { composerAction: composer.get('action') }; + const topicId = composer.get('topic.id'); + const postId = composer.get('post.id'); + + if (topicId) { args.topic_id = topicId; } + if (postId) { args.post_id = postId; } + + const queuedForTyping = this.get('queuedForTyping'); + composer.store.find('composer-message', args).then(messages => { + this.set('checkedMessages', true); + messages.forEach(msg => msg.wait_for_typing ? queuedForTyping.addObject(msg) : this.send('popup', msg)); + }); + } +}); diff --git a/app/assets/javascripts/discourse/controllers/composer-messages.js.es6 b/app/assets/javascripts/discourse/controllers/composer-messages.js.es6 deleted file mode 100644 index a821c758e8c..00000000000 --- a/app/assets/javascripts/discourse/controllers/composer-messages.js.es6 +++ /dev/null @@ -1,85 +0,0 @@ -// A controller for displaying messages as the user composes a message. -export default Ember.ArrayController.extend({ - needs: ['composer'], - - // Whether we've checked our messages - checkedMessages: false, - - _init: function() { - this.reset(); - }.on("init"), - - actions: { - closeMessage(message) { - this.removeObject(message); - }, - - hideMessage(message) { - this.removeObject(message); - // kind of hacky but the visibility depends on this - this.get('messagesByTemplate')[message.get('templateName')] = undefined; - }, - - popup(message) { - let messagesByTemplate = this.get('messagesByTemplate'); - const templateName = message.get('templateName'); - - if (!messagesByTemplate[templateName]) { - this.pushObject(message); - messagesByTemplate[templateName] = message; - } - } - }, - - // Resets all active messages. - // For example if composing a new post. - reset() { - this.clear(); - this.setProperties({ - messagesByTemplate: {}, - queuedForTyping: [], - checkedMessages: false - }); - }, - - // Called after the user has typed a reply. - // Some messages only get shown after being typed. - typedReply() { - this.get('queuedForTyping').forEach(msg => this.send("popup", msg)); - }, - - groupsMentioned(groups) { - // reset existing messages, this should always win it is critical - this.reset(); - groups.forEach(group => { - const msg = I18n.t('composer.group_mentioned', { - group: "@" + group.name, - count: group.user_count, - group_link: Discourse.getURL(`/group/${group.name}/members`) - }); - this.send("popup", - Em.Object.create({ - templateName: 'composer/group-mentioned', - body: msg}) - ); - }); - }, - - // Figure out if there are any messages that should be displayed above the composer. - queryFor(composer) { - if (this.get('checkedMessages')) { return; } - - const args = { composerAction: composer.get('action') }; - const topicId = composer.get('topic.id'); - const postId = composer.get('post.id'); - - if (topicId) { args.topic_id = topicId; } - if (postId) { args.post_id = postId; } - - const queuedForTyping = this.get('queuedForTyping'); - this.store.findAll('composer-message', args).then(messages => { - this.set('checkedMessages', true); - messages.forEach(msg => msg.wait_for_typing ? queuedForTyping.addObject(msg) : this.send('popup', msg)); - }); - } -}); diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 6d306e26f21..2cf69458a19 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -42,16 +42,14 @@ function loadDraft(store, opts) { } export default Ember.Controller.extend({ - needs: ['modal', 'topic', 'composer-messages', 'application'], + needs: ['modal', 'topic', 'application'], replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Composer.REPLY_AS_NEW_TOPIC_KEY), checkedMessages: false, + messageCount: null, showEditReason: false, editReason: null, scopedCategoryId: null, - similarTopics: null, - similarTopicsMessage: null, - lastSimilaritySearch: null, optionsVisible: false, lastValidatedAt: null, isUploading: false, @@ -78,10 +76,6 @@ export default Ember.Controller.extend({ topicModel: Ember.computed.alias('controllers.topic.model'), - _initializeSimilar: function() { - this.set('similarTopics', []); - }.on('init'), - @computed('model.canEditTitle', 'model.creatingPrivateMessage') canEditTags(canEditTitle, creatingPrivateMessage) { return !this.site.mobileView && @@ -183,9 +177,8 @@ export default Ember.Controller.extend({ }, hitEsc() { - const messages = this.get('controllers.composer-messages.model'); - if (messages.length) { - messages.popObject(); + if ((this.get('messageCount') || 0) > 0) { + this.appEvents.trigger('composer-messages:close'); return; } @@ -359,62 +352,14 @@ export default Ember.Controller.extend({ return promise; }, - // Checks to see if a reply has been typed. - // This is signaled by a keyUp event in a view. + // Notify the composer messages controller that a reply has been typed. Some + // messages only appear after typing. checkReplyLength() { if (!Ember.isEmpty('model.reply')) { - // Notify the composer messages controller that a reply has been typed. Some - // messages only appear after typing. - this.get('controllers.composer-messages').typedReply(); + this.appEvents.trigger('composer:typed-reply'); } }, - // Fired after a user stops typing. - // Considers whether to check for similar topics based on the current composer state. - findSimilarTopics() { - // We don't care about similar topics unless creating a topic - if (!this.get('model.creatingTopic')) { return; } - - let body = this.get('model.reply') || ''; - const title = this.get('model.title') || ''; - - // Ensure the fields are of the minimum length - if (body.length < Discourse.SiteSettings.min_body_similar_length) { return; } - if (title.length < Discourse.SiteSettings.min_title_similar_length) { return; } - - // TODO pass the 200 in from somewhere - body = body.substr(0, 200); - - // Done search over and over - if ((title + body) === this.get('lastSimilaritySearch')) { return; } - this.set('lastSimilaritySearch', title + body); - - const messageController = this.get('controllers.composer-messages'), - similarTopics = this.get('similarTopics'); - - let message = this.get('similarTopicsMessage'); - if (!message) { - message = this.store.createRecord('composer-message', { - id: 'similar_topics', - templateName: 'composer/similar-topics', - extraClass: 'similar-topics' - }); - this.set('similarTopicsMessage', message); - } - - this.store.find('similar-topic', {title, raw: body}).then(function(newTopics) { - similarTopics.clear(); - similarTopics.pushObjects(newTopics.get('content')); - - if (similarTopics.get('length') > 0) { - message.set('similarTopics', similarTopics); - messageController.send("popup", message); - } else if (message) { - messageController.send("hideMessage", message); - } - }); - }, - /** Open the composer view @@ -439,13 +384,10 @@ export default Ember.Controller.extend({ this.set('scopedCategoryId', opts.categoryId); } - const composerMessages = this.get('controllers.composer-messages'), - self = this; - + const self = this; let composerModel = this.get('model'); this.setProperties({ showEditReason: false, editReason: null }); - composerMessages.reset(); // If we want a different draft than the current composer, close it and clear our model. if (composerModel && @@ -539,8 +481,6 @@ export default Ember.Controller.extend({ if (opts.topicBody) { this.set('model.reply', opts.topicBody); } - - this.get('controllers.composer-messages').queryFor(composerModel); }, // View a new reply we've made diff --git a/app/assets/javascripts/discourse/templates/components/composer-messages.hbs b/app/assets/javascripts/discourse/templates/components/composer-messages.hbs new file mode 100644 index 00000000000..0048c0c4c9c --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/composer-messages.hbs @@ -0,0 +1,3 @@ +{{#each messages as |message|}} + {{composer-message message=message closeMessage="closeMessage"}} +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs index eb72ca673b3..b5422104c39 100644 --- a/app/assets/javascripts/discourse/templates/composer.hbs +++ b/app/assets/javascripts/discourse/templates/composer.hbs @@ -9,9 +9,9 @@ {{/popup-menu}} {{/if}} - {{render "composer-messages"}} -