diff --git a/core/client/views/editor.js b/core/client/views/editor.js index 3f944e91ba..d7872b21f7 100644 --- a/core/client/views/editor.js +++ b/core/client/views/editor.js @@ -284,7 +284,8 @@ }, events: { - 'click .markdown-help': 'showHelp' + 'click .markdown-help': 'showHelp', + 'blur #entry-title': 'trimTitle' }, syncScroll: _.debounce(function (e) { @@ -320,6 +321,16 @@ })); }, + trimTitle: function () { + var $title = $('#entry-title'), + rawTitle = $title.val(), + trimmedTitle = $.trim(rawTitle); + + if (rawTitle !== trimmedTitle) { + $title.val(trimmedTitle); + } + }, + // This updates the editor preview panel. // Currently gets called on every key press. // Also trigger word count update diff --git a/core/server/models/post.js b/core/server/models/post.js index a8aec027cb..c4f61876aa 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -50,6 +50,8 @@ Post = GhostBookshelf.Model.extend({ this.set('content', converter.makeHtml(this.get('content_raw'))); + this.set('title', this.get('title').trim()); + if (this.hasChanged('status') && this.get('status') === 'published') { this.set('published_at', new Date()); // This will need to go elsewhere in the API layer. diff --git a/core/test/functional/admin/03_editor_test.js b/core/test/functional/admin/03_editor_test.js index 5a498e5533..fa8b0451f2 100644 --- a/core/test/functional/admin/03_editor_test.js +++ b/core/test/functional/admin/03_editor_test.js @@ -128,6 +128,31 @@ casper.test.begin("Word count and plurality", 4, function suite(test) { test.assertSelectorHasText('.entry-word-count', '2 words', 'count of 2 produces plural "words".'); }); + casper.run(function () { + test.done(); + }); +}); + +casper.test.begin('Title Trimming', function suite(test) { + var untrimmedTitle = ' test title ', + trimmedTitle = 'test title'; + + test.filename = 'editor_title_trimming_test.png'; + + casper.start(url + 'ghost/editor/', function testTitleAndUrl() { + test.assertTitle('', 'Ghost admin has no title'); + }).viewport(1280, 1024); + + casper.then(function populateTitle() { + casper.sendKeys('#entry-title', untrimmedTitle); + + test.assertEvalEquals(function () { + + return $('#entry-title').val(); + + }, trimmedTitle, 'Entry title should match expected value.'); + }); + casper.run(function () { test.done(); }); diff --git a/core/test/unit/api_posts_spec.js b/core/test/unit/api_posts_spec.js index 4c1545c9d6..3ed2b17a40 100644 --- a/core/test/unit/api_posts_spec.js +++ b/core/test/unit/api_posts_spec.js @@ -162,6 +162,28 @@ describe('Post Model', function () { }); + it('can trim title', function (done) { + var untrimmedCreateTitle = ' test trimmed create title ', + untrimmedUpdateTitle = ' test trimmed update title ', + newPost = { + title: untrimmedCreateTitle, + content_raw: 'Test Content' + }; + + PostModel.add(newPost).then(function (createdPost) { + return new PostModel({ id: createdPost.id }).fetch(); + }).then(function (createdPost) { + should.exist(createdPost); + createdPost.get('title').should.equal(untrimmedCreateTitle.trim()); + + return createdPost.save({ title: untrimmedUpdateTitle }); + }).then(function (updatedPost) { + updatedPost.get('title').should.equal(untrimmedUpdateTitle.trim()); + + done(); + }).otherwise(done); + }); + it('can generate a non conflicting slug', function (done) { var newPost = { title: 'Test Title',