FIX: Unintended edits with composer

This commit is contained in:
Robin Ward
2013-07-04 13:35:15 -04:00
parent c157c61d0e
commit 28c168fc2b
6 changed files with 114 additions and 77 deletions

View File

@ -107,12 +107,6 @@ Discourse = Ember.Application.createWithMixins({
this.set('notifyCount', count); this.set('notifyCount', count);
}, },
openComposer: function(opts) {
// TODO, remove container link
var composer = Discourse.__container__.lookup('controller:composer');
if (composer) composer.open(opts);
},
/** /**
Establishes global DOM events and bindings via jQuery. Establishes global DOM events and bindings via jQuery.

View File

@ -202,6 +202,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
**/ **/
open: function(opts) { open: function(opts) {
if (!opts) opts = {}; if (!opts) opts = {};
var promise = opts.promise || Ember.Deferred.create(); var promise = opts.promise || Ember.Deferred.create();
opts.promise = promise; opts.promise = promise;
this.set('typedReply', false); this.set('typedReply', false);
@ -273,7 +274,8 @@ Discourse.ComposerController = Discourse.Controller.extend({
} }
} }
composer = composer || Discourse.Composer.open(opts); composer = composer || Discourse.Composer.create();
composer.open(opts);
this.set('model', composer); this.set('model', composer);
composer.set('composeState', Discourse.Composer.OPEN); composer.set('composeState', Discourse.Composer.OPEN);
promise.resolve(); promise.resolve();

View File

@ -7,19 +7,17 @@
@module Discourse @module Discourse
**/ **/
var CLOSED, CREATE_TOPIC, DRAFT, EDIT, OPEN, PRIVATE_MESSAGE, REPLY, REPLY_AS_NEW_TOPIC_KEY, SAVING; var CLOSED = 'closed',
SAVING = 'saving',
OPEN = 'open',
DRAFT = 'draft',
CLOSED = 'closed'; // The actions the composer can take
SAVING = 'saving'; CREATE_TOPIC = 'createTopic',
OPEN = 'open'; PRIVATE_MESSAGE = 'privateMessage',
DRAFT = 'draft'; REPLY = 'reply',
EDIT = 'edit',
// The actions the composer can take REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic";
CREATE_TOPIC = 'createTopic';
PRIVATE_MESSAGE = 'privateMessage';
REPLY = 'reply';
EDIT = 'edit';
REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic";
Discourse.Composer = Discourse.Model.extend({ Discourse.Composer = Discourse.Model.extend({
@ -70,23 +68,14 @@ Discourse.Composer = Discourse.Model.extend({
Discourse.KeyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') }); Discourse.KeyValueStore.set({ key: 'composer.showPreview', value: this.get('showPreview') });
}, },
// Import a quote from the post
importQuote: function() { importQuote: function() {
var post = this.get('post'); // If there is no current post, use the post id from the stream
var postId = this.get('post.id') || this.get('topic.postStream.firstPostId');
// If we don't have a post, check the topic for the first one if (postId) {
if (!post) {
var posts = this.get('topic.posts');
if (posts && posts.length > 0) {
post = posts[0];
}
}
if (post) {
this.set('loading', true); this.set('loading', true);
var composer = this; var composer = this;
Discourse.Post.load(post.get('id')).then(function(result) { return Discourse.Post.load(postId).then(function(post) {
composer.appendText(Discourse.BBCode.buildQuoteBBCode(post, result.get('raw'))); composer.appendText(Discourse.BBCode.buildQuoteBBCode(post, post.get('raw')));
composer.set('loading', false); composer.set('loading', false);
}); });
} }
@ -211,7 +200,6 @@ Discourse.Composer = Discourse.Model.extend({
*/ */
open: function(opts) { open: function(opts) {
if (!opts) opts = {}; if (!opts) opts = {};
this.set('loading', false); this.set('loading', false);
var replyBlank = Em.isEmpty(this.get("reply")); var replyBlank = Em.isEmpty(this.get("reply"));
@ -221,7 +209,7 @@ Discourse.Composer = Discourse.Model.extend({
(opts.action !== this.get('action') || ((opts.reply || opts.action === this.EDIT) && this.get('reply') !== this.get('originalText'))) && (opts.action !== this.get('action') || ((opts.reply || opts.action === this.EDIT) && this.get('reply') !== this.get('originalText'))) &&
!opts.tested) { !opts.tested) {
opts.tested = true; opts.tested = true;
this.cancel(function() { composer.open(opts); }); //composer.cancel(function() { composer.open(opts); });
return; return;
} }
@ -288,16 +276,18 @@ Discourse.Composer = Discourse.Model.extend({
// When you edit a post // When you edit a post
editPost: function(opts) { editPost: function(opts) {
var post = this.get('post'); var post = this.get('post'),
var oldCooked = post.get('cooked'); oldCooked = post.get('cooked'),
var composer = this; composer = this;
// Update the title if we've changed it // Update the title if we've changed it
if (this.get('title') && post.get('post_number') === 1) { if (this.get('title') && post.get('post_number') === 1) {
var topic = this.get('topic'); var topic = this.get('topic');
topic.set('title', this.get('title')); topic.setProperties({
topic.set('fancy_title', this.get('title')); title: this.get('title'),
topic.set('categoryName', this.get('categoryName')); fancy_title: this.get('title'),
categoryName: this.get('categoryName')
});
topic.save(); topic.save();
} }
@ -310,24 +300,9 @@ Discourse.Composer = Discourse.Model.extend({
return Ember.Deferred.promise(function(promise) { return Ember.Deferred.promise(function(promise) {
post.save(function(savedPost) { post.save(function(savedPost) {
var posts = composer.get('topic.posts'); composer.set('originalText', '');
composer.set('reply', '');
composer.set('originalText', composer.get('reply')); composer.set('post', null);
// perhaps our post came from elsewhere eg. draft
var idx = -1;
var postNumber = post.get('post_number');
_.each(posts,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) { }, function(error) {
var response = $.parseJSON(error.responseText); var response = $.parseJSON(error.responseText);
if (response && response.errors) { if (response && response.errors) {
@ -402,8 +377,12 @@ Discourse.Composer = Discourse.Model.extend({
saving = false; saving = false;
} }
composer.set('reply', ''); composer.setProperties({
composer.set('createdPost', createdPost); reply: '',
createdPost: createdPost,
title: ''
});
if (addedToStream) { if (addedToStream) {
composer.set('composeState', CLOSED); composer.set('composeState', CLOSED);
} else if (saving) { } else if (saving) {

View File

@ -24,18 +24,14 @@ Discourse.PostStream = Em.Object.extend({
@property hasPosts @property hasPosts
**/ **/
hasPosts: function() { hasPosts: Em.computed.gt('posts.length', 0),
return this.get('posts.length') > 0;
}.property('posts.length'),
/** /**
Do we have a stream list of post ids? Do we have a stream list of post ids?
@property hasStream @property hasStream
**/ **/
hasStream: function() { hasStream: Em.computed.gt('filteredPostsCount', 0),
return this.get('filteredPostsCount') > 0;
}.property('filteredPostsCount'),
/** /**
Can we append more posts to our current stream? Can we append more posts to our current stream?
@ -44,7 +40,6 @@ Discourse.PostStream = Em.Object.extend({
**/ **/
canAppendMore: Em.computed.and('notLoading', 'hasPosts', 'lastPostNotLoaded'), canAppendMore: Em.computed.and('notLoading', 'hasPosts', 'lastPostNotLoaded'),
/** /**
Can we prepend more posts to our current stream? Can we prepend more posts to our current stream?
@ -59,11 +54,20 @@ Discourse.PostStream = Em.Object.extend({
**/ **/
firstPostLoaded: function() { firstPostLoaded: function() {
if (!this.get('hasLoadedData')) { return false; } if (!this.get('hasLoadedData')) { return false; }
return !!this.get('posts').findProperty('id', this.get('stream')[0]); return !!this.get('posts').findProperty('id', this.get('firstPostId'));
}.property('hasLoadedData', 'posts.[]', 'stream.@each'), }.property('hasLoadedData', 'posts.[]', 'firstPostId'),
firstPostNotLoaded: Em.computed.not('firstPostLoaded'), firstPostNotLoaded: Em.computed.not('firstPostLoaded'),
/**
Returns the id of the first post in the set
@property firstPostId
**/
firstPostId: function() {
return this.get('stream')[0];
}.property('stream.@each'),
/** /**
Returns the id of the last post in the set Returns the id of the last post in the set

View File

@ -30,7 +30,9 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({
} }
} }
var topicController = this.controllerFor('topic'); var topicController = this.controllerFor('topic'),
composerController = this.controllerFor('composer');
postStream.refresh(params).then(function () { postStream.refresh(params).then(function () {
// The post we requested might not exist. Let's find the closest post // The post we requested might not exist. Let's find the closest post
@ -43,7 +45,7 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({
}); });
if (topic.present('draft')) { if (topic.present('draft')) {
Discourse.openComposer({ composerController.open({
draft: Discourse.Draft.getLocal(topic.get('draft_key'), topic.get('draft')), draft: Discourse.Draft.getLocal(topic.get('draft_key'), topic.get('draft')),
draftKey: topic.get('draft_key'), draftKey: topic.get('draft_key'),
draftSequence: topic.get('draft_sequence'), draftSequence: topic.get('draft_sequence'),

View File

@ -1,8 +1,6 @@
module("Discourse.Composer"); module("Discourse.Composer");
test('replyLength', function() { test('replyLength', function() {
var replyLength = function(val, expectedLength, text) { var replyLength = function(val, expectedLength, text) {
var composer = Discourse.Composer.create({ reply: val }); var composer = Discourse.Composer.create({ reply: val });
equal(composer.get('replyLength'), expectedLength); equal(composer.get('replyLength'), expectedLength);
@ -13,10 +11,8 @@ test('replyLength', function() {
replyLength("ba sic\n\nreply", 12, "count only significant whitespaces"); replyLength("ba sic\n\nreply", 12, "count only significant whitespaces");
replyLength("1[quote=]not counted[/quote]2[quote=]at all[/quote]3", 3, "removes quotes"); replyLength("1[quote=]not counted[/quote]2[quote=]at all[/quote]3", 3, "removes quotes");
replyLength("1[quote=]not[quote=]counted[/quote]yay[/quote]2", 2, "handles nested quotes correctly"); replyLength("1[quote=]not[quote=]counted[/quote]yay[/quote]2", 2, "handles nested quotes correctly");
}); });
test('missingReplyCharacters', function() { test('missingReplyCharacters', function() {
var missingReplyCharacters = function(val, isPM, expected, message) { var missingReplyCharacters = function(val, isPM, expected, message) {
var composer = Discourse.Composer.create({ reply: val, creatingPrivateMessage: isPM }); var composer = Discourse.Composer.create({ reply: val, creatingPrivateMessage: isPM });
@ -35,4 +31,64 @@ test('missingTitleCharacters', function() {
missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title'); missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title');
missingTitleCharacters('z', true, Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title'); missingTitleCharacters('z', true, Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title');
}); });
test('wouldLoseChanges', function() {
var composer = Discourse.Composer.create();
ok(!composer.get('wouldLoseChanges'), "by default it's false");
composer.setProperties({
originalText: "hello",
reply: "hello"
});
ok(!composer.get('wouldLoseChanges'), "it's false when the originalText is the same as the reply");
composer.set('reply', 'hello world');
ok(composer.get('wouldLoseChanges'), "it's true when the reply changes");
});
test('importQuote with no data', function() {
this.stub(Discourse.Post, 'load');
var composer = Discourse.Composer.create();
composer.importQuote();
blank(composer.get('reply'), 'importing with no topic adds nothing');
ok(!Discourse.Post.load.calledOnce, "load is not called");
composer = Discourse.Composer.create({topic: Discourse.Topic.create()});
composer.importQuote();
blank(composer.get('reply'), 'importing with no posts in a topic adds nothing');
ok(!Discourse.Post.load.calledOnce, "load is not called");
});
asyncTest('importQuote with a post', function() {
expect(1);
this.stub(Discourse.Post, 'load').withArgs(123).returns(Em.Deferred.promise(function (p) {
p.resolve(Discourse.Post.create({raw: "let's quote"}));
}));
var composer = Discourse.Composer.create({post: Discourse.Post.create({id: 123})});
composer.importQuote().then(function () {
start();
ok(composer.get('reply').indexOf("let's quote") > -1, "it quoted the post");
});
});
asyncTest('importQuote with no post', function() {
expect(1);
this.stub(Discourse.Post, 'load').withArgs(4).returns(Em.Deferred.promise(function (p) {
p.resolve(Discourse.Post.create({raw: 'quote me'}));
}));
var composer = Discourse.Composer.create({topic: Discourse.Topic.create()});
composer.set('topic.postStream.stream', [4, 5]);
composer.importQuote().then(function () {
start();
ok(composer.get('reply').indexOf('quote me') > -1, "it contains the word quote me");
});
});