mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
Big commit:
- Support for a popup that shows similar topics - Cleaned up a lot of Javascript - Cleaned up use of Promises
This commit is contained in:
parent
7714e2050e
commit
ad082cea70
39 changed files with 584 additions and 560 deletions
|
@ -456,7 +456,7 @@ GEM
|
|||
turbo-sprockets-rails3 (0.3.6)
|
||||
railties (> 3.2.8, < 4.0.0)
|
||||
sprockets (>= 2.0.0)
|
||||
tzinfo (0.3.35)
|
||||
tzinfo (0.3.37)
|
||||
uglifier (1.3.0)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (~> 1.0, >= 1.0.2)
|
||||
|
|
|
@ -156,15 +156,9 @@ Discourse.AdminUser.reopenClass({
|
|||
},
|
||||
|
||||
find: function(username) {
|
||||
var promise;
|
||||
promise = new RSVP.Promise();
|
||||
$.ajax({
|
||||
url: "/admin/users/" + username,
|
||||
success: function(result) {
|
||||
return promise.resolve(Discourse.AdminUser.create(result));
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
return $.ajax({url: "/admin/users/" + username}).then(function (result) {
|
||||
return Discourse.AdminUser.create(result);
|
||||
})
|
||||
},
|
||||
|
||||
findAll: function(query, filter) {
|
||||
|
|
|
@ -46,47 +46,15 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
|||
}).property('topic_hidden'),
|
||||
|
||||
deletePost: function() {
|
||||
var promise;
|
||||
promise = new RSVP.Promise();
|
||||
if (this.get('post_number') === "1") {
|
||||
return $.ajax("/t/" + this.topic_id, {
|
||||
type: 'DELETE',
|
||||
cache: false,
|
||||
success: function() {
|
||||
promise.resolve();
|
||||
},
|
||||
error: function(e) {
|
||||
promise.reject();
|
||||
}
|
||||
});
|
||||
return $.ajax("/t/" + this.topic_id, { type: 'DELETE', cache: false });
|
||||
} else {
|
||||
return $.ajax("/posts/" + this.id, {
|
||||
type: 'DELETE',
|
||||
cache: false,
|
||||
success: function() {
|
||||
promise.resolve();
|
||||
},
|
||||
error: function(e) {
|
||||
promise.reject();
|
||||
}
|
||||
});
|
||||
return $.ajax("/posts/" + this.id, { type: 'DELETE', cache: false });
|
||||
}
|
||||
},
|
||||
|
||||
clearFlags: function() {
|
||||
var promise;
|
||||
promise = new RSVP.Promise();
|
||||
$.ajax("/admin/flags/clear/" + this.id, {
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
success: function() {
|
||||
promise.resolve();
|
||||
},
|
||||
error: function(e) {
|
||||
promise.reject();
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
return $.ajax("/admin/flags/clear/" + this.id, { type: 'POST', cache: false });
|
||||
},
|
||||
|
||||
hiddenClass: (function() {
|
||||
|
|
|
@ -26,14 +26,8 @@ Discourse.VersionCheck = Discourse.Model.extend({
|
|||
|
||||
Discourse.VersionCheck.reopenClass({
|
||||
find: function() {
|
||||
var promise = new RSVP.Promise();
|
||||
$.ajax({
|
||||
url: '/admin/version_check',
|
||||
dataType: 'json',
|
||||
success: function(json) {
|
||||
promise.resolve(Discourse.VersionCheck.create(json));
|
||||
}
|
||||
return $.ajax({ url: '/admin/version_check', dataType: 'json' }).then(function(json) {
|
||||
return Discourse.VersionCheck.create(json);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
});
|
|
@ -8,22 +8,20 @@
|
|||
**/
|
||||
Discourse.ComposerController = Discourse.Controller.extend({
|
||||
needs: ['modal', 'topic'],
|
||||
hasReply: false,
|
||||
|
||||
togglePreview: function() {
|
||||
return this.get('content').togglePreview();
|
||||
this.get('content').togglePreview();
|
||||
},
|
||||
|
||||
// Import a quote from the post
|
||||
importQuote: function() {
|
||||
return this.get('content').importQuote();
|
||||
this.get('content').importQuote();
|
||||
},
|
||||
|
||||
appendText: function(text) {
|
||||
var c;
|
||||
c = this.get('content');
|
||||
var c = this.get('content');
|
||||
if (c) {
|
||||
return c.appendText(text);
|
||||
c.appendText(text);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -72,7 +70,6 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
}
|
||||
|
||||
bootbox.dialog(message, buttons);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -94,17 +91,80 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
});
|
||||
},
|
||||
|
||||
checkReplyLength: function() {
|
||||
if (this.present('content.reply')) {
|
||||
this.set('hasReply', true);
|
||||
} else {
|
||||
this.set('hasReply', false);
|
||||
closeEducation: function() {
|
||||
this.set('educationClosed', true);
|
||||
},
|
||||
|
||||
closeSimilar: function() {
|
||||
this.set('similarClosed', true);
|
||||
},
|
||||
|
||||
similarVisible: function() {
|
||||
if (this.get('similarClosed')) return false;
|
||||
if (this.get('content.composeState') !== Discourse.Composer.OPEN) return false;
|
||||
return (this.get('similarTopics.length') || 0) > 0;
|
||||
}.property('similarTopics.length', 'similarClosed', 'content.composeState'),
|
||||
|
||||
newUserEducationVisible: function() {
|
||||
if (!this.get('educationContents')) return false;
|
||||
if (this.get('content.composeState') !== Discourse.Composer.OPEN) return false;
|
||||
if (!this.present('content.reply')) return false;
|
||||
if (this.get('educationClosed')) return false;
|
||||
return true;
|
||||
}.property('content.composeState', 'content.reply', 'educationClosed', 'educationContents'),
|
||||
|
||||
fetchNewUserEducation: function() {
|
||||
// If creating a topic, use topic_count, otherwise post_count
|
||||
var count = this.get('content.creatingTopic') ? Discourse.get('currentUser.topic_count') : Discourse.get('currentUser.reply_count');
|
||||
if (count >= Discourse.SiteSettings.educate_until_posts) {
|
||||
this.set('educationClosed', true);
|
||||
this.set('educationContents', '');
|
||||
return;
|
||||
}
|
||||
|
||||
// The user must have typed a reply
|
||||
if (!this.get('typedReply')) return;
|
||||
|
||||
this.set('educationClosed', false);
|
||||
|
||||
// If visible update the text
|
||||
var educationKey = this.get('content.creatingTopic') ? 'new-topic' : 'new-reply';
|
||||
var composerController = this;
|
||||
$.get("/education/" + educationKey).then(function(result) {
|
||||
composerController.set('educationContents', result);
|
||||
});
|
||||
}.observes('typedReply', 'content.creatingTopic', 'Discourse.currentUser.reply_count'),
|
||||
|
||||
checkReplyLength: function() {
|
||||
this.set('typedReply', this.present('content.reply'));
|
||||
},
|
||||
|
||||
/**
|
||||
Fired after a user stops typing. Considers whether to check for similar
|
||||
topics based on the current composer state.
|
||||
|
||||
@method findSimilarTopics
|
||||
**/
|
||||
findSimilarTopics: function() {
|
||||
|
||||
// We don't care about similar topics unless creating a topic
|
||||
if (!this.get('content.creatingTopic')) return;
|
||||
|
||||
var body = this.get('content.reply');
|
||||
var title = this.get('content.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;
|
||||
|
||||
var composerController = this;
|
||||
Discourse.Topic.findSimilarTo(title, body).then(function (topics) {
|
||||
composerController.set('similarTopics', topics);
|
||||
});
|
||||
},
|
||||
|
||||
saveDraft: function() {
|
||||
var model;
|
||||
model = this.get('content');
|
||||
var model = this.get('content');
|
||||
if (model) model.saveDraft();
|
||||
},
|
||||
|
||||
|
@ -123,8 +183,11 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
_this = this;
|
||||
if (!opts) opts = {};
|
||||
|
||||
opts.promise = promise = opts.promise || new RSVP.Promise();
|
||||
this.set('hasReply', false);
|
||||
opts.promise = promise = opts.promise || Ember.Deferred.create();
|
||||
this.set('typedReply', false);
|
||||
this.set('similarTopics', null);
|
||||
this.set('similarClosed', false);
|
||||
|
||||
if (!opts.draftKey) {
|
||||
alert("composer was opened without a draft key");
|
||||
throw "composer opened without a proper draft key";
|
||||
|
@ -133,9 +196,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
// ensure we have a view now, without it transitions are going to be messed
|
||||
view = this.get('view');
|
||||
if (!view) {
|
||||
view = Discourse.ComposerView.create({
|
||||
controller: this
|
||||
});
|
||||
view = Discourse.ComposerView.create({ controller: this });
|
||||
view.appendTo($('#main'));
|
||||
this.set('view', view);
|
||||
// the next runloop is too soon, need to get the control rendered and then
|
||||
|
@ -197,8 +258,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
},
|
||||
|
||||
wouldLoseChanges: function() {
|
||||
var composer;
|
||||
composer = this.get('content');
|
||||
var composer = this.get('content');
|
||||
return composer && composer.wouldLoseChanges();
|
||||
},
|
||||
|
||||
|
@ -210,10 +270,9 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
},
|
||||
|
||||
destroyDraft: function() {
|
||||
var key;
|
||||
key = this.get('content.draftKey');
|
||||
var key = this.get('content.draftKey');
|
||||
if (key) {
|
||||
return Discourse.Draft.clear(key, this.get('content.draftSequence'));
|
||||
Discourse.Draft.clear(key, this.get('content.draftSequence'));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -243,17 +302,17 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
}
|
||||
},
|
||||
|
||||
click: function() {
|
||||
openIfDraft: function() {
|
||||
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
|
||||
return this.set('content.composeState', Discourse.Composer.OPEN);
|
||||
this.set('content.composeState', Discourse.Composer.OPEN);
|
||||
}
|
||||
},
|
||||
|
||||
shrink: function() {
|
||||
if (this.get('content.reply') === this.get('content.originalText')) {
|
||||
return this.close();
|
||||
this.close();
|
||||
} else {
|
||||
return this.collapse();
|
||||
this.collapse();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -42,13 +42,11 @@ Discourse.ListController = Discourse.Controller.extend({
|
|||
this.set('loading', true);
|
||||
|
||||
if (filterMode === 'categories') {
|
||||
return Ember.Deferred.promise(function(deferred) {
|
||||
Discourse.CategoryList.list(filterMode).then(function(items) {
|
||||
listController.set('loading', false);
|
||||
listController.set('filterMode', filterMode);
|
||||
listController.set('categoryMode', true);
|
||||
return deferred.resolve(items);
|
||||
});
|
||||
return Discourse.CategoryList.list(filterMode).then(function(items) {
|
||||
listController.set('loading', false);
|
||||
listController.set('filterMode', filterMode);
|
||||
listController.set('categoryMode', true);
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,13 +54,11 @@ Discourse.ListController = Discourse.Controller.extend({
|
|||
if (!current) {
|
||||
current = Discourse.NavItem.create({ name: filterMode });
|
||||
}
|
||||
return Ember.Deferred.promise(function(deferred) {
|
||||
Discourse.TopicList.list(current).then(function(items) {
|
||||
listController.set('filterSummary', items.filter_summary);
|
||||
listController.set('filterMode', filterMode);
|
||||
listController.set('loading', false);
|
||||
return deferred.resolve(items);
|
||||
});
|
||||
return Discourse.TopicList.list(current).then(function(items) {
|
||||
listController.set('filterSummary', items.filter_summary);
|
||||
listController.set('filterMode', filterMode);
|
||||
listController.set('loading', false);
|
||||
return items;
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -117,19 +117,18 @@ Discourse.TopicController = Discourse.ObjectController.extend({
|
|||
},
|
||||
|
||||
replyAsNewTopic: function(post) {
|
||||
var composerController, postLink, postUrl, promise;
|
||||
composerController = this.get('controllers.composer');
|
||||
|
||||
// TODO shut down topic draft cleanly if it exists ...
|
||||
promise = composerController.open({
|
||||
var composerController = this.get('controllers.composer');
|
||||
var promise = composerController.open({
|
||||
action: Discourse.Composer.CREATE_TOPIC,
|
||||
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
|
||||
});
|
||||
postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
|
||||
postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
|
||||
return promise.then(function() {
|
||||
return Discourse.Post.loadQuote(post.get('id')).then(function(q) {
|
||||
return composerController.appendText("" + (Em.String.i18n("post.continue_discussion", {
|
||||
var postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
|
||||
var postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
|
||||
|
||||
promise.then(function() {
|
||||
Discourse.Post.loadQuote(post.get('id')).then(function(q) {
|
||||
composerController.appendText("" + (Em.String.i18n("post.continue_discussion", {
|
||||
postLink: postLink
|
||||
})) + "\n\n" + q);
|
||||
});
|
||||
|
|
|
@ -40,8 +40,6 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||
act: function(opts) {
|
||||
|
||||
// Mark it as acted
|
||||
var promise,
|
||||
_this = this;
|
||||
this.set('acted', true);
|
||||
this.set('count', this.get('count') + 1);
|
||||
this.set('can_act', false);
|
||||
|
@ -53,26 +51,19 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||
}
|
||||
|
||||
// Create our post action
|
||||
promise = new RSVP.Promise();
|
||||
$.ajax({
|
||||
var actionSummary = this;
|
||||
return $.ajax({
|
||||
url: "/post_actions",
|
||||
type: 'POST',
|
||||
data: {
|
||||
id: this.get('post.id'),
|
||||
post_action_type_id: this.get('id'),
|
||||
message: (opts ? opts.message : void 0) || ""
|
||||
},
|
||||
error: function(error) {
|
||||
var errors;
|
||||
_this.removeAction();
|
||||
errors = $.parseJSON(error.responseText).errors;
|
||||
return promise.reject(errors);
|
||||
},
|
||||
success: function() {
|
||||
return promise.resolve();
|
||||
}
|
||||
}).then(null, function (error) {
|
||||
actionSummary.removeAction();
|
||||
return $.parseJSON(error.responseText).errors;
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
// Undo this action
|
||||
|
|
|
@ -31,18 +31,14 @@ Discourse.CategoryList.reopenClass({
|
|||
},
|
||||
|
||||
list: function(filter) {
|
||||
var promise,
|
||||
_this = this;
|
||||
promise = new RSVP.Promise();
|
||||
$.getJSON("/" + filter + ".json").then(function(result) {
|
||||
var categoryList;
|
||||
categoryList = Discourse.TopicList.create();
|
||||
var route = this;
|
||||
return $.getJSON("/" + filter + ".json").then(function(result) {
|
||||
var categoryList = Discourse.TopicList.create();
|
||||
categoryList.set('can_create_category', result.category_list.can_create_category);
|
||||
categoryList.set('categories', _this.categoriesFrom(result));
|
||||
categoryList.set('categories', route.categoriesFrom(result));
|
||||
categoryList.set('loaded', true);
|
||||
return promise.resolve(categoryList);
|
||||
return categoryList;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -85,9 +85,9 @@ Discourse.Composer = Discourse.Model.extend({
|
|||
if (post) {
|
||||
this.set('loading', true);
|
||||
var composer = this;
|
||||
return Discourse.Post.load(post.get('id'), function(result) {
|
||||
Discourse.Post.load(post.get('id')).then(function(result) {
|
||||
composer.appendText(Discourse.BBCode.buildQuoteBBCode(post, result.get('raw')));
|
||||
return composer.set('loading', false);
|
||||
composer.set('loading', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -249,9 +249,10 @@ Discourse.Composer = Discourse.Model.extend({
|
|||
this.set('reply', opts.reply || this.get("reply") || "");
|
||||
if (opts.postId) {
|
||||
this.set('loading', true);
|
||||
Discourse.Post.load(opts.postId, function(result) {
|
||||
Discourse.Post.load(opts.postId).then(function(result) {
|
||||
console.log(result);
|
||||
composer.set('post', result);
|
||||
return composer.set('loading', false);
|
||||
composer.set('loading', false);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -259,7 +260,7 @@ Discourse.Composer = Discourse.Model.extend({
|
|||
if (opts.action === EDIT && opts.post) {
|
||||
this.set('title', this.get('topic.title'));
|
||||
this.set('loading', true);
|
||||
Discourse.Post.load(opts.post.get('id'), function(result) {
|
||||
Discourse.Post.load(opts.post.get('id')).then(function(result) {
|
||||
composer.set('reply', result.get('raw'));
|
||||
composer.set('originalText', composer.get('reply'));
|
||||
composer.set('loading', false);
|
||||
|
@ -285,7 +286,6 @@ Discourse.Composer = Discourse.Model.extend({
|
|||
|
||||
// When you edit a post
|
||||
editPost: function(opts) {
|
||||
var promise = new RSVP.Promise();
|
||||
var post = this.get('post');
|
||||
var oldCooked = post.get('cooked');
|
||||
var composer = this;
|
||||
|
@ -304,40 +304,37 @@ Discourse.Composer = Discourse.Model.extend({
|
|||
post.set('cooked', $('#wmd-preview').html());
|
||||
this.set('composeState', CLOSED);
|
||||
|
||||
return Ember.Deferred.promise(function(promise) {
|
||||
post.save(function(savedPost) {
|
||||
var posts = composer.get('topic.posts');
|
||||
|
||||
post.save(function(savedPost) {
|
||||
|
||||
var idx, postNumber;
|
||||
var posts = composer.get('topic.posts');
|
||||
|
||||
// perhaps our post came from elsewhere eg. draft
|
||||
idx = -1;
|
||||
postNumber = post.get('post_number');
|
||||
posts.each(function(p, i) {
|
||||
if (p.get('post_number') === postNumber) {
|
||||
idx = i;
|
||||
// perhaps our post came from elsewhere eg. draft
|
||||
var idx = -1;
|
||||
var postNumber = post.get('post_number');
|
||||
posts.each(function(p, i) {
|
||||
if (p.get('post_number') === postNumber) {
|
||||
idx = i;
|
||||
}
|
||||
});
|
||||
if (idx > -1) {
|
||||
savedPost.set('topic', composer.get('topic'));
|
||||
posts.replace(idx, 1, [savedPost]);
|
||||
promise.resolve({ post: post });
|
||||
composer.set('topic.draft_sequence', savedPost.draft_sequence);
|
||||
}
|
||||
}, function(error) {
|
||||
var errors;
|
||||
errors = $.parseJSON(error.responseText).errors;
|
||||
promise.reject(errors[0]);
|
||||
post.set('cooked', oldCooked);
|
||||
return composer.set('composeState', OPEN);
|
||||
});
|
||||
if (idx > -1) {
|
||||
savedPost.set('topic', composer.get('topic'));
|
||||
posts.replace(idx, 1, [savedPost]);
|
||||
promise.resolve({ post: post });
|
||||
composer.set('topic.draft_sequence', savedPost.draft_sequence);
|
||||
}
|
||||
}, function(error) {
|
||||
var errors;
|
||||
errors = $.parseJSON(error.responseText).errors;
|
||||
promise.reject(errors[0]);
|
||||
post.set('cooked', oldCooked);
|
||||
return composer.set('composeState', OPEN);
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
// Create a new Post
|
||||
createPost: function(opts) {
|
||||
var promise = new RSVP.Promise(),
|
||||
post = this.get('post'),
|
||||
var post = this.get('post'),
|
||||
topic = this.get('topic'),
|
||||
currentUser = Discourse.get('currentUser'),
|
||||
addedToStream = false;
|
||||
|
@ -401,38 +398,37 @@ Discourse.Composer = Discourse.Model.extend({
|
|||
|
||||
// Save callback
|
||||
var composer = this;
|
||||
createdPost.save(function(result) {
|
||||
var addedPost = false,
|
||||
saving = true;
|
||||
createdPost.updateFromSave(result);
|
||||
if (topic) {
|
||||
// It's no longer a new post
|
||||
createdPost.set('newPost', false);
|
||||
topic.set('draft_sequence', result.draft_sequence);
|
||||
} else {
|
||||
// We created a new topic, let's show it.
|
||||
composer.set('composeState', CLOSED);
|
||||
saving = false;
|
||||
}
|
||||
composer.set('reply', '');
|
||||
composer.set('createdPost', createdPost);
|
||||
if (addedToStream) {
|
||||
composer.set('composeState', CLOSED);
|
||||
} else if (saving) {
|
||||
composer.set('composeState', SAVING);
|
||||
}
|
||||
return promise.resolve({ post: result });
|
||||
}, function(error) {
|
||||
// If an error occurs
|
||||
var errors;
|
||||
if (topic) {
|
||||
topic.posts.removeObject(createdPost);
|
||||
}
|
||||
errors = $.parseJSON(error.responseText).errors;
|
||||
promise.reject(errors[0]);
|
||||
composer.set('composeState', OPEN);
|
||||
return Ember.Deferred.promise(function(promise) {
|
||||
createdPost.save(function(result) {
|
||||
var addedPost = false,
|
||||
saving = true;
|
||||
createdPost.updateFromSave(result);
|
||||
if (topic) {
|
||||
// It's no longer a new post
|
||||
createdPost.set('newPost', false);
|
||||
topic.set('draft_sequence', result.draft_sequence);
|
||||
} else {
|
||||
// We created a new topic, let's show it.
|
||||
composer.set('composeState', CLOSED);
|
||||
saving = false;
|
||||
}
|
||||
composer.set('reply', '');
|
||||
composer.set('createdPost', createdPost);
|
||||
if (addedToStream) {
|
||||
composer.set('composeState', CLOSED);
|
||||
} else if (saving) {
|
||||
composer.set('composeState', SAVING);
|
||||
}
|
||||
return promise.resolve({ post: result });
|
||||
}, function(error) {
|
||||
// If an error occurs
|
||||
if (topic) {
|
||||
topic.posts.removeObject(createdPost);
|
||||
}
|
||||
promise.reject($.parseJSON(error.responseText).errors[0]);
|
||||
composer.set('composeState', OPEN);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
saveDraft: function() {
|
||||
|
|
|
@ -22,20 +22,11 @@ Discourse.Draft.reopenClass({
|
|||
},
|
||||
|
||||
get: function(key) {
|
||||
var promise,
|
||||
_this = this;
|
||||
promise = new RSVP.Promise();
|
||||
$.ajax({
|
||||
return $.ajax({
|
||||
url: '/draft',
|
||||
data: {
|
||||
draft_key: key
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
return promise.resolve(data);
|
||||
}
|
||||
data: { draft_key: key },
|
||||
dataType: 'json'
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
getLocal: function(key, current) {
|
||||
|
@ -44,35 +35,16 @@ Discourse.Draft.reopenClass({
|
|||
},
|
||||
|
||||
save: function(key, sequence, data) {
|
||||
var promise;
|
||||
promise = new RSVP.Promise();
|
||||
data = typeof data === "string" ? data : JSON.stringify(data);
|
||||
$.ajax({
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: "/draft",
|
||||
data: {
|
||||
draft_key: key,
|
||||
data: data,
|
||||
sequence: sequence
|
||||
},
|
||||
success: function() {
|
||||
/* don't keep local
|
||||
*/
|
||||
|
||||
/* Discourse.KeyValueStore.remove("draft_#{key}")
|
||||
*/
|
||||
return promise.resolve();
|
||||
},
|
||||
error: function() {
|
||||
/* save local
|
||||
*/
|
||||
|
||||
/* Discourse.KeyValueStore.set(key: "draft_#{key}", value: data)
|
||||
*/
|
||||
return promise.reject();
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -15,28 +15,21 @@ Discourse.InviteList = Discourse.Model.extend({
|
|||
Discourse.InviteList.reopenClass({
|
||||
|
||||
findInvitedBy: function(user) {
|
||||
var promise;
|
||||
promise = new RSVP.Promise();
|
||||
$.ajax({
|
||||
url: "/users/" + (user.get('username_lower')) + "/invited.json",
|
||||
success: function(result) {
|
||||
var invitedList;
|
||||
invitedList = result.invited_list;
|
||||
if (invitedList.pending) {
|
||||
invitedList.pending = invitedList.pending.map(function(i) {
|
||||
return Discourse.Invite.create(i);
|
||||
});
|
||||
}
|
||||
if (invitedList.redeemed) {
|
||||
invitedList.redeemed = invitedList.redeemed.map(function(i) {
|
||||
return Discourse.Invite.create(i);
|
||||
});
|
||||
}
|
||||
invitedList.user = user;
|
||||
return promise.resolve(Discourse.InviteList.create(invitedList));
|
||||
return $.ajax({ url: "/users/" + (user.get('username_lower')) + "/invited.json" }).then(function (result) {
|
||||
var invitedList = result.invited_list;
|
||||
if (invitedList.pending) {
|
||||
invitedList.pending = invitedList.pending.map(function(i) {
|
||||
return Discourse.Invite.create(i);
|
||||
});
|
||||
}
|
||||
if (invitedList.redeemed) {
|
||||
invitedList.redeemed = invitedList.redeemed.map(function(i) {
|
||||
return Discourse.Invite.create(i);
|
||||
});
|
||||
}
|
||||
invitedList.user = user;
|
||||
return Discourse.InviteList.create(invitedList);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -215,22 +215,19 @@ Discourse.Post = Discourse.Model.extend({
|
|||
|
||||
// Load replies to this post
|
||||
loadReplies: function() {
|
||||
var promise,
|
||||
_this = this;
|
||||
promise = new RSVP.Promise();
|
||||
this.set('loadingReplies', true);
|
||||
this.set('replies', []);
|
||||
$.getJSON("/posts/" + (this.get('id')) + "/replies", function(loaded) {
|
||||
|
||||
var parent = this;
|
||||
return $.ajax({url: "/posts/" + (this.get('id')) + "/replies"}).then(function(loaded) {
|
||||
var replies = parent.get('replies');
|
||||
loaded.each(function(reply) {
|
||||
var post;
|
||||
post = Discourse.Post.create(reply);
|
||||
post.set('topic', _this.get('topic'));
|
||||
return _this.get('replies').pushObject(post);
|
||||
var post = Discourse.Post.create(reply);
|
||||
post.set('topic', parent.get('topic'));
|
||||
replies.pushObject(post);
|
||||
});
|
||||
_this.set('loadingReplies', false);
|
||||
return promise.resolve();
|
||||
parent.set('loadingReplies', false);
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
loadVersions: function(callback) {
|
||||
|
@ -293,43 +290,33 @@ Discourse.Post.reopenClass({
|
|||
return $.ajax("/posts/destroy_many", {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
post_ids: posts.map(function(p) {
|
||||
return p.get('id');
|
||||
})
|
||||
post_ids: posts.map(function(p) { return p.get('id'); })
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadVersion: function(postId, version, callback) {
|
||||
var _this = this;
|
||||
return $.getJSON("/posts/" + postId + ".json?version=" + version, function(result) {
|
||||
return callback(Discourse.Post.create(result));
|
||||
return $.ajax({url: "/posts/" + postId + ".json?version=" + version}).then(function(result) {
|
||||
return Discourse.Post.create(result);
|
||||
});
|
||||
},
|
||||
|
||||
loadByPostNumber: function(topicId, postId, callback) {
|
||||
var _this = this;
|
||||
return $.getJSON("/posts/by_number/" + topicId + "/" + postId + ".json", function(result) {
|
||||
return callback(Discourse.Post.create(result));
|
||||
loadByPostNumber: function(topicId, postId) {
|
||||
return $.ajax({url: "/posts/by_number/" + topicId + "/" + postId + ".json"}).then(function (result) {
|
||||
return Discourse.Post.create(result);
|
||||
});
|
||||
},
|
||||
|
||||
loadQuote: function(postId) {
|
||||
var promise,
|
||||
_this = this;
|
||||
promise = new RSVP.Promise();
|
||||
$.getJSON("/posts/" + postId + ".json", function(result) {
|
||||
var post;
|
||||
post = Discourse.Post.create(result);
|
||||
return promise.resolve(Discourse.BBCode.buildQuoteBBCode(post, post.get('raw')));
|
||||
return $.ajax({url: "/posts/" + postId + ".json"}).then(function(result) {
|
||||
var post = Discourse.Post.create(result);
|
||||
return Discourse.BBCode.buildQuoteBBCode(post, post.get('raw'));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
load: function(postId, callback) {
|
||||
var _this = this;
|
||||
return $.getJSON("/posts/" + postId + ".json", function(result) {
|
||||
return callback(Discourse.Post.create(result));
|
||||
load: function(postId) {
|
||||
return $.ajax({url: "/posts/" + postId + ".json"}).then(function (result) {
|
||||
return Discourse.Post.create(result);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -372,6 +372,20 @@ Discourse.Topic.reopenClass({
|
|||
MUTE: 0
|
||||
},
|
||||
|
||||
/**
|
||||
Find similar topics to a given title and body
|
||||
|
||||
@method findSimilar
|
||||
@param {String} title The current title
|
||||
@param {String} body The current body
|
||||
@returns A promise that will resolve to the topics
|
||||
**/
|
||||
findSimilarTo: function(title, body) {
|
||||
return $.ajax({url: "/topics/similar_to", data: {title: title, raw: body} }).then(function (results) {
|
||||
return results.map(function(topic) { return Discourse.Topic.create(topic) });
|
||||
});
|
||||
},
|
||||
|
||||
// Load a topic, but accepts a set of filters
|
||||
// options:
|
||||
// onLoad - the callback after the topic is loaded
|
||||
|
@ -408,20 +422,15 @@ Discourse.Topic.reopenClass({
|
|||
}
|
||||
|
||||
// Check the preload store. If not, load it via JSON
|
||||
promise = new RSVP.Promise();
|
||||
PreloadStore.get("topic_" + topicId, function() {
|
||||
return PreloadStore.get("topic_" + topicId, function() {
|
||||
return $.getJSON(url + ".json", data);
|
||||
}).then(function(result) {
|
||||
var first;
|
||||
first = result.posts.first();
|
||||
var first = result.posts.first();
|
||||
if (first && opts && opts.bestOf) {
|
||||
first.bestOfFirst = true;
|
||||
}
|
||||
return promise.resolve(result);
|
||||
}, function(result) {
|
||||
return promise.reject(result);
|
||||
return result;
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
// Create a topic from posts
|
||||
|
|
|
@ -10,42 +10,38 @@
|
|||
Discourse.TopicList = Discourse.Model.extend({
|
||||
|
||||
loadMoreTopics: function() {
|
||||
var moreUrl, promise,
|
||||
_this = this;
|
||||
promise = new RSVP.Promise();
|
||||
var moreUrl, _this = this;
|
||||
|
||||
if (moreUrl = this.get('more_topics_url')) {
|
||||
Discourse.URL.replaceState("/" + (this.get('filter')) + "/more");
|
||||
$.ajax(moreUrl, {
|
||||
success: function(result) {
|
||||
var newTopics, topicIds, topics, topicsAdded = 0;
|
||||
if (result) {
|
||||
// the new topics loaded from the server
|
||||
newTopics = Discourse.TopicList.topicsFrom(result);
|
||||
// the current topics
|
||||
topics = _this.get('topics');
|
||||
// keeps track of the ids of the current topics
|
||||
topicIds = [];
|
||||
topics.each(function(t) {
|
||||
topicIds[t.get('id')] = true;
|
||||
});
|
||||
// add new topics to the list of current topics if not already present
|
||||
newTopics.each(function(t) {
|
||||
if (!topicIds[t.get('id')]) {
|
||||
// highlight the first of the new topics so we can get a visual feedback
|
||||
t.set('highlight', topicsAdded++ === 0);
|
||||
return topics.pushObject(t);
|
||||
}
|
||||
});
|
||||
_this.set('more_topics_url', result.topic_list.more_topics_url);
|
||||
Discourse.set('transient.topicsList', _this);
|
||||
}
|
||||
return promise.resolve(result.topic_list.more_topics_url ? true : false);
|
||||
return $.ajax({url: moreUrl}).then(function (result) {
|
||||
var newTopics, topicIds, topics, topicsAdded = 0;
|
||||
if (result) {
|
||||
// the new topics loaded from the server
|
||||
newTopics = Discourse.TopicList.topicsFrom(result);
|
||||
// the current topics
|
||||
topics = _this.get('topics');
|
||||
// keeps track of the ids of the current topics
|
||||
topicIds = [];
|
||||
topics.each(function(t) {
|
||||
topicIds[t.get('id')] = true;
|
||||
});
|
||||
// add new topics to the list of current topics if not already present
|
||||
newTopics.each(function(t) {
|
||||
if (!topicIds[t.get('id')]) {
|
||||
// highlight the first of the new topics so we can get a visual feedback
|
||||
t.set('highlight', topicsAdded++ === 0);
|
||||
return topics.pushObject(t);
|
||||
}
|
||||
});
|
||||
_this.set('more_topics_url', result.topic_list.more_topics_url);
|
||||
Discourse.set('transient.topicsList', _this);
|
||||
}
|
||||
return result.topic_list.more_topics_url;
|
||||
});
|
||||
} else {
|
||||
promise.resolve(false);
|
||||
return null;
|
||||
}
|
||||
return promise;
|
||||
},
|
||||
|
||||
insert: function(json) {
|
||||
|
@ -90,7 +86,7 @@ Discourse.TopicList.reopenClass({
|
|||
},
|
||||
|
||||
list: function(menuItem) {
|
||||
var filter, found, list, promise, topic_list, url;
|
||||
var filter, list, promise, topic_list, url;
|
||||
filter = menuItem.name;
|
||||
topic_list = Discourse.TopicList.create();
|
||||
topic_list.set('inserted', Em.A());
|
||||
|
@ -101,19 +97,16 @@ Discourse.TopicList.reopenClass({
|
|||
}
|
||||
if (list = Discourse.get('transient.topicsList')) {
|
||||
if ((list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
|
||||
promise = new RSVP.Promise();
|
||||
list.set('loaded', true);
|
||||
promise.resolve(list);
|
||||
return promise;
|
||||
return Ember.Deferred.promise(function(promise) {
|
||||
promise.resolve(list);
|
||||
});
|
||||
}
|
||||
}
|
||||
Discourse.set('transient.topicsList', null);
|
||||
Discourse.set('transient.topicListScrollPos', null);
|
||||
promise = new RSVP.Promise();
|
||||
found = PreloadStore.contains('topic_list');
|
||||
PreloadStore.get("topic_list", function() {
|
||||
return $.getJSON(url);
|
||||
}).then(function(result) {
|
||||
|
||||
return PreloadStore.get("topic_list", function() { return $.getJSON(url) }).then(function(result) {
|
||||
topic_list.set('topics', Discourse.TopicList.topicsFrom(result));
|
||||
topic_list.set('can_create_topic', result.topic_list.can_create_topic);
|
||||
topic_list.set('more_topics_url', result.topic_list.more_topics_url);
|
||||
|
@ -125,9 +118,8 @@ Discourse.TopicList.reopenClass({
|
|||
topic_list.set('category', Discourse.Category.create(result.topic_list.filtered_category));
|
||||
}
|
||||
topic_list.set('loaded', true);
|
||||
return promise.resolve(topic_list);
|
||||
return topic_list;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
|
||||
}).property('username'),
|
||||
|
||||
/**
|
||||
/**
|
||||
Small version of this user's avatar.
|
||||
|
||||
@property avatarSmall
|
||||
|
@ -68,7 +68,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
|
||||
}).property('trust_level'),
|
||||
|
||||
/**
|
||||
/**
|
||||
Changes this user's username.
|
||||
|
||||
@method changeUsername
|
||||
|
@ -76,7 +76,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
@returns Result of ajax call
|
||||
**/
|
||||
changeUsername: function(newUsername) {
|
||||
return jQuery.ajax({
|
||||
return $.ajax({
|
||||
url: "/users/" + (this.get('username_lower')) + "/preferences/username",
|
||||
type: 'PUT',
|
||||
data: {
|
||||
|
@ -93,7 +93,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
@returns Result of ajax call
|
||||
**/
|
||||
changeEmail: function(email) {
|
||||
return jQuery.ajax({
|
||||
return $.ajax({
|
||||
url: "/users/" + (this.get('username_lower')) + "/preferences/email",
|
||||
type: 'PUT',
|
||||
data: {
|
||||
|
@ -121,7 +121,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
**/
|
||||
save: function(finished) {
|
||||
var _this = this;
|
||||
jQuery.ajax("/users/" + this.get('username').toLowerCase(), {
|
||||
$.ajax("/users/" + this.get('username').toLowerCase(), {
|
||||
data: this.getProperties('auto_track_topics_after_msecs',
|
||||
'bio_raw',
|
||||
'website',
|
||||
|
@ -153,7 +153,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
changePassword: function(callback) {
|
||||
var good;
|
||||
good = false;
|
||||
jQuery.ajax({
|
||||
$.ajax({
|
||||
url: '/session/forgot_password',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
|
@ -199,7 +199,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
var stream,
|
||||
_this = this;
|
||||
stream = this.get('stream');
|
||||
jQuery.ajax({
|
||||
$.ajax({
|
||||
url: "/user_actions/" + id + ".json",
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
|
@ -241,7 +241,7 @@ Discourse.User = Discourse.Model.extend({
|
|||
url += "&filter=" + (this.get('streamFilter'));
|
||||
}
|
||||
|
||||
return jQuery.ajax({
|
||||
return $.ajax({
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
|
@ -362,7 +362,7 @@ Discourse.User.reopenClass({
|
|||
@param {String} email An email address to check
|
||||
**/
|
||||
checkUsername: function(username, email) {
|
||||
return jQuery.ajax({
|
||||
return $.ajax({
|
||||
url: '/users/check_username',
|
||||
type: 'GET',
|
||||
data: {
|
||||
|
@ -465,7 +465,7 @@ Discourse.User.reopenClass({
|
|||
@returns Result of ajax call
|
||||
**/
|
||||
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
|
||||
return jQuery.ajax({
|
||||
return $.ajax({
|
||||
url: '/users',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
|
|
|
@ -2,10 +2,20 @@
|
|||
|
||||
<div class='contents'>
|
||||
|
||||
<div id='new-user-education' style='display: none'>
|
||||
<a href='#' {{action closeEducation target="view"}} class='close'>{{i18n ok}}</a>
|
||||
<div id='new-user-education' class='composer-popup' style='display: none'>
|
||||
<a href='#' {{action closeEducation}} class='close'>{{i18n ok}}</a>
|
||||
{{{controller.educationContents}}}
|
||||
</div>
|
||||
|
||||
{{{view.educationContents}}}
|
||||
<div id='similar-topics' class='composer-popup' style='display: none'>
|
||||
<a href='#' {{action closeSimilar}} class='close'>{{i18n ok}}</a>
|
||||
<h3>{{i18n composer.similar_topics}}<h3>
|
||||
|
||||
<ul class='topics'>
|
||||
{{#each controller.similarTopics}}
|
||||
<li>{{{topicLink this}}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class='control'>
|
||||
|
|
|
@ -19,36 +19,32 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||
'content.creatingTopic:topic',
|
||||
'content.showPreview',
|
||||
'content.hidePreview'],
|
||||
educationClosed: null,
|
||||
|
||||
composeState: (function() {
|
||||
var state;
|
||||
state = this.get('content.composeState');
|
||||
if (!state) {
|
||||
state = Discourse.Composer.CLOSED;
|
||||
}
|
||||
return state;
|
||||
}).property('content.composeState'),
|
||||
composeState: function() {
|
||||
var state = this.get('content.composeState');
|
||||
if (state) return state;
|
||||
return Discourse.Composer.CLOSED;
|
||||
}.property('content.composeState'),
|
||||
|
||||
draftStatus: (function() {
|
||||
return this.$('.saving-draft').text(this.get('content.draftStatus') || "");
|
||||
}).observes('content.draftStatus'),
|
||||
draftStatus: function() {
|
||||
this.$('.saving-draft').text(this.get('content.draftStatus') || "");
|
||||
}.observes('content.draftStatus'),
|
||||
|
||||
// Disable fields when we're loading
|
||||
loadingChanged: (function() {
|
||||
loadingChanged: function() {
|
||||
if (this.get('loading')) {
|
||||
$('#wmd-input, #reply-title').prop('disabled', 'disabled');
|
||||
} else {
|
||||
$('#wmd-input, #reply-title').prop('disabled', '');
|
||||
}
|
||||
}).observes('loading'),
|
||||
}.observes('loading'),
|
||||
|
||||
postMade: (function() {
|
||||
postMade: function() {
|
||||
if (this.present('controller.createdPost')) return 'created-post';
|
||||
return null;
|
||||
}).property('content.createdPost'),
|
||||
}.property('content.createdPost'),
|
||||
|
||||
observeReplyChanges: (function() {
|
||||
observeReplyChanges: function() {
|
||||
var _this = this;
|
||||
if (this.get('content.hidePreview')) return;
|
||||
Ember.run.next(null, function() {
|
||||
|
@ -65,89 +61,70 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||
}
|
||||
}
|
||||
});
|
||||
}).observes('content.reply', 'content.hidePreview'),
|
||||
}.observes('content.reply', 'content.hidePreview'),
|
||||
|
||||
closeEducation: function() {
|
||||
this.set('educationClosed', true);
|
||||
return false;
|
||||
},
|
||||
|
||||
fetchNewUserEducation: (function() {
|
||||
// If creating a topic, use topic_count, otherwise post_count
|
||||
var count, educationKey,
|
||||
_this = this;
|
||||
count = this.get('content.creatingTopic') ? Discourse.get('currentUser.topic_count') : Discourse.get('currentUser.reply_count');
|
||||
if (count >= Discourse.SiteSettings.educate_until_posts) {
|
||||
this.set('educationClosed', true);
|
||||
this.set('educationContents', '');
|
||||
return;
|
||||
}
|
||||
if (!this.get('controller.hasReply')) {
|
||||
return;
|
||||
}
|
||||
this.set('educationClosed', false);
|
||||
|
||||
// If visible update the text
|
||||
educationKey = this.get('content.creatingTopic') ? 'new-topic' : 'new-reply';
|
||||
return $.get("/education/" + educationKey).then(function(result) {
|
||||
return _this.set('educationContents', result);
|
||||
});
|
||||
}).observes('controller.hasReply', 'content.creatingTopic', 'Discourse.currentUser.reply_count'),
|
||||
|
||||
newUserEducationVisible: (function() {
|
||||
if (!this.get('educationContents')) return false;
|
||||
if (this.get('content.composeState') !== Discourse.Composer.OPEN) return false;
|
||||
if (!this.present('content.reply')) return false;
|
||||
if (this.get('educationClosed')) return false;
|
||||
return true;
|
||||
}).property('content.composeState', 'content.reply', 'educationClosed', 'educationContents'),
|
||||
|
||||
newUserEducationVisibilityChanged: (function() {
|
||||
var $panel;
|
||||
$panel = $('#new-user-education');
|
||||
if (this.get('newUserEducationVisible')) {
|
||||
return $panel.slideDown('fast');
|
||||
newUserEducationVisibilityChanged: function() {
|
||||
var $panel = $('#new-user-education');
|
||||
if (this.get('controller.newUserEducationVisible')) {
|
||||
$panel.slideDown('fast');
|
||||
} else {
|
||||
return $panel.slideUp('fast');
|
||||
$panel.slideUp('fast')
|
||||
}
|
||||
}).observes('newUserEducationVisible'),
|
||||
}.observes('controller.newUserEducationVisible'),
|
||||
|
||||
moveNewUserEducation: function(sizePx) {
|
||||
$('#new-user-education').css('bottom', sizePx);
|
||||
similarVisibilityChanged: function() {
|
||||
var $panel = $('#similar-topics');
|
||||
if (this.get('controller.similarVisible')) {
|
||||
$panel.slideDown('fast');
|
||||
} else {
|
||||
$panel.slideUp('fast')
|
||||
}
|
||||
}.observes('controller.similarVisible'),
|
||||
|
||||
movePanels: function(sizePx) {
|
||||
$('.composer-popup').css('bottom', sizePx);
|
||||
},
|
||||
|
||||
resize: (function() {
|
||||
resize: function() {
|
||||
// this still needs to wait on animations, need a clean way to do that
|
||||
var _this = this;
|
||||
return Em.run.next(null, function() {
|
||||
var h, replyControl, sizePx;
|
||||
replyControl = $('#reply-control');
|
||||
h = replyControl.height() || 0;
|
||||
sizePx = "" + h + "px";
|
||||
var replyControl = $('#reply-control');
|
||||
var h = replyControl.height() || 0;
|
||||
var sizePx = "" + h + "px";
|
||||
$('.topic-area').css('padding-bottom', sizePx);
|
||||
return $('#new-user-education').css('bottom', sizePx);
|
||||
$('.composer-popup').css('bottom', sizePx);
|
||||
});
|
||||
}).observes('content.composeState'),
|
||||
}.observes('content.composeState'),
|
||||
|
||||
keyUp: function(e) {
|
||||
var controller;
|
||||
controller = this.get('controller');
|
||||
var controller = this.get('controller');
|
||||
controller.checkReplyLength();
|
||||
|
||||
var lastKeyUp = new Date();
|
||||
this.set('lastKeyUp', lastKeyUp);
|
||||
|
||||
// One second from now, check to see if the last key was hit when
|
||||
// we recorded it. If it was, the user paused typing.
|
||||
var composerView = this;
|
||||
Em.run.later(function() {
|
||||
if (lastKeyUp !== composerView.get('lastKeyUp')) return;
|
||||
|
||||
// Search for similar topics if the user pauses typing
|
||||
controller.findSimilarTopics();
|
||||
}, 1000);
|
||||
|
||||
// If the user hit ESC
|
||||
if (e.which === 27) controller.hitEsc();
|
||||
},
|
||||
|
||||
didInsertElement: function() {
|
||||
var replyControl;
|
||||
replyControl = $('#reply-control');
|
||||
replyControl.DivResizer({
|
||||
resize: this.resize,
|
||||
onDrag: this.moveNewUserEducation
|
||||
});
|
||||
var replyControl = $('#reply-control');
|
||||
replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
|
||||
Discourse.TransitionHelper.after(replyControl, this.resize);
|
||||
},
|
||||
|
||||
click: function() {
|
||||
return this.get('controller').click();
|
||||
this.get('controller').openIfDraft();
|
||||
},
|
||||
|
||||
// Called after the preview renders. Debounced for performance
|
||||
|
@ -229,7 +206,7 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||
});
|
||||
},
|
||||
onChangeItems: function(items) {
|
||||
items = jQuery.map(items, function(i) {
|
||||
items = $.map(items, function(i) {
|
||||
if (i.username) {
|
||||
return i.username;
|
||||
} else {
|
||||
|
@ -243,9 +220,7 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||
transformComplete: transformTemplate,
|
||||
|
||||
reverseTransform: function(i) {
|
||||
return {
|
||||
username: i
|
||||
};
|
||||
return { username: i };
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -58,21 +58,23 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
|
|||
},
|
||||
|
||||
loadMore: function() {
|
||||
var _this = this;
|
||||
if (this.get('loading')) {
|
||||
return;
|
||||
}
|
||||
if (this.get('loading')) return;
|
||||
this.set('loading', true);
|
||||
return this.get('controller.content').loadMoreTopics().then(function(hasMoreResults) {
|
||||
_this.set('loadedMore', true);
|
||||
_this.set('loading', false);
|
||||
Em.run.next(function() {
|
||||
return _this.saveScrollPos();
|
||||
|
||||
var listTopicsView = this;
|
||||
var promise = this.get('controller.content').loadMoreTopics();
|
||||
if (promise) {
|
||||
promise.then(function(hasMoreResults) {
|
||||
listTopicsView.set('loadedMore', true);
|
||||
listTopicsView.set('loading', false);
|
||||
Em.run.next(function() { listTopicsView.saveScrollPos(); });
|
||||
if (!hasMoreResults) {
|
||||
listTopicsView.get('eyeline').flushRest();
|
||||
}
|
||||
});
|
||||
if (!hasMoreResults) {
|
||||
return _this.get('eyeline').flushRest();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.set('loading', false);
|
||||
}
|
||||
},
|
||||
|
||||
// Remember where we were scrolled to
|
||||
|
|
|
@ -12,28 +12,27 @@ Discourse.HistoryView = Discourse.View.extend({
|
|||
modalClass: 'history-modal',
|
||||
|
||||
loadSide: function(side) {
|
||||
var orig, version,
|
||||
_this = this;
|
||||
if (this.get("version" + side)) {
|
||||
orig = this.get('originalPost');
|
||||
version = this.get("version" + side + ".number");
|
||||
var orig = this.get('originalPost');
|
||||
var version = this.get("version" + side + ".number");
|
||||
if (version === orig.get('version')) {
|
||||
return this.set("post" + side, orig);
|
||||
this.set("post" + side, orig);
|
||||
} else {
|
||||
return Discourse.Post.loadVersion(orig.get('id'), version, function(post) {
|
||||
return _this.set("post" + side, post);
|
||||
var historyView = this;
|
||||
Discourse.Post.loadVersion(orig.get('id'), version).then(function(post) {
|
||||
historyView.set("post" + side, post);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
changedLeftVersion: (function() {
|
||||
return this.loadSide("Left");
|
||||
}).observes('versionLeft'),
|
||||
changedLeftVersion: function() {
|
||||
this.loadSide("Left");
|
||||
}.observes('versionLeft'),
|
||||
|
||||
changedRightVersion: (function() {
|
||||
return this.loadSide("Right");
|
||||
}).observes('versionRight'),
|
||||
changedRightVersion: function() {
|
||||
this.loadSide("Right");
|
||||
}.observes('versionRight'),
|
||||
|
||||
didInsertElement: function() {
|
||||
var _this = this;
|
||||
|
|
|
@ -109,9 +109,8 @@ Discourse.PostView = Discourse.View.extend({
|
|||
|
||||
// Toggle visibility of parent post
|
||||
toggleParent: function(e) {
|
||||
var $parent, post,
|
||||
_this = this;
|
||||
$parent = this.$('.parent-post');
|
||||
var postView = this;
|
||||
var $parent = this.$('.parent-post');
|
||||
if (this.get('parentPost')) {
|
||||
$('nav', $parent).removeClass('toggled');
|
||||
// Don't animate on touch
|
||||
|
@ -119,19 +118,18 @@ Discourse.PostView = Discourse.View.extend({
|
|||
$parent.hide();
|
||||
this.set('parentPost', null);
|
||||
} else {
|
||||
$parent.slideUp(function() {
|
||||
return _this.set('parentPost', null);
|
||||
});
|
||||
$parent.slideUp(function() { postView.set('parentPost', null); });
|
||||
}
|
||||
} else {
|
||||
post = this.get('post');
|
||||
var post = this.get('post');
|
||||
this.set('loadingParent', true);
|
||||
$('nav', $parent).addClass('toggled');
|
||||
Discourse.Post.loadByPostNumber(post.get('topic_id'), post.get('reply_to_post_number'), function(result) {
|
||||
_this.set('loadingParent', false);
|
||||
|
||||
Discourse.Post.loadByPostNumber(post.get('topic_id'), post.get('reply_to_post_number')).then(function(result) {
|
||||
postView.set('loadingParent', false);
|
||||
// Give the post a reference back to the topic
|
||||
result.topic = _this.get('post.topic');
|
||||
return _this.set('parentPost', result);
|
||||
result.topic = postView.get('post.topic');
|
||||
postView.set('parentPost', result);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -235,7 +235,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
|
|||
if (this.loading) return;
|
||||
this.set('loading', true);
|
||||
this.set('loadingAbove', true);
|
||||
opts = jQuery.extend({
|
||||
opts = $.extend({
|
||||
postsBefore: post.get('post_number')
|
||||
}, this.get('controller.postFilters'));
|
||||
|
||||
|
@ -303,9 +303,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
|
|||
if (this.topic.posts.last().post_number !== post.post_number) return;
|
||||
this.set('loadingBelow', true);
|
||||
this.set('loading', true);
|
||||
opts = jQuery.extend({
|
||||
postsAfter: post.get('post_number')
|
||||
}, this.get('controller.postFilters'));
|
||||
opts = $.extend({ postsAfter: post.get('post_number') }, this.get('controller.postFilters'));
|
||||
return Discourse.Topic.find(this.get('topic.id'), opts).then(function(result) {
|
||||
var suggested;
|
||||
if (result.at_bottom || result.posts.length === 0) {
|
||||
|
|
|
@ -28,34 +28,34 @@ PreloadStore = {
|
|||
@method get
|
||||
@param {String} key the key to look up the object with
|
||||
@param {function} finder a function to find the object with
|
||||
@returns {Promise} a promise that will eventually be the object we want.
|
||||
@returns {Ember.Deferred} a promise that will eventually be the object we want.
|
||||
**/
|
||||
get: function(key, finder) {
|
||||
var promise = new RSVP.Promise();
|
||||
|
||||
if (this.data[key]) {
|
||||
promise.resolve(this.data[key]);
|
||||
delete this.data[key];
|
||||
} else {
|
||||
|
||||
if (finder) {
|
||||
var result = finder();
|
||||
|
||||
// If the finder returns a promise, we support that too
|
||||
if (result.then) {
|
||||
result.then(function(result) {
|
||||
return promise.resolve(result);
|
||||
}, function(result) {
|
||||
return promise.reject(result);
|
||||
});
|
||||
} else {
|
||||
promise.resolve(result);
|
||||
}
|
||||
var preloadStore = this;
|
||||
return Ember.Deferred.promise(function(promise) {
|
||||
if (preloadStore.data[key]) {
|
||||
promise.resolve(preloadStore.data[key]);
|
||||
delete preloadStore.data[key];
|
||||
} else {
|
||||
promise.resolve(null);
|
||||
|
||||
if (finder) {
|
||||
var result = finder();
|
||||
|
||||
// If the finder returns a promise, we support that too
|
||||
if (result.then) {
|
||||
result.then(function(result) {
|
||||
return promise.resolve(result);
|
||||
}, function(result) {
|
||||
return promise.reject(result);
|
||||
});
|
||||
} else {
|
||||
promise.resolve(result);
|
||||
}
|
||||
} else {
|
||||
promise.resolve(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return promise;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
@import "foundation/mixins";
|
||||
|
||||
|
||||
#new-user-education {
|
||||
.composer-popup {
|
||||
|
||||
@include box-shadow(3px 3px 3px rgba($black, 0.14));
|
||||
|
||||
|
@ -17,10 +17,21 @@
|
|||
}
|
||||
|
||||
background-color: lighten($yellow, 40%);
|
||||
border: 1px solid $yellow;
|
||||
border: 1px solid darken($yellow, 5%);
|
||||
padding: 10px;
|
||||
width: 600px;
|
||||
position: absolute;
|
||||
|
||||
ul.topics {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
font-weight: normal;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#reply-control {
|
||||
|
|
|
@ -59,6 +59,17 @@ class TopicsController < ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
|
||||
def similar_to
|
||||
requires_parameters(:title, :raw)
|
||||
title, raw = params[:title], params[:raw]
|
||||
|
||||
raise Discourse::InvalidParameters.new(:title) if title.length < SiteSetting.min_title_similar_length
|
||||
raise Discourse::InvalidParameters.new(:raw) if raw.length < SiteSetting.min_body_similar_length
|
||||
|
||||
topics = Topic.similar_to(title, raw)
|
||||
render_serialized(topics, BasicTopicSerializer)
|
||||
end
|
||||
|
||||
def status
|
||||
requires_parameters(:status, :enabled)
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ class SiteSetting < ActiveRecord::Base
|
|||
client_setting(:supress_reply_directly_below, true)
|
||||
client_setting(:email_domains_blacklist, 'mailinator.com')
|
||||
client_setting(:version_checks, true)
|
||||
client_setting(:min_title_similar_length, 10)
|
||||
client_setting(:min_body_similar_length, 15)
|
||||
|
||||
# settings only available server side
|
||||
setting(:auto_track_topics_after, 300000)
|
||||
|
|
|
@ -213,8 +213,22 @@ class Topic < ActiveRecord::Base
|
|||
id).to_a
|
||||
end
|
||||
|
||||
def update_status(property, status, user)
|
||||
# Search for similar topics
|
||||
def self.similar_to(title, raw)
|
||||
return [] unless title.present?
|
||||
return [] unless raw.present?
|
||||
|
||||
# For now, we only match on title. We'll probably add body later on, hence the API hook
|
||||
Topic.select(sanitize_sql_array(["topics.*, similarity(topics.title, :title) AS similarity", title: title]))
|
||||
.visible
|
||||
.where(closed: false, archived: false)
|
||||
.listable_topics
|
||||
.limit(5)
|
||||
.order('similarity desc')
|
||||
.all
|
||||
end
|
||||
|
||||
def update_status(property, status, user)
|
||||
Topic.transaction do
|
||||
|
||||
# Special case: if it's pinned, update that
|
||||
|
@ -592,7 +606,6 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
def clear_pin_for(user)
|
||||
return unless user.present?
|
||||
|
||||
TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,58 +1,9 @@
|
|||
require_dependency 'age_words'
|
||||
|
||||
# The most basic attributes of a topic that we need to create a link for it.
|
||||
class BasicTopicSerializer < ApplicationSerializer
|
||||
include ActionView::Helpers
|
||||
|
||||
attributes :id,
|
||||
:title,
|
||||
:fancy_title,
|
||||
:reply_count,
|
||||
:posts_count,
|
||||
:highest_post_number,
|
||||
:image_url,
|
||||
:created_at,
|
||||
:last_posted_at,
|
||||
:age,
|
||||
:unseen,
|
||||
:last_read_post_number,
|
||||
:unread,
|
||||
:new_posts
|
||||
|
||||
def age
|
||||
AgeWords.age_words(Time.now - (object.created_at || Time.now))
|
||||
end
|
||||
|
||||
def seen
|
||||
object.user_data.present?
|
||||
end
|
||||
|
||||
def unseen
|
||||
return false if scope.blank?
|
||||
return false if scope.user.blank?
|
||||
return false if object.user_data.present?
|
||||
return false if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
true
|
||||
end
|
||||
|
||||
def last_read_post_number
|
||||
object.user_data.last_read_post_number
|
||||
end
|
||||
alias :include_last_read_post_number? :seen
|
||||
|
||||
def unread
|
||||
unread_helper.unread_posts
|
||||
end
|
||||
alias :include_unread? :seen
|
||||
|
||||
def new_posts
|
||||
unread_helper.new_posts
|
||||
end
|
||||
alias :include_new_posts? :seen
|
||||
|
||||
protected
|
||||
|
||||
def unread_helper
|
||||
@unread_helper ||= Unread.new(object, object.user_data)
|
||||
end
|
||||
attributes :id, :fancy_title, :slug
|
||||
|
||||
end
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
class CategoryTopicSerializer < BasicTopicSerializer
|
||||
|
||||
attributes :slug,
|
||||
:visible,
|
||||
:closed,
|
||||
:archived
|
||||
class CategoryTopicSerializer < ListableTopicSerializer
|
||||
|
||||
attributes :visible, :closed, :archived
|
||||
has_one :category
|
||||
|
||||
end
|
||||
|
|
56
app/serializers/listable_topic_serializer.rb
Normal file
56
app/serializers/listable_topic_serializer.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require_dependency 'age_words'
|
||||
|
||||
class ListableTopicSerializer < BasicTopicSerializer
|
||||
include ActionView::Helpers
|
||||
|
||||
attributes :reply_count,
|
||||
:posts_count,
|
||||
:highest_post_number,
|
||||
:image_url,
|
||||
:created_at,
|
||||
:last_posted_at,
|
||||
:age,
|
||||
:unseen,
|
||||
:last_read_post_number,
|
||||
:unread,
|
||||
:new_posts,
|
||||
:title
|
||||
|
||||
def age
|
||||
AgeWords.age_words(Time.now - (object.created_at || Time.now))
|
||||
end
|
||||
|
||||
def seen
|
||||
object.user_data.present?
|
||||
end
|
||||
|
||||
def unseen
|
||||
return false if scope.blank?
|
||||
return false if scope.user.blank?
|
||||
return false if object.user_data.present?
|
||||
return false if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
true
|
||||
end
|
||||
|
||||
def last_read_post_number
|
||||
object.user_data.last_read_post_number
|
||||
end
|
||||
alias :include_last_read_post_number? :seen
|
||||
|
||||
def unread
|
||||
unread_helper.unread_posts
|
||||
end
|
||||
alias :include_unread? :seen
|
||||
|
||||
def new_posts
|
||||
unread_helper.new_posts
|
||||
end
|
||||
alias :include_new_posts? :seen
|
||||
|
||||
protected
|
||||
|
||||
def unread_helper
|
||||
@unread_helper ||= Unread.new(object, object.user_data)
|
||||
end
|
||||
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
class SuggestedTopicSerializer < BasicTopicSerializer
|
||||
class SuggestedTopicSerializer < ListableTopicSerializer
|
||||
|
||||
attributes :archetype, :slug, :like_count, :views, :last_post_age
|
||||
attributes :archetype, :like_count, :views, :last_post_age
|
||||
has_one :category, embed: :objects
|
||||
|
||||
def last_post_age
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require_dependency 'pinned_check'
|
||||
|
||||
class TopicListItemSerializer < BasicTopicSerializer
|
||||
class TopicListItemSerializer < ListableTopicSerializer
|
||||
|
||||
attributes :views,
|
||||
:like_count,
|
||||
|
@ -11,8 +11,7 @@ class TopicListItemSerializer < BasicTopicSerializer
|
|||
:last_post_age,
|
||||
:starred,
|
||||
:has_best_of,
|
||||
:archetype,
|
||||
:slug
|
||||
:archetype
|
||||
|
||||
has_one :category
|
||||
has_many :posters, serializer: TopicPosterSerializer, embed: :objects
|
||||
|
|
|
@ -267,6 +267,7 @@ en:
|
|||
saving_draft_tip: "saving"
|
||||
saved_draft_tip: "saved"
|
||||
saved_local_draft_tip: "saved locally"
|
||||
similar_topics: "Similar Topics"
|
||||
|
||||
min_length:
|
||||
at_least: "enter at least {{n}} characters"
|
||||
|
|
|
@ -408,6 +408,9 @@ en:
|
|||
new_user_period_days: "How long a user is highlighted as being new, in days"
|
||||
title_fancy_entities: "Convert fancy HTML entities in topic titles"
|
||||
|
||||
min_title_similar_length: "The minimum length of a title before it will be checked for similar topics"
|
||||
min_body_similar_length: "The minimum length of a post's body before it will be checked for similar topics"
|
||||
|
||||
notification_types:
|
||||
mentioned: "%{display_username} mentioned you in %{link}"
|
||||
liked: "%{display_username} liked your post in %{link}"
|
||||
|
|
|
@ -164,7 +164,8 @@ Discourse::Application.routes.draw do
|
|||
delete 't/:id' => 'topics#destroy'
|
||||
put 't/:id' => 'topics#update'
|
||||
post 't' => 'topics#create'
|
||||
post 'topics/timings' => 'topics#timings'
|
||||
post 'topics/timings'
|
||||
get 'topics/similar_to'
|
||||
|
||||
# Legacy route for old avatars
|
||||
get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', :constraints => {:topic_id => /\d+/, :post_number => /\d+/}
|
||||
|
|
9
db/migrate/20130315180637_enable_trigram_support.rb
Normal file
9
db/migrate/20130315180637_enable_trigram_support.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class EnableTrigramSupport < ActiveRecord::Migration
|
||||
def up
|
||||
execute "CREATE EXTENSION IF NOT EXISTS pg_trgm"
|
||||
end
|
||||
|
||||
def down
|
||||
execute "DROP EXTENSION pg_trgm"
|
||||
end
|
||||
end
|
|
@ -70,10 +70,39 @@ describe TopicsController do
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'similar_to' do
|
||||
|
||||
let(:title) { 'this title is long enough to search for' }
|
||||
let(:raw) { 'this body is long enough to search for' }
|
||||
|
||||
it "requires a title" do
|
||||
-> { xhr :get, :similar_to, raw: raw }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "requires a raw body" do
|
||||
-> { xhr :get, :similar_to, title: title }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "raises an error if the title length is below the minimum" do
|
||||
SiteSetting.stubs(:min_title_similar_length).returns(100)
|
||||
-> { xhr :get, :similar_to, title: title, raw: raw }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "raises an error if the body length is below the minimum" do
|
||||
SiteSetting.stubs(:min_body_similar_length).returns(100)
|
||||
-> { xhr :get, :similar_to, title: title, raw: raw }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "delegates to Topic.similar_to" do
|
||||
Topic.expects(:similar_to).with(title, raw).returns([Fabricate(:topic)])
|
||||
xhr :get, :similar_to, title: title, raw: raw
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'clear_pin' do
|
||||
it 'needs you to be logged in' do
|
||||
lambda { xhr :put, :clear_pin, topic_id: 1 }.should raise_error(Discourse::NotLoggedIn)
|
||||
|
|
|
@ -68,9 +68,7 @@ describe("PreloadStore", function() {
|
|||
var done, finder, storeResult;
|
||||
done = storeResult = null;
|
||||
finder = function() {
|
||||
var promise = new RSVP.Promise();
|
||||
promise.resolve('evil');
|
||||
return promise;
|
||||
return Ember.Deferred.promise(function(promise) { promise.resolve('evil'); });
|
||||
};
|
||||
PreloadStore.get('joker', finder).then(function(result) {
|
||||
done = true;
|
||||
|
@ -86,9 +84,7 @@ describe("PreloadStore", function() {
|
|||
var done, finder, storeResult;
|
||||
done = storeResult = null;
|
||||
finder = function() {
|
||||
var promise = new RSVP.Promise();
|
||||
promise.reject('evil');
|
||||
return promise;
|
||||
return Ember.Deferred.promise(function(promise) { promise.reject('evil'); });
|
||||
};
|
||||
PreloadStore.get('joker', finder).then(null, function(rejectedResult) {
|
||||
done = true;
|
||||
|
|
|
@ -155,6 +155,24 @@ describe Topic do
|
|||
end
|
||||
|
||||
|
||||
context 'similar_to' do
|
||||
|
||||
it 'returns blank with nil params' do
|
||||
Topic.similar_to(nil, nil).should be_blank
|
||||
end
|
||||
|
||||
context 'with a similar topic' do
|
||||
let!(:topic) { Fabricate(:topic, title: "Evil trout is the dude who posted this topic") }
|
||||
|
||||
it 'returns the similar topic if the title is similar' do
|
||||
Topic.similar_to("has evil trout made any topics?", "i am wondering has evil trout made any topics?").should == [topic]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
context 'message bus' do
|
||||
it 'calls the message bus observer after create' do
|
||||
MessageBusObserver.any_instance.expects(:after_create_topic).with(instance_of(Topic))
|
||||
|
|
Loading…
Reference in a new issue