diff --git a/core/client/assets/sass/layouts/editor.scss b/core/client/assets/sass/layouts/editor.scss index b24bb89b22..be4a40ea6d 100644 --- a/core/client/assets/sass/layouts/editor.scss +++ b/core/client/assets/sass/layouts/editor.scss @@ -521,7 +521,15 @@ body.zen { } -#entry-settings { +#entry-controls { + @include box-sizing(border-box); + display: inline-block; + position: relative; + padding: 0; + z-index: 1; +} + +.entry-settings { @include icon($i-settings, 1.1em){vertical-align: 0;}; @include box-sizing(border-box); display: inline-block; @@ -535,9 +543,9 @@ body.zen { } } -#entry-settings-menu { +.entry-settings-menu { position: absolute; - bottom: 50px; + bottom: 40px; right: -5px; } diff --git a/core/client/assets/sass/layouts/manage.scss b/core/client/assets/sass/layouts/manage.scss index 3714e465c5..a3e53f363d 100644 --- a/core/client/assets/sass/layouts/manage.scss +++ b/core/client/assets/sass/layouts/manage.scss @@ -232,7 +232,7 @@ ul { position: absolute; top:15px; - right:-15px; + right:-10px; } } @@ -241,11 +241,6 @@ margin-right:7px; padding: 5px; } - .post-settings { - @include icon($i-settings, 14px); - padding: 5px; - margin-right: -5px; - } img { width:100%; diff --git a/core/client/assets/sass/modules/global.scss b/core/client/assets/sass/modules/global.scss index 06b8b6a457..7472410225 100644 --- a/core/client/assets/sass/modules/global.scss +++ b/core/client/assets/sass/modules/global.scss @@ -831,6 +831,59 @@ nav { @extend %menu-right; } +/* ========================================================================== + Post Settings + ========================================================================== */ + +.post-setting { + min-width: 260px; + border-bottom: 1px solid #35393b; + + &:first-child { + margin-top: -6px; + } + + input { + width: 100%; + background: none; + border: none; + color: #e2edf2; + line-height: 1.68em; + } +} + +.post-setting-label { + float: left; + width: 26%; + text-align: right; + padding: 10px 2%; + border-right: 1px solid #35393b; +} + +.post-setting-field { + float: left; + width: 64%; + padding: 8px 2%; + + input:focus { + outline: none; + } +} + +.post-settings { + @include icon($i-settings, 14px); + padding: 5px; + /* margin-right: -5px;*/ +} + +.post-setting-calendar { + @include icon-after($i-calendar, 18px); + width: 18px; + height: 18px; + position: absolute; + margin-top: -25px; /* This doesn't work in FF */ + right: 10px; +} /* ========================================================================== Notifications diff --git a/core/client/tpl/preview.hbs b/core/client/tpl/preview.hbs index 16480506f4..62f449133e 100644 --- a/core/client/tpl/preview.hbs +++ b/core/client/tpl/preview.hbs @@ -10,6 +10,22 @@ diff --git a/core/client/views/blog.js b/core/client/views/blog.js index 6a5e1714ad..dc1c1b7436 100644 --- a/core/client/views/blog.js +++ b/core/client/views/blog.js @@ -114,7 +114,6 @@ activeId: null, events: { - 'click .post-controls .delete' : 'deletePost', 'click .post-controls .post-edit' : 'editPost' }, @@ -129,53 +128,6 @@ } }, - deletePost: function (e) { - e.preventDefault(); - var self = this; - this.addSubview(new Ghost.Views.Modal({ - model: { - options: { - close: false, - confirm: { - accept: { - func: function () { - self.model.destroy({ - wait: true - }).then(function () { - Ghost.notifications.addItem({ - type: 'success', - message: 'Your post has been deleted.', - status: 'passive' - }); - }, function () { - Ghost.notifications.addItem({ - type: 'error', - message: 'Your post could not be deleted. Please try again.', - status: 'passive' - }); - }); - }, - text: "Yes" - }, - reject: { - func: function () { - return true; - }, - text: "No" - } - }, - type: "action", - style: ["wide", "centered"], - animation: 'fade' - }, - content: { - template: 'blank', - title: 'Are you sure you want to delete this post?' - } - } - })); - }, - editPost: function (e) { e.preventDefault(); // for now this will disable "open in new tab", but when we have a Router implemented @@ -194,6 +146,11 @@ this.$('.wrapper').on('click', 'a', function (e) { $(e.currentTarget).attr('target', '_blank'); }); + + if (this.model !== 'undefined') { + this.addSubview(new Ghost.View.PostSettings({el: $('.post-controls'), model: this.model})).render(); + } + Ghost.temporary.initToggles(this.$el); return this; } diff --git a/core/client/views/editor.js b/core/client/views/editor.js index 3ef57a849a..eb25bdbe20 100644 --- a/core/client/views/editor.js +++ b/core/client/views/editor.js @@ -41,6 +41,7 @@ initialize: function () { this.addSubview(new Ghost.View.EditorTagWidget({el: this.$('#entry-tags'), model: this.model})).render(); this.addSubview(new ActionsWidget({el: this.$('#entry-actions'), model: this.model})).render(); + this.addSubview(new Ghost.View.PostSettings({el: $('#entry-controls'), model: this.model})).render(); }, render: function () { return this; } diff --git a/core/client/views/post-settings.js b/core/client/views/post-settings.js new file mode 100644 index 0000000000..6a49fb583d --- /dev/null +++ b/core/client/views/post-settings.js @@ -0,0 +1,113 @@ +// The Post Settings Menu available in the content preview screen, as well as the post editor. + +/*global window, document, $, _, Backbone, Ghost */ + +(function () { + "use strict"; + + Ghost.View.PostSettings = Ghost.View.extend({ + + events: { + 'blur .post-setting-slug' : 'editSlug', + 'click .post-setting-slug' : 'selectSlug', + 'click .delete' : 'deletePost' + }, + + render: function () { + var slug = this.model ? this.model.get('slug') : ''; + //var pubDate = this.model.get('published_at'); + + //pubDate = moment(pubDate).format('DD MMM YY'); + + $('.post-setting-slug').val(slug); + //$('.post-setting-date').val(pubDate); + }, + + selectSlug: function (e) { + e.currentTarget.select(); + }, + + editSlug: function (e) { + e.preventDefault(); + var self = this, + slug = self.model.get('slug'), + slugEl = e.currentTarget, + newSlug = slugEl.value; + + // Ignore empty or unchanged slugs + if (newSlug.length === 0 || slug === newSlug) { + slugEl.value = slug === undefined ? '' : slug; + return; + } + + this.model.save({ + slug: newSlug + }, { + success : function (model, response, options) { + // Repopulate slug in case it changed on the server (e.g. 'new-slug-2') + slugEl.value = model.get('slug'); + }, + error : function (model, xhr) { + Ghost.notifications.addItem({ + type: 'error', + message: Ghost.Views.Utils.getRequestErrorMessage(xhr), + status: 'passive' + }); + } + }); + }, + + deletePost: function (e) { + e.preventDefault(); + var self = this; + this.addSubview(new Ghost.Views.Modal({ + model: { + options: { + close: false, + confirm: { + accept: { + func: function () { + self.model.destroy({ + wait: true + }).then(function () { + // Redirect to content screen if deleting post from editor. + if (window.location.pathname.indexOf('editor') > -1) { + window.location = '/ghost/content/'; + } + Ghost.notifications.addItem({ + type: 'success', + message: 'Your post has been deleted.', + status: 'passive' + }); + }, function () { + Ghost.notifications.addItem({ + type: 'error', + message: 'Your post could not be deleted. Please try again.', + status: 'passive' + }); + }); + }, + text: "Yes" + }, + reject: { + func: function () { + return true; + }, + text: "No" + } + }, + type: "action", + style: ["wide", "centered"], + animation: 'fade' + }, + content: { + template: 'blank', + title: 'Are you sure you want to delete this post?' + } + } + })); + } + + }); + +}()); \ No newline at end of file diff --git a/core/server/models/post.js b/core/server/models/post.js index 1e67154183..04cb845448 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -47,6 +47,7 @@ Post = GhostBookshelf.Model.extend({ saving: function () { // Deal with the related data here + var self = this; // Remove any properties which don't belong on the post model this.attributes = this.pick(this.permittedAttributes); @@ -55,6 +56,14 @@ Post = GhostBookshelf.Model.extend({ this.set('title', this.get('title').trim()); + if (this.hasChanged('slug')) { + // Pass the new slug through the generator to strip illegal characters, detect duplicates + return this.generateSlug(this.get('slug')) + .then(function (slug) { + self.set({slug: slug}); + }); + } + 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/server/views/default.hbs b/core/server/views/default.hbs index d60ee7fe2d..0ba88e1553 100644 --- a/core/server/views/default.hbs +++ b/core/server/views/default.hbs @@ -84,6 +84,7 @@ + diff --git a/core/server/views/editor.hbs b/core/server/views/editor.hbs index 9116f28e85..b2c9583ca5 100644 --- a/core/server/views/editor.hbs +++ b/core/server/views/editor.hbs @@ -40,6 +40,30 @@
+ +
+ + +
+
diff --git a/core/test/unit/admin_spec.js b/core/test/unit/admin_spec.js index d7f896aab5..5a82fd832c 100644 --- a/core/test/unit/admin_spec.js +++ b/core/test/unit/admin_spec.js @@ -100,7 +100,7 @@ describe('Admin Controller', function() { }); it('should send correct path to image when today is in Jan 2014', function(done) { - clock = sinon.useFakeTimers(1388534400000); // Wed Jan 01 2014 00:00:00 GMT+0000 (GMT) + clock = sinon.useFakeTimers(1388707200000); // Wed Jan 03 2014 00:00:00 GMT+0000 (GMT) sinon.stub(res, 'send', function(data) { data.should.equal('/content/images/2014/Jan/IMAGE.jpg'); return done();