mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -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)
|
turbo-sprockets-rails3 (0.3.6)
|
||||||
railties (> 3.2.8, < 4.0.0)
|
railties (> 3.2.8, < 4.0.0)
|
||||||
sprockets (>= 2.0.0)
|
sprockets (>= 2.0.0)
|
||||||
tzinfo (0.3.35)
|
tzinfo (0.3.37)
|
||||||
uglifier (1.3.0)
|
uglifier (1.3.0)
|
||||||
execjs (>= 0.3.0)
|
execjs (>= 0.3.0)
|
||||||
multi_json (~> 1.0, >= 1.0.2)
|
multi_json (~> 1.0, >= 1.0.2)
|
||||||
|
|
|
@ -156,15 +156,9 @@ Discourse.AdminUser.reopenClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
find: function(username) {
|
find: function(username) {
|
||||||
var promise;
|
return $.ajax({url: "/admin/users/" + username}).then(function (result) {
|
||||||
promise = new RSVP.Promise();
|
return Discourse.AdminUser.create(result);
|
||||||
$.ajax({
|
})
|
||||||
url: "/admin/users/" + username,
|
|
||||||
success: function(result) {
|
|
||||||
return promise.resolve(Discourse.AdminUser.create(result));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
findAll: function(query, filter) {
|
findAll: function(query, filter) {
|
||||||
|
|
|
@ -46,47 +46,15 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
||||||
}).property('topic_hidden'),
|
}).property('topic_hidden'),
|
||||||
|
|
||||||
deletePost: function() {
|
deletePost: function() {
|
||||||
var promise;
|
|
||||||
promise = new RSVP.Promise();
|
|
||||||
if (this.get('post_number') === "1") {
|
if (this.get('post_number') === "1") {
|
||||||
return $.ajax("/t/" + this.topic_id, {
|
return $.ajax("/t/" + this.topic_id, { type: 'DELETE', cache: false });
|
||||||
type: 'DELETE',
|
|
||||||
cache: false,
|
|
||||||
success: function() {
|
|
||||||
promise.resolve();
|
|
||||||
},
|
|
||||||
error: function(e) {
|
|
||||||
promise.reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return $.ajax("/posts/" + this.id, {
|
return $.ajax("/posts/" + this.id, { type: 'DELETE', cache: false });
|
||||||
type: 'DELETE',
|
|
||||||
cache: false,
|
|
||||||
success: function() {
|
|
||||||
promise.resolve();
|
|
||||||
},
|
|
||||||
error: function(e) {
|
|
||||||
promise.reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clearFlags: function() {
|
clearFlags: function() {
|
||||||
var promise;
|
return $.ajax("/admin/flags/clear/" + this.id, { type: 'POST', cache: false });
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hiddenClass: (function() {
|
hiddenClass: (function() {
|
||||||
|
|
|
@ -26,14 +26,8 @@ Discourse.VersionCheck = Discourse.Model.extend({
|
||||||
|
|
||||||
Discourse.VersionCheck.reopenClass({
|
Discourse.VersionCheck.reopenClass({
|
||||||
find: function() {
|
find: function() {
|
||||||
var promise = new RSVP.Promise();
|
return $.ajax({ url: '/admin/version_check', dataType: 'json' }).then(function(json) {
|
||||||
$.ajax({
|
return Discourse.VersionCheck.create(json);
|
||||||
url: '/admin/version_check',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(json) {
|
|
||||||
promise.resolve(Discourse.VersionCheck.create(json));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -8,22 +8,20 @@
|
||||||
**/
|
**/
|
||||||
Discourse.ComposerController = Discourse.Controller.extend({
|
Discourse.ComposerController = Discourse.Controller.extend({
|
||||||
needs: ['modal', 'topic'],
|
needs: ['modal', 'topic'],
|
||||||
hasReply: false,
|
|
||||||
|
|
||||||
togglePreview: function() {
|
togglePreview: function() {
|
||||||
return this.get('content').togglePreview();
|
this.get('content').togglePreview();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Import a quote from the post
|
// Import a quote from the post
|
||||||
importQuote: function() {
|
importQuote: function() {
|
||||||
return this.get('content').importQuote();
|
this.get('content').importQuote();
|
||||||
},
|
},
|
||||||
|
|
||||||
appendText: function(text) {
|
appendText: function(text) {
|
||||||
var c;
|
var c = this.get('content');
|
||||||
c = this.get('content');
|
|
||||||
if (c) {
|
if (c) {
|
||||||
return c.appendText(text);
|
c.appendText(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -72,7 +70,6 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
bootbox.dialog(message, buttons);
|
bootbox.dialog(message, buttons);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,17 +91,80 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
checkReplyLength: function() {
|
closeEducation: function() {
|
||||||
if (this.present('content.reply')) {
|
this.set('educationClosed', true);
|
||||||
this.set('hasReply', true);
|
},
|
||||||
} else {
|
|
||||||
this.set('hasReply', false);
|
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() {
|
saveDraft: function() {
|
||||||
var model;
|
var model = this.get('content');
|
||||||
model = this.get('content');
|
|
||||||
if (model) model.saveDraft();
|
if (model) model.saveDraft();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -123,8 +183,11 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
||||||
_this = this;
|
_this = this;
|
||||||
if (!opts) opts = {};
|
if (!opts) opts = {};
|
||||||
|
|
||||||
opts.promise = promise = opts.promise || new RSVP.Promise();
|
opts.promise = promise = opts.promise || Ember.Deferred.create();
|
||||||
this.set('hasReply', false);
|
this.set('typedReply', false);
|
||||||
|
this.set('similarTopics', null);
|
||||||
|
this.set('similarClosed', false);
|
||||||
|
|
||||||
if (!opts.draftKey) {
|
if (!opts.draftKey) {
|
||||||
alert("composer was opened without a draft key");
|
alert("composer was opened without a draft key");
|
||||||
throw "composer opened without a proper 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
|
// ensure we have a view now, without it transitions are going to be messed
|
||||||
view = this.get('view');
|
view = this.get('view');
|
||||||
if (!view) {
|
if (!view) {
|
||||||
view = Discourse.ComposerView.create({
|
view = Discourse.ComposerView.create({ controller: this });
|
||||||
controller: this
|
|
||||||
});
|
|
||||||
view.appendTo($('#main'));
|
view.appendTo($('#main'));
|
||||||
this.set('view', view);
|
this.set('view', view);
|
||||||
// the next runloop is too soon, need to get the control rendered and then
|
// 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() {
|
wouldLoseChanges: function() {
|
||||||
var composer;
|
var composer = this.get('content');
|
||||||
composer = this.get('content');
|
|
||||||
return composer && composer.wouldLoseChanges();
|
return composer && composer.wouldLoseChanges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -210,10 +270,9 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyDraft: function() {
|
destroyDraft: function() {
|
||||||
var key;
|
var key = this.get('content.draftKey');
|
||||||
key = this.get('content.draftKey');
|
|
||||||
if (key) {
|
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) {
|
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() {
|
shrink: function() {
|
||||||
if (this.get('content.reply') === this.get('content.originalText')) {
|
if (this.get('content.reply') === this.get('content.originalText')) {
|
||||||
return this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
return this.collapse();
|
this.collapse();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -42,13 +42,11 @@ Discourse.ListController = Discourse.Controller.extend({
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
|
|
||||||
if (filterMode === 'categories') {
|
if (filterMode === 'categories') {
|
||||||
return Ember.Deferred.promise(function(deferred) {
|
return Discourse.CategoryList.list(filterMode).then(function(items) {
|
||||||
Discourse.CategoryList.list(filterMode).then(function(items) {
|
listController.set('loading', false);
|
||||||
listController.set('loading', false);
|
listController.set('filterMode', filterMode);
|
||||||
listController.set('filterMode', filterMode);
|
listController.set('categoryMode', true);
|
||||||
listController.set('categoryMode', true);
|
return items;
|
||||||
return deferred.resolve(items);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +54,11 @@ Discourse.ListController = Discourse.Controller.extend({
|
||||||
if (!current) {
|
if (!current) {
|
||||||
current = Discourse.NavItem.create({ name: filterMode });
|
current = Discourse.NavItem.create({ name: filterMode });
|
||||||
}
|
}
|
||||||
return Ember.Deferred.promise(function(deferred) {
|
return Discourse.TopicList.list(current).then(function(items) {
|
||||||
Discourse.TopicList.list(current).then(function(items) {
|
listController.set('filterSummary', items.filter_summary);
|
||||||
listController.set('filterSummary', items.filter_summary);
|
listController.set('filterMode', filterMode);
|
||||||
listController.set('filterMode', filterMode);
|
listController.set('loading', false);
|
||||||
listController.set('loading', false);
|
return items;
|
||||||
return deferred.resolve(items);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -117,19 +117,18 @@ Discourse.TopicController = Discourse.ObjectController.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
replyAsNewTopic: function(post) {
|
replyAsNewTopic: function(post) {
|
||||||
var composerController, postLink, postUrl, promise;
|
|
||||||
composerController = this.get('controllers.composer');
|
|
||||||
|
|
||||||
// TODO shut down topic draft cleanly if it exists ...
|
// 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,
|
action: Discourse.Composer.CREATE_TOPIC,
|
||||||
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
|
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
|
||||||
});
|
});
|
||||||
postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
|
var postUrl = "" + location.protocol + "//" + location.host + (post.get('url'));
|
||||||
postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
|
var postLink = "[" + (this.get('title')) + "](" + postUrl + ")";
|
||||||
return promise.then(function() {
|
|
||||||
return Discourse.Post.loadQuote(post.get('id')).then(function(q) {
|
promise.then(function() {
|
||||||
return composerController.appendText("" + (Em.String.i18n("post.continue_discussion", {
|
Discourse.Post.loadQuote(post.get('id')).then(function(q) {
|
||||||
|
composerController.appendText("" + (Em.String.i18n("post.continue_discussion", {
|
||||||
postLink: postLink
|
postLink: postLink
|
||||||
})) + "\n\n" + q);
|
})) + "\n\n" + q);
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,8 +40,6 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||||
act: function(opts) {
|
act: function(opts) {
|
||||||
|
|
||||||
// Mark it as acted
|
// Mark it as acted
|
||||||
var promise,
|
|
||||||
_this = this;
|
|
||||||
this.set('acted', true);
|
this.set('acted', true);
|
||||||
this.set('count', this.get('count') + 1);
|
this.set('count', this.get('count') + 1);
|
||||||
this.set('can_act', false);
|
this.set('can_act', false);
|
||||||
|
@ -53,26 +51,19 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create our post action
|
// Create our post action
|
||||||
promise = new RSVP.Promise();
|
var actionSummary = this;
|
||||||
$.ajax({
|
return $.ajax({
|
||||||
url: "/post_actions",
|
url: "/post_actions",
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
id: this.get('post.id'),
|
id: this.get('post.id'),
|
||||||
post_action_type_id: this.get('id'),
|
post_action_type_id: this.get('id'),
|
||||||
message: (opts ? opts.message : void 0) || ""
|
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
|
// Undo this action
|
||||||
|
|
|
@ -31,18 +31,14 @@ Discourse.CategoryList.reopenClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
list: function(filter) {
|
list: function(filter) {
|
||||||
var promise,
|
var route = this;
|
||||||
_this = this;
|
return $.getJSON("/" + filter + ".json").then(function(result) {
|
||||||
promise = new RSVP.Promise();
|
var categoryList = Discourse.TopicList.create();
|
||||||
$.getJSON("/" + filter + ".json").then(function(result) {
|
|
||||||
var categoryList;
|
|
||||||
categoryList = Discourse.TopicList.create();
|
|
||||||
categoryList.set('can_create_category', result.category_list.can_create_category);
|
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);
|
categoryList.set('loaded', true);
|
||||||
return promise.resolve(categoryList);
|
return categoryList;
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -85,9 +85,9 @@ Discourse.Composer = Discourse.Model.extend({
|
||||||
if (post) {
|
if (post) {
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
var composer = this;
|
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')));
|
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") || "");
|
this.set('reply', opts.reply || this.get("reply") || "");
|
||||||
if (opts.postId) {
|
if (opts.postId) {
|
||||||
this.set('loading', true);
|
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);
|
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) {
|
if (opts.action === EDIT && opts.post) {
|
||||||
this.set('title', this.get('topic.title'));
|
this.set('title', this.get('topic.title'));
|
||||||
this.set('loading', true);
|
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('reply', result.get('raw'));
|
||||||
composer.set('originalText', composer.get('reply'));
|
composer.set('originalText', composer.get('reply'));
|
||||||
composer.set('loading', false);
|
composer.set('loading', false);
|
||||||
|
@ -285,7 +286,6 @@ Discourse.Composer = Discourse.Model.extend({
|
||||||
|
|
||||||
// When you edit a post
|
// When you edit a post
|
||||||
editPost: function(opts) {
|
editPost: function(opts) {
|
||||||
var promise = new RSVP.Promise();
|
|
||||||
var post = this.get('post');
|
var post = this.get('post');
|
||||||
var oldCooked = post.get('cooked');
|
var oldCooked = post.get('cooked');
|
||||||
var composer = this;
|
var composer = this;
|
||||||
|
@ -304,40 +304,37 @@ Discourse.Composer = Discourse.Model.extend({
|
||||||
post.set('cooked', $('#wmd-preview').html());
|
post.set('cooked', $('#wmd-preview').html());
|
||||||
this.set('composeState', CLOSED);
|
this.set('composeState', CLOSED);
|
||||||
|
|
||||||
|
return Ember.Deferred.promise(function(promise) {
|
||||||
|
post.save(function(savedPost) {
|
||||||
|
var posts = composer.get('topic.posts');
|
||||||
|
|
||||||
post.save(function(savedPost) {
|
// perhaps our post came from elsewhere eg. draft
|
||||||
|
var idx = -1;
|
||||||
var idx, postNumber;
|
var postNumber = post.get('post_number');
|
||||||
var posts = composer.get('topic.posts');
|
posts.each(function(p, i) {
|
||||||
|
if (p.get('post_number') === postNumber) {
|
||||||
// perhaps our post came from elsewhere eg. draft
|
idx = i;
|
||||||
idx = -1;
|
}
|
||||||
postNumber = post.get('post_number');
|
});
|
||||||
posts.each(function(p, i) {
|
if (idx > -1) {
|
||||||
if (p.get('post_number') === postNumber) {
|
savedPost.set('topic', composer.get('topic'));
|
||||||
idx = i;
|
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
|
// Create a new Post
|
||||||
createPost: function(opts) {
|
createPost: function(opts) {
|
||||||
var promise = new RSVP.Promise(),
|
var post = this.get('post'),
|
||||||
post = this.get('post'),
|
|
||||||
topic = this.get('topic'),
|
topic = this.get('topic'),
|
||||||
currentUser = Discourse.get('currentUser'),
|
currentUser = Discourse.get('currentUser'),
|
||||||
addedToStream = false;
|
addedToStream = false;
|
||||||
|
@ -401,38 +398,37 @@ Discourse.Composer = Discourse.Model.extend({
|
||||||
|
|
||||||
// Save callback
|
// Save callback
|
||||||
var composer = this;
|
var composer = this;
|
||||||
createdPost.save(function(result) {
|
return Ember.Deferred.promise(function(promise) {
|
||||||
var addedPost = false,
|
createdPost.save(function(result) {
|
||||||
saving = true;
|
var addedPost = false,
|
||||||
createdPost.updateFromSave(result);
|
saving = true;
|
||||||
if (topic) {
|
createdPost.updateFromSave(result);
|
||||||
// It's no longer a new post
|
if (topic) {
|
||||||
createdPost.set('newPost', false);
|
// It's no longer a new post
|
||||||
topic.set('draft_sequence', result.draft_sequence);
|
createdPost.set('newPost', false);
|
||||||
} else {
|
topic.set('draft_sequence', result.draft_sequence);
|
||||||
// We created a new topic, let's show it.
|
} else {
|
||||||
composer.set('composeState', CLOSED);
|
// We created a new topic, let's show it.
|
||||||
saving = false;
|
composer.set('composeState', CLOSED);
|
||||||
}
|
saving = false;
|
||||||
composer.set('reply', '');
|
}
|
||||||
composer.set('createdPost', createdPost);
|
composer.set('reply', '');
|
||||||
if (addedToStream) {
|
composer.set('createdPost', createdPost);
|
||||||
composer.set('composeState', CLOSED);
|
if (addedToStream) {
|
||||||
} else if (saving) {
|
composer.set('composeState', CLOSED);
|
||||||
composer.set('composeState', SAVING);
|
} else if (saving) {
|
||||||
}
|
composer.set('composeState', SAVING);
|
||||||
return promise.resolve({ post: result });
|
}
|
||||||
}, function(error) {
|
return promise.resolve({ post: result });
|
||||||
// If an error occurs
|
}, function(error) {
|
||||||
var errors;
|
// If an error occurs
|
||||||
if (topic) {
|
if (topic) {
|
||||||
topic.posts.removeObject(createdPost);
|
topic.posts.removeObject(createdPost);
|
||||||
}
|
}
|
||||||
errors = $.parseJSON(error.responseText).errors;
|
promise.reject($.parseJSON(error.responseText).errors[0]);
|
||||||
promise.reject(errors[0]);
|
composer.set('composeState', OPEN);
|
||||||
composer.set('composeState', OPEN);
|
});
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveDraft: function() {
|
saveDraft: function() {
|
||||||
|
|
|
@ -22,20 +22,11 @@ Discourse.Draft.reopenClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
get: function(key) {
|
get: function(key) {
|
||||||
var promise,
|
return $.ajax({
|
||||||
_this = this;
|
|
||||||
promise = new RSVP.Promise();
|
|
||||||
$.ajax({
|
|
||||||
url: '/draft',
|
url: '/draft',
|
||||||
data: {
|
data: { draft_key: key },
|
||||||
draft_key: key
|
dataType: 'json'
|
||||||
},
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
return promise.resolve(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocal: function(key, current) {
|
getLocal: function(key, current) {
|
||||||
|
@ -44,35 +35,16 @@ Discourse.Draft.reopenClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
save: function(key, sequence, data) {
|
save: function(key, sequence, data) {
|
||||||
var promise;
|
|
||||||
promise = new RSVP.Promise();
|
|
||||||
data = typeof data === "string" ? data : JSON.stringify(data);
|
data = typeof data === "string" ? data : JSON.stringify(data);
|
||||||
$.ajax({
|
return $.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: "/draft",
|
url: "/draft",
|
||||||
data: {
|
data: {
|
||||||
draft_key: key,
|
draft_key: key,
|
||||||
data: data,
|
data: data,
|
||||||
sequence: sequence
|
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({
|
Discourse.InviteList.reopenClass({
|
||||||
|
|
||||||
findInvitedBy: function(user) {
|
findInvitedBy: function(user) {
|
||||||
var promise;
|
return $.ajax({ url: "/users/" + (user.get('username_lower')) + "/invited.json" }).then(function (result) {
|
||||||
promise = new RSVP.Promise();
|
var invitedList = result.invited_list;
|
||||||
$.ajax({
|
if (invitedList.pending) {
|
||||||
url: "/users/" + (user.get('username_lower')) + "/invited.json",
|
invitedList.pending = invitedList.pending.map(function(i) {
|
||||||
success: function(result) {
|
return Discourse.Invite.create(i);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
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
|
// Load replies to this post
|
||||||
loadReplies: function() {
|
loadReplies: function() {
|
||||||
var promise,
|
|
||||||
_this = this;
|
|
||||||
promise = new RSVP.Promise();
|
|
||||||
this.set('loadingReplies', true);
|
this.set('loadingReplies', true);
|
||||||
this.set('replies', []);
|
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) {
|
loaded.each(function(reply) {
|
||||||
var post;
|
var post = Discourse.Post.create(reply);
|
||||||
post = Discourse.Post.create(reply);
|
post.set('topic', parent.get('topic'));
|
||||||
post.set('topic', _this.get('topic'));
|
replies.pushObject(post);
|
||||||
return _this.get('replies').pushObject(post);
|
|
||||||
});
|
});
|
||||||
_this.set('loadingReplies', false);
|
parent.set('loadingReplies', false);
|
||||||
return promise.resolve();
|
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loadVersions: function(callback) {
|
loadVersions: function(callback) {
|
||||||
|
@ -293,43 +290,33 @@ Discourse.Post.reopenClass({
|
||||||
return $.ajax("/posts/destroy_many", {
|
return $.ajax("/posts/destroy_many", {
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
data: {
|
data: {
|
||||||
post_ids: posts.map(function(p) {
|
post_ids: posts.map(function(p) { return p.get('id'); })
|
||||||
return p.get('id');
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
loadVersion: function(postId, version, callback) {
|
loadVersion: function(postId, version, callback) {
|
||||||
var _this = this;
|
return $.ajax({url: "/posts/" + postId + ".json?version=" + version}).then(function(result) {
|
||||||
return $.getJSON("/posts/" + postId + ".json?version=" + version, function(result) {
|
return Discourse.Post.create(result);
|
||||||
return callback(Discourse.Post.create(result));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
loadByPostNumber: function(topicId, postId, callback) {
|
loadByPostNumber: function(topicId, postId) {
|
||||||
var _this = this;
|
return $.ajax({url: "/posts/by_number/" + topicId + "/" + postId + ".json"}).then(function (result) {
|
||||||
return $.getJSON("/posts/by_number/" + topicId + "/" + postId + ".json", function(result) {
|
return Discourse.Post.create(result);
|
||||||
return callback(Discourse.Post.create(result));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
loadQuote: function(postId) {
|
loadQuote: function(postId) {
|
||||||
var promise,
|
return $.ajax({url: "/posts/" + postId + ".json"}).then(function(result) {
|
||||||
_this = this;
|
var post = Discourse.Post.create(result);
|
||||||
promise = new RSVP.Promise();
|
return Discourse.BBCode.buildQuoteBBCode(post, post.get('raw'));
|
||||||
$.getJSON("/posts/" + postId + ".json", function(result) {
|
|
||||||
var post;
|
|
||||||
post = Discourse.Post.create(result);
|
|
||||||
return promise.resolve(Discourse.BBCode.buildQuoteBBCode(post, post.get('raw')));
|
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
load: function(postId, callback) {
|
load: function(postId) {
|
||||||
var _this = this;
|
return $.ajax({url: "/posts/" + postId + ".json"}).then(function (result) {
|
||||||
return $.getJSON("/posts/" + postId + ".json", function(result) {
|
return Discourse.Post.create(result);
|
||||||
return callback(Discourse.Post.create(result));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -372,6 +372,20 @@ Discourse.Topic.reopenClass({
|
||||||
MUTE: 0
|
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
|
// Load a topic, but accepts a set of filters
|
||||||
// options:
|
// options:
|
||||||
// onLoad - the callback after the topic is loaded
|
// 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
|
// Check the preload store. If not, load it via JSON
|
||||||
promise = new RSVP.Promise();
|
return PreloadStore.get("topic_" + topicId, function() {
|
||||||
PreloadStore.get("topic_" + topicId, function() {
|
|
||||||
return $.getJSON(url + ".json", data);
|
return $.getJSON(url + ".json", data);
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
var first;
|
var first = result.posts.first();
|
||||||
first = result.posts.first();
|
|
||||||
if (first && opts && opts.bestOf) {
|
if (first && opts && opts.bestOf) {
|
||||||
first.bestOfFirst = true;
|
first.bestOfFirst = true;
|
||||||
}
|
}
|
||||||
return promise.resolve(result);
|
return result;
|
||||||
}, function(result) {
|
|
||||||
return promise.reject(result);
|
|
||||||
});
|
});
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Create a topic from posts
|
// Create a topic from posts
|
||||||
|
|
|
@ -10,42 +10,38 @@
|
||||||
Discourse.TopicList = Discourse.Model.extend({
|
Discourse.TopicList = Discourse.Model.extend({
|
||||||
|
|
||||||
loadMoreTopics: function() {
|
loadMoreTopics: function() {
|
||||||
var moreUrl, promise,
|
var moreUrl, _this = this;
|
||||||
_this = this;
|
|
||||||
promise = new RSVP.Promise();
|
|
||||||
if (moreUrl = this.get('more_topics_url')) {
|
if (moreUrl = this.get('more_topics_url')) {
|
||||||
Discourse.URL.replaceState("/" + (this.get('filter')) + "/more");
|
Discourse.URL.replaceState("/" + (this.get('filter')) + "/more");
|
||||||
$.ajax(moreUrl, {
|
return $.ajax({url: moreUrl}).then(function (result) {
|
||||||
success: function(result) {
|
var newTopics, topicIds, topics, topicsAdded = 0;
|
||||||
var newTopics, topicIds, topics, topicsAdded = 0;
|
if (result) {
|
||||||
if (result) {
|
// the new topics loaded from the server
|
||||||
// the new topics loaded from the server
|
newTopics = Discourse.TopicList.topicsFrom(result);
|
||||||
newTopics = Discourse.TopicList.topicsFrom(result);
|
// the current topics
|
||||||
// the current topics
|
topics = _this.get('topics');
|
||||||
topics = _this.get('topics');
|
// keeps track of the ids of the current topics
|
||||||
// keeps track of the ids of the current topics
|
topicIds = [];
|
||||||
topicIds = [];
|
topics.each(function(t) {
|
||||||
topics.each(function(t) {
|
topicIds[t.get('id')] = true;
|
||||||
topicIds[t.get('id')] = true;
|
});
|
||||||
});
|
// add new topics to the list of current topics if not already present
|
||||||
// add new topics to the list of current topics if not already present
|
newTopics.each(function(t) {
|
||||||
newTopics.each(function(t) {
|
if (!topicIds[t.get('id')]) {
|
||||||
if (!topicIds[t.get('id')]) {
|
// highlight the first of the new topics so we can get a visual feedback
|
||||||
// highlight the first of the new topics so we can get a visual feedback
|
t.set('highlight', topicsAdded++ === 0);
|
||||||
t.set('highlight', topicsAdded++ === 0);
|
return topics.pushObject(t);
|
||||||
return topics.pushObject(t);
|
}
|
||||||
}
|
});
|
||||||
});
|
_this.set('more_topics_url', result.topic_list.more_topics_url);
|
||||||
_this.set('more_topics_url', result.topic_list.more_topics_url);
|
Discourse.set('transient.topicsList', _this);
|
||||||
Discourse.set('transient.topicsList', _this);
|
|
||||||
}
|
|
||||||
return promise.resolve(result.topic_list.more_topics_url ? true : false);
|
|
||||||
}
|
}
|
||||||
|
return result.topic_list.more_topics_url;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
promise.resolve(false);
|
return null;
|
||||||
}
|
}
|
||||||
return promise;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
insert: function(json) {
|
insert: function(json) {
|
||||||
|
@ -90,7 +86,7 @@ Discourse.TopicList.reopenClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
list: function(menuItem) {
|
list: function(menuItem) {
|
||||||
var filter, found, list, promise, topic_list, url;
|
var filter, list, promise, topic_list, url;
|
||||||
filter = menuItem.name;
|
filter = menuItem.name;
|
||||||
topic_list = Discourse.TopicList.create();
|
topic_list = Discourse.TopicList.create();
|
||||||
topic_list.set('inserted', Em.A());
|
topic_list.set('inserted', Em.A());
|
||||||
|
@ -101,19 +97,16 @@ Discourse.TopicList.reopenClass({
|
||||||
}
|
}
|
||||||
if (list = Discourse.get('transient.topicsList')) {
|
if (list = Discourse.get('transient.topicsList')) {
|
||||||
if ((list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
|
if ((list.get('filter') === filter) && window.location.pathname.indexOf('more') > 0) {
|
||||||
promise = new RSVP.Promise();
|
|
||||||
list.set('loaded', true);
|
list.set('loaded', true);
|
||||||
promise.resolve(list);
|
return Ember.Deferred.promise(function(promise) {
|
||||||
return promise;
|
promise.resolve(list);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Discourse.set('transient.topicsList', null);
|
Discourse.set('transient.topicsList', null);
|
||||||
Discourse.set('transient.topicListScrollPos', null);
|
Discourse.set('transient.topicListScrollPos', null);
|
||||||
promise = new RSVP.Promise();
|
|
||||||
found = PreloadStore.contains('topic_list');
|
return PreloadStore.get("topic_list", function() { return $.getJSON(url) }).then(function(result) {
|
||||||
PreloadStore.get("topic_list", function() {
|
|
||||||
return $.getJSON(url);
|
|
||||||
}).then(function(result) {
|
|
||||||
topic_list.set('topics', Discourse.TopicList.topicsFrom(result));
|
topic_list.set('topics', Discourse.TopicList.topicsFrom(result));
|
||||||
topic_list.set('can_create_topic', result.topic_list.can_create_topic);
|
topic_list.set('can_create_topic', result.topic_list.can_create_topic);
|
||||||
topic_list.set('more_topics_url', result.topic_list.more_topics_url);
|
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('category', Discourse.Category.create(result.topic_list.filtered_category));
|
||||||
}
|
}
|
||||||
topic_list.set('loaded', true);
|
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'));
|
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
|
||||||
}).property('username'),
|
}).property('username'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Small version of this user's avatar.
|
Small version of this user's avatar.
|
||||||
|
|
||||||
@property avatarSmall
|
@property avatarSmall
|
||||||
|
@ -68,7 +68,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
|
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
|
||||||
}).property('trust_level'),
|
}).property('trust_level'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Changes this user's username.
|
Changes this user's username.
|
||||||
|
|
||||||
@method changeUsername
|
@method changeUsername
|
||||||
|
@ -76,7 +76,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
@returns Result of ajax call
|
@returns Result of ajax call
|
||||||
**/
|
**/
|
||||||
changeUsername: function(newUsername) {
|
changeUsername: function(newUsername) {
|
||||||
return jQuery.ajax({
|
return $.ajax({
|
||||||
url: "/users/" + (this.get('username_lower')) + "/preferences/username",
|
url: "/users/" + (this.get('username_lower')) + "/preferences/username",
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
|
@ -93,7 +93,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
@returns Result of ajax call
|
@returns Result of ajax call
|
||||||
**/
|
**/
|
||||||
changeEmail: function(email) {
|
changeEmail: function(email) {
|
||||||
return jQuery.ajax({
|
return $.ajax({
|
||||||
url: "/users/" + (this.get('username_lower')) + "/preferences/email",
|
url: "/users/" + (this.get('username_lower')) + "/preferences/email",
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
data: {
|
data: {
|
||||||
|
@ -121,7 +121,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
**/
|
**/
|
||||||
save: function(finished) {
|
save: function(finished) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
jQuery.ajax("/users/" + this.get('username').toLowerCase(), {
|
$.ajax("/users/" + this.get('username').toLowerCase(), {
|
||||||
data: this.getProperties('auto_track_topics_after_msecs',
|
data: this.getProperties('auto_track_topics_after_msecs',
|
||||||
'bio_raw',
|
'bio_raw',
|
||||||
'website',
|
'website',
|
||||||
|
@ -153,7 +153,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
changePassword: function(callback) {
|
changePassword: function(callback) {
|
||||||
var good;
|
var good;
|
||||||
good = false;
|
good = false;
|
||||||
jQuery.ajax({
|
$.ajax({
|
||||||
url: '/session/forgot_password',
|
url: '/session/forgot_password',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: {
|
data: {
|
||||||
|
@ -199,7 +199,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
var stream,
|
var stream,
|
||||||
_this = this;
|
_this = this;
|
||||||
stream = this.get('stream');
|
stream = this.get('stream');
|
||||||
jQuery.ajax({
|
$.ajax({
|
||||||
url: "/user_actions/" + id + ".json",
|
url: "/user_actions/" + id + ".json",
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
cache: 'false',
|
cache: 'false',
|
||||||
|
@ -241,7 +241,7 @@ Discourse.User = Discourse.Model.extend({
|
||||||
url += "&filter=" + (this.get('streamFilter'));
|
url += "&filter=" + (this.get('streamFilter'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return jQuery.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
cache: 'false',
|
cache: 'false',
|
||||||
|
@ -362,7 +362,7 @@ Discourse.User.reopenClass({
|
||||||
@param {String} email An email address to check
|
@param {String} email An email address to check
|
||||||
**/
|
**/
|
||||||
checkUsername: function(username, email) {
|
checkUsername: function(username, email) {
|
||||||
return jQuery.ajax({
|
return $.ajax({
|
||||||
url: '/users/check_username',
|
url: '/users/check_username',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
data: {
|
data: {
|
||||||
|
@ -465,7 +465,7 @@ Discourse.User.reopenClass({
|
||||||
@returns Result of ajax call
|
@returns Result of ajax call
|
||||||
**/
|
**/
|
||||||
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
|
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
|
||||||
return jQuery.ajax({
|
return $.ajax({
|
||||||
url: '/users',
|
url: '/users',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -2,10 +2,20 @@
|
||||||
|
|
||||||
<div class='contents'>
|
<div class='contents'>
|
||||||
|
|
||||||
<div id='new-user-education' style='display: none'>
|
<div id='new-user-education' class='composer-popup' style='display: none'>
|
||||||
<a href='#' {{action closeEducation target="view"}} class='close'>{{i18n ok}}</a>
|
<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>
|
||||||
|
|
||||||
<div class='control'>
|
<div class='control'>
|
||||||
|
|
|
@ -19,36 +19,32 @@ Discourse.ComposerView = Discourse.View.extend({
|
||||||
'content.creatingTopic:topic',
|
'content.creatingTopic:topic',
|
||||||
'content.showPreview',
|
'content.showPreview',
|
||||||
'content.hidePreview'],
|
'content.hidePreview'],
|
||||||
educationClosed: null,
|
|
||||||
|
|
||||||
composeState: (function() {
|
composeState: function() {
|
||||||
var state;
|
var state = this.get('content.composeState');
|
||||||
state = this.get('content.composeState');
|
if (state) return state;
|
||||||
if (!state) {
|
return Discourse.Composer.CLOSED;
|
||||||
state = Discourse.Composer.CLOSED;
|
}.property('content.composeState'),
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}).property('content.composeState'),
|
|
||||||
|
|
||||||
draftStatus: (function() {
|
draftStatus: function() {
|
||||||
return this.$('.saving-draft').text(this.get('content.draftStatus') || "");
|
this.$('.saving-draft').text(this.get('content.draftStatus') || "");
|
||||||
}).observes('content.draftStatus'),
|
}.observes('content.draftStatus'),
|
||||||
|
|
||||||
// Disable fields when we're loading
|
// Disable fields when we're loading
|
||||||
loadingChanged: (function() {
|
loadingChanged: function() {
|
||||||
if (this.get('loading')) {
|
if (this.get('loading')) {
|
||||||
$('#wmd-input, #reply-title').prop('disabled', 'disabled');
|
$('#wmd-input, #reply-title').prop('disabled', 'disabled');
|
||||||
} else {
|
} else {
|
||||||
$('#wmd-input, #reply-title').prop('disabled', '');
|
$('#wmd-input, #reply-title').prop('disabled', '');
|
||||||
}
|
}
|
||||||
}).observes('loading'),
|
}.observes('loading'),
|
||||||
|
|
||||||
postMade: (function() {
|
postMade: function() {
|
||||||
if (this.present('controller.createdPost')) return 'created-post';
|
if (this.present('controller.createdPost')) return 'created-post';
|
||||||
return null;
|
return null;
|
||||||
}).property('content.createdPost'),
|
}.property('content.createdPost'),
|
||||||
|
|
||||||
observeReplyChanges: (function() {
|
observeReplyChanges: function() {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
if (this.get('content.hidePreview')) return;
|
if (this.get('content.hidePreview')) return;
|
||||||
Ember.run.next(null, function() {
|
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() {
|
newUserEducationVisibilityChanged: function() {
|
||||||
this.set('educationClosed', true);
|
var $panel = $('#new-user-education');
|
||||||
return false;
|
if (this.get('controller.newUserEducationVisible')) {
|
||||||
},
|
$panel.slideDown('fast');
|
||||||
|
|
||||||
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');
|
|
||||||
} else {
|
} else {
|
||||||
return $panel.slideUp('fast');
|
$panel.slideUp('fast')
|
||||||
}
|
}
|
||||||
}).observes('newUserEducationVisible'),
|
}.observes('controller.newUserEducationVisible'),
|
||||||
|
|
||||||
moveNewUserEducation: function(sizePx) {
|
similarVisibilityChanged: function() {
|
||||||
$('#new-user-education').css('bottom', sizePx);
|
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
|
// this still needs to wait on animations, need a clean way to do that
|
||||||
var _this = this;
|
|
||||||
return Em.run.next(null, function() {
|
return Em.run.next(null, function() {
|
||||||
var h, replyControl, sizePx;
|
var replyControl = $('#reply-control');
|
||||||
replyControl = $('#reply-control');
|
var h = replyControl.height() || 0;
|
||||||
h = replyControl.height() || 0;
|
var sizePx = "" + h + "px";
|
||||||
sizePx = "" + h + "px";
|
|
||||||
$('.topic-area').css('padding-bottom', sizePx);
|
$('.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) {
|
keyUp: function(e) {
|
||||||
var controller;
|
var controller = this.get('controller');
|
||||||
controller = this.get('controller');
|
|
||||||
controller.checkReplyLength();
|
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();
|
if (e.which === 27) controller.hitEsc();
|
||||||
},
|
},
|
||||||
|
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
var replyControl;
|
var replyControl = $('#reply-control');
|
||||||
replyControl = $('#reply-control');
|
replyControl.DivResizer({ resize: this.resize, onDrag: this.movePanels });
|
||||||
replyControl.DivResizer({
|
|
||||||
resize: this.resize,
|
|
||||||
onDrag: this.moveNewUserEducation
|
|
||||||
});
|
|
||||||
Discourse.TransitionHelper.after(replyControl, this.resize);
|
Discourse.TransitionHelper.after(replyControl, this.resize);
|
||||||
},
|
},
|
||||||
|
|
||||||
click: function() {
|
click: function() {
|
||||||
return this.get('controller').click();
|
this.get('controller').openIfDraft();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Called after the preview renders. Debounced for performance
|
// Called after the preview renders. Debounced for performance
|
||||||
|
@ -229,7 +206,7 @@ Discourse.ComposerView = Discourse.View.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onChangeItems: function(items) {
|
onChangeItems: function(items) {
|
||||||
items = jQuery.map(items, function(i) {
|
items = $.map(items, function(i) {
|
||||||
if (i.username) {
|
if (i.username) {
|
||||||
return i.username;
|
return i.username;
|
||||||
} else {
|
} else {
|
||||||
|
@ -243,9 +220,7 @@ Discourse.ComposerView = Discourse.View.extend({
|
||||||
transformComplete: transformTemplate,
|
transformComplete: transformTemplate,
|
||||||
|
|
||||||
reverseTransform: function(i) {
|
reverseTransform: function(i) {
|
||||||
return {
|
return { username: i };
|
||||||
username: i
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,21 +58,23 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
|
||||||
},
|
},
|
||||||
|
|
||||||
loadMore: function() {
|
loadMore: function() {
|
||||||
var _this = this;
|
if (this.get('loading')) return;
|
||||||
if (this.get('loading')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
return this.get('controller.content').loadMoreTopics().then(function(hasMoreResults) {
|
|
||||||
_this.set('loadedMore', true);
|
var listTopicsView = this;
|
||||||
_this.set('loading', false);
|
var promise = this.get('controller.content').loadMoreTopics();
|
||||||
Em.run.next(function() {
|
if (promise) {
|
||||||
return _this.saveScrollPos();
|
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) {
|
} else {
|
||||||
return _this.get('eyeline').flushRest();
|
this.set('loading', false);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Remember where we were scrolled to
|
// Remember where we were scrolled to
|
||||||
|
|
|
@ -12,28 +12,27 @@ Discourse.HistoryView = Discourse.View.extend({
|
||||||
modalClass: 'history-modal',
|
modalClass: 'history-modal',
|
||||||
|
|
||||||
loadSide: function(side) {
|
loadSide: function(side) {
|
||||||
var orig, version,
|
|
||||||
_this = this;
|
|
||||||
if (this.get("version" + side)) {
|
if (this.get("version" + side)) {
|
||||||
orig = this.get('originalPost');
|
var orig = this.get('originalPost');
|
||||||
version = this.get("version" + side + ".number");
|
var version = this.get("version" + side + ".number");
|
||||||
if (version === orig.get('version')) {
|
if (version === orig.get('version')) {
|
||||||
return this.set("post" + side, orig);
|
this.set("post" + side, orig);
|
||||||
} else {
|
} else {
|
||||||
return Discourse.Post.loadVersion(orig.get('id'), version, function(post) {
|
var historyView = this;
|
||||||
return _this.set("post" + side, post);
|
Discourse.Post.loadVersion(orig.get('id'), version).then(function(post) {
|
||||||
|
historyView.set("post" + side, post);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
changedLeftVersion: (function() {
|
changedLeftVersion: function() {
|
||||||
return this.loadSide("Left");
|
this.loadSide("Left");
|
||||||
}).observes('versionLeft'),
|
}.observes('versionLeft'),
|
||||||
|
|
||||||
changedRightVersion: (function() {
|
changedRightVersion: function() {
|
||||||
return this.loadSide("Right");
|
this.loadSide("Right");
|
||||||
}).observes('versionRight'),
|
}.observes('versionRight'),
|
||||||
|
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
|
@ -109,9 +109,8 @@ Discourse.PostView = Discourse.View.extend({
|
||||||
|
|
||||||
// Toggle visibility of parent post
|
// Toggle visibility of parent post
|
||||||
toggleParent: function(e) {
|
toggleParent: function(e) {
|
||||||
var $parent, post,
|
var postView = this;
|
||||||
_this = this;
|
var $parent = this.$('.parent-post');
|
||||||
$parent = this.$('.parent-post');
|
|
||||||
if (this.get('parentPost')) {
|
if (this.get('parentPost')) {
|
||||||
$('nav', $parent).removeClass('toggled');
|
$('nav', $parent).removeClass('toggled');
|
||||||
// Don't animate on touch
|
// Don't animate on touch
|
||||||
|
@ -119,19 +118,18 @@ Discourse.PostView = Discourse.View.extend({
|
||||||
$parent.hide();
|
$parent.hide();
|
||||||
this.set('parentPost', null);
|
this.set('parentPost', null);
|
||||||
} else {
|
} else {
|
||||||
$parent.slideUp(function() {
|
$parent.slideUp(function() { postView.set('parentPost', null); });
|
||||||
return _this.set('parentPost', null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
post = this.get('post');
|
var post = this.get('post');
|
||||||
this.set('loadingParent', true);
|
this.set('loadingParent', true);
|
||||||
$('nav', $parent).addClass('toggled');
|
$('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
|
// Give the post a reference back to the topic
|
||||||
result.topic = _this.get('post.topic');
|
result.topic = postView.get('post.topic');
|
||||||
return _this.set('parentPost', result);
|
postView.set('parentPost', result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -235,7 +235,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, {
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
this.set('loadingAbove', true);
|
this.set('loadingAbove', true);
|
||||||
opts = jQuery.extend({
|
opts = $.extend({
|
||||||
postsBefore: post.get('post_number')
|
postsBefore: post.get('post_number')
|
||||||
}, this.get('controller.postFilters'));
|
}, 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;
|
if (this.topic.posts.last().post_number !== post.post_number) return;
|
||||||
this.set('loadingBelow', true);
|
this.set('loadingBelow', true);
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
opts = jQuery.extend({
|
opts = $.extend({ postsAfter: post.get('post_number') }, this.get('controller.postFilters'));
|
||||||
postsAfter: post.get('post_number')
|
|
||||||
}, this.get('controller.postFilters'));
|
|
||||||
return Discourse.Topic.find(this.get('topic.id'), opts).then(function(result) {
|
return Discourse.Topic.find(this.get('topic.id'), opts).then(function(result) {
|
||||||
var suggested;
|
var suggested;
|
||||||
if (result.at_bottom || result.posts.length === 0) {
|
if (result.at_bottom || result.posts.length === 0) {
|
||||||
|
|
|
@ -28,34 +28,34 @@ PreloadStore = {
|
||||||
@method get
|
@method get
|
||||||
@param {String} key the key to look up the object with
|
@param {String} key the key to look up the object with
|
||||||
@param {function} finder a function to find 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) {
|
get: function(key, finder) {
|
||||||
var promise = new RSVP.Promise();
|
var preloadStore = this;
|
||||||
|
return Ember.Deferred.promise(function(promise) {
|
||||||
if (this.data[key]) {
|
if (preloadStore.data[key]) {
|
||||||
promise.resolve(this.data[key]);
|
promise.resolve(preloadStore.data[key]);
|
||||||
delete this.data[key];
|
delete preloadStore.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);
|
|
||||||
}
|
|
||||||
} else {
|
} 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";
|
@import "foundation/mixins";
|
||||||
|
|
||||||
|
|
||||||
#new-user-education {
|
.composer-popup {
|
||||||
|
|
||||||
@include box-shadow(3px 3px 3px rgba($black, 0.14));
|
@include box-shadow(3px 3px 3px rgba($black, 0.14));
|
||||||
|
|
||||||
|
@ -17,10 +17,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color: lighten($yellow, 40%);
|
background-color: lighten($yellow, 40%);
|
||||||
border: 1px solid $yellow;
|
border: 1px solid darken($yellow, 5%);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 600px;
|
width: 600px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
ul.topics {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
font-weight: normal;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#reply-control {
|
#reply-control {
|
||||||
|
|
|
@ -59,6 +59,17 @@ class TopicsController < ApplicationController
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
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
|
def status
|
||||||
requires_parameters(:status, :enabled)
|
requires_parameters(:status, :enabled)
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ class SiteSetting < ActiveRecord::Base
|
||||||
client_setting(:supress_reply_directly_below, true)
|
client_setting(:supress_reply_directly_below, true)
|
||||||
client_setting(:email_domains_blacklist, 'mailinator.com')
|
client_setting(:email_domains_blacklist, 'mailinator.com')
|
||||||
client_setting(:version_checks, true)
|
client_setting(:version_checks, true)
|
||||||
|
client_setting(:min_title_similar_length, 10)
|
||||||
|
client_setting(:min_body_similar_length, 15)
|
||||||
|
|
||||||
# settings only available server side
|
# settings only available server side
|
||||||
setting(:auto_track_topics_after, 300000)
|
setting(:auto_track_topics_after, 300000)
|
||||||
|
|
|
@ -213,8 +213,22 @@ class Topic < ActiveRecord::Base
|
||||||
id).to_a
|
id).to_a
|
||||||
end
|
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
|
Topic.transaction do
|
||||||
|
|
||||||
# Special case: if it's pinned, update that
|
# Special case: if it's pinned, update that
|
||||||
|
@ -592,7 +606,6 @@ class Topic < ActiveRecord::Base
|
||||||
|
|
||||||
def clear_pin_for(user)
|
def clear_pin_for(user)
|
||||||
return unless user.present?
|
return unless user.present?
|
||||||
|
|
||||||
TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
|
TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,58 +1,9 @@
|
||||||
require_dependency 'age_words'
|
require_dependency 'age_words'
|
||||||
|
|
||||||
|
# The most basic attributes of a topic that we need to create a link for it.
|
||||||
class BasicTopicSerializer < ApplicationSerializer
|
class BasicTopicSerializer < ApplicationSerializer
|
||||||
include ActionView::Helpers
|
include ActionView::Helpers
|
||||||
|
|
||||||
attributes :id,
|
attributes :id, :fancy_title, :slug
|
||||||
: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
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
class CategoryTopicSerializer < BasicTopicSerializer
|
class CategoryTopicSerializer < ListableTopicSerializer
|
||||||
|
|
||||||
attributes :slug,
|
|
||||||
:visible,
|
|
||||||
:closed,
|
|
||||||
:archived
|
|
||||||
|
|
||||||
|
attributes :visible, :closed, :archived
|
||||||
has_one :category
|
has_one :category
|
||||||
|
|
||||||
end
|
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
|
has_one :category, embed: :objects
|
||||||
|
|
||||||
def last_post_age
|
def last_post_age
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require_dependency 'pinned_check'
|
require_dependency 'pinned_check'
|
||||||
|
|
||||||
class TopicListItemSerializer < BasicTopicSerializer
|
class TopicListItemSerializer < ListableTopicSerializer
|
||||||
|
|
||||||
attributes :views,
|
attributes :views,
|
||||||
:like_count,
|
:like_count,
|
||||||
|
@ -11,8 +11,7 @@ class TopicListItemSerializer < BasicTopicSerializer
|
||||||
:last_post_age,
|
:last_post_age,
|
||||||
:starred,
|
:starred,
|
||||||
:has_best_of,
|
:has_best_of,
|
||||||
:archetype,
|
:archetype
|
||||||
:slug
|
|
||||||
|
|
||||||
has_one :category
|
has_one :category
|
||||||
has_many :posters, serializer: TopicPosterSerializer, embed: :objects
|
has_many :posters, serializer: TopicPosterSerializer, embed: :objects
|
||||||
|
|
|
@ -267,6 +267,7 @@ en:
|
||||||
saving_draft_tip: "saving"
|
saving_draft_tip: "saving"
|
||||||
saved_draft_tip: "saved"
|
saved_draft_tip: "saved"
|
||||||
saved_local_draft_tip: "saved locally"
|
saved_local_draft_tip: "saved locally"
|
||||||
|
similar_topics: "Similar Topics"
|
||||||
|
|
||||||
min_length:
|
min_length:
|
||||||
at_least: "enter at least {{n}} characters"
|
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"
|
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"
|
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:
|
notification_types:
|
||||||
mentioned: "%{display_username} mentioned you in %{link}"
|
mentioned: "%{display_username} mentioned you in %{link}"
|
||||||
liked: "%{display_username} liked your post in %{link}"
|
liked: "%{display_username} liked your post in %{link}"
|
||||||
|
|
|
@ -164,7 +164,8 @@ Discourse::Application.routes.draw do
|
||||||
delete 't/:id' => 'topics#destroy'
|
delete 't/:id' => 'topics#destroy'
|
||||||
put 't/:id' => 'topics#update'
|
put 't/:id' => 'topics#update'
|
||||||
post 't' => 'topics#create'
|
post 't' => 'topics#create'
|
||||||
post 'topics/timings' => 'topics#timings'
|
post 'topics/timings'
|
||||||
|
get 'topics/similar_to'
|
||||||
|
|
||||||
# Legacy route for old avatars
|
# Legacy route for old avatars
|
||||||
get 'threads/:topic_id/:post_number/avatar' => 'topics#avatar', :constraints => {:topic_id => /\d+/, :post_number => /\d+/}
|
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
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
context 'clear_pin' do
|
context 'clear_pin' do
|
||||||
it 'needs you to be logged in' do
|
it 'needs you to be logged in' do
|
||||||
lambda { xhr :put, :clear_pin, topic_id: 1 }.should raise_error(Discourse::NotLoggedIn)
|
lambda { xhr :put, :clear_pin, topic_id: 1 }.should raise_error(Discourse::NotLoggedIn)
|
||||||
|
|
|
@ -68,9 +68,7 @@ describe("PreloadStore", function() {
|
||||||
var done, finder, storeResult;
|
var done, finder, storeResult;
|
||||||
done = storeResult = null;
|
done = storeResult = null;
|
||||||
finder = function() {
|
finder = function() {
|
||||||
var promise = new RSVP.Promise();
|
return Ember.Deferred.promise(function(promise) { promise.resolve('evil'); });
|
||||||
promise.resolve('evil');
|
|
||||||
return promise;
|
|
||||||
};
|
};
|
||||||
PreloadStore.get('joker', finder).then(function(result) {
|
PreloadStore.get('joker', finder).then(function(result) {
|
||||||
done = true;
|
done = true;
|
||||||
|
@ -86,9 +84,7 @@ describe("PreloadStore", function() {
|
||||||
var done, finder, storeResult;
|
var done, finder, storeResult;
|
||||||
done = storeResult = null;
|
done = storeResult = null;
|
||||||
finder = function() {
|
finder = function() {
|
||||||
var promise = new RSVP.Promise();
|
return Ember.Deferred.promise(function(promise) { promise.reject('evil'); });
|
||||||
promise.reject('evil');
|
|
||||||
return promise;
|
|
||||||
};
|
};
|
||||||
PreloadStore.get('joker', finder).then(null, function(rejectedResult) {
|
PreloadStore.get('joker', finder).then(null, function(rejectedResult) {
|
||||||
done = true;
|
done = true;
|
||||||
|
|
|
@ -155,6 +155,24 @@ describe Topic do
|
||||||
end
|
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
|
context 'message bus' do
|
||||||
it 'calls the message bus observer after create' do
|
it 'calls the message bus observer after create' do
|
||||||
MessageBusObserver.any_instance.expects(:after_create_topic).with(instance_of(Topic))
|
MessageBusObserver.any_instance.expects(:after_create_topic).with(instance_of(Topic))
|
||||||
|
|
Loading…
Reference in a new issue