REFACTOR: Composer messages to use new ember idioms
This commit is contained in:
parent
6aaa484baa
commit
67303d7679
|
@ -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'));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{{#each messages as |message|}}
|
||||
{{composer-message message=message closeMessage="closeMessage"}}
|
||||
{{/each}}
|
|
@ -9,9 +9,9 @@
|
|||
{{/popup-menu}}
|
||||
{{/if}}
|
||||
|
||||
{{render "composer-messages"}}
|
||||
<div class='control'>
|
||||
{{composer-messages composer=model messageCount=messageCount}}
|
||||
|
||||
<div class='control'>
|
||||
{{#if site.mobileView}}
|
||||
<a href class='toggle-toolbar' {{action "toggleToolbar" bubbles=false}}></a>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<a href {{action "closeMessage" this}} class='close'><i class='fa fa-times'></i></a>
|
||||
{{{body}}}
|
||||
<a href {{action "closeMessage"}} class='close'>{{fa-icon "times"}}</a>
|
||||
{{{message.body}}}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<a href {{action "closeMessage" this}} class='close'><i class='fa fa-close'></i></a>
|
||||
{{{body}}}
|
||||
<a href {{action "closeMessage"}} class='close'>{{fa-icon "close"}}</a>
|
||||
{{{message.body}}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<a href {{action "closeMessage" this}} class='close'>{{fa-icon "close"}}</a>
|
||||
<a href {{action "closeMessage"}} class='close'>{{fa-icon "close"}}</a>
|
||||
<h3>{{i18n 'composer.similar_topics'}}</h3>
|
||||
|
||||
<ul class='topics'>
|
||||
{{mount-widget widget="search-result-topic" args=(as-hash results=similarTopics)}}
|
||||
{{mount-widget widget="search-result-topic" args=(as-hash results=message.similarTopics)}}
|
||||
</ul>
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
export default Ember.CollectionView.extend({
|
||||
classNameBindings: [':composer-popup-container', 'hidden'],
|
||||
content: Em.computed.alias('controller.content'),
|
||||
|
||||
hidden: Em.computed.not('controller.controllers.composer.model.viewOpen'),
|
||||
|
||||
itemViewClass: Ember.View.extend({
|
||||
classNames: ['composer-popup', 'hidden'],
|
||||
templateName: Em.computed.alias('content.templateName'),
|
||||
|
||||
_setup: function() {
|
||||
this._super();
|
||||
this.set('context', this.get('content'));
|
||||
|
||||
if (this.get('content.extraClass')) {
|
||||
this.get('classNames').pushObject(this.get('content.extraClass'));
|
||||
}
|
||||
}.on('init'),
|
||||
|
||||
_initCss: function() {
|
||||
this.$().show();
|
||||
}.on('didInsertElement')
|
||||
})
|
||||
});
|
|
@ -64,9 +64,7 @@ const ComposerView = Ember.View.extend({
|
|||
Ember.run.cancel(this._lastKeyTimeout);
|
||||
this._lastKeyTimeout = Ember.run.later(() => {
|
||||
if (lastKeyUp !== this._lastKeyUp) { return; }
|
||||
|
||||
// Search for similar topics if the user pauses typing
|
||||
controller.findSimilarTopics();
|
||||
this.appEvents.trigger('composer:find-similar');
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function() {
|
|||
|
||||
this.get('/admin/plugins', () => response({ plugins: [] }));
|
||||
|
||||
this.get('/composer-messages', () => response([]));
|
||||
this.get('/composer_messages', () => response({ composer_messages: [] }));
|
||||
|
||||
this.get("/latest.json", () => {
|
||||
const json = fixturesByUrl['/latest.json'];
|
||||
|
|
Loading…
Reference in New Issue