mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Publish button amendments
Fixes #667 - Removed superfluous as-of-yet-unused options in the publish menu. - Adjusted display names of publish buttons according to differing states the publish menu can be in (new post, saved draft, published post). - Added red highlight style to "important" status change options in the publish menu (draft => published, published => unpublished). - Added suite of functional tests around new labels and classes.
This commit is contained in:
parent
03f2fd87bb
commit
07629dd9ab
6 changed files with 218 additions and 66 deletions
|
@ -420,9 +420,11 @@ body.zen {
|
|||
box-shadow: rgba(255,255,255,0.4) 0 1px 0 inset;
|
||||
}
|
||||
|
||||
.splitbutton-save{
|
||||
.button-save{
|
||||
@include transition(width 0.25s ease);
|
||||
.splitbutton-save,
|
||||
.splitbutton-delete{
|
||||
.button-save,
|
||||
.button-delete{
|
||||
@include transition(width 0.25s ease, background-color 0.3s linear);
|
||||
}
|
||||
|
||||
.editor-options{
|
||||
|
|
|
@ -315,6 +315,7 @@ input[type="reset"] {
|
|||
@include transition-duration(0.3);
|
||||
@include transition-timing-function(ease);
|
||||
};
|
||||
@include transition(background-color 0.3s linear);
|
||||
|
||||
// Keep the arrow spun when the associated menu is open
|
||||
&.active:before {
|
||||
|
|
|
@ -54,23 +54,24 @@
|
|||
|
||||
events: {
|
||||
'click [data-set-status]': 'handleStatus',
|
||||
'click .js-post-button': 'handlePostButton'
|
||||
'click .js-publish-button': 'handlePostButton'
|
||||
},
|
||||
|
||||
statusMap: {
|
||||
statusMap: null,
|
||||
|
||||
createStatusMap: {
|
||||
'draft': 'Save Draft',
|
||||
'published': 'Publish Now',
|
||||
'scheduled': 'Save Schedued Post',
|
||||
'queue': 'Add to Queue',
|
||||
'publish-on': 'Publish on...'
|
||||
'published': 'Publish Now'
|
||||
},
|
||||
|
||||
updateStatusMap: {
|
||||
'draft': 'Unpublish',
|
||||
'published': 'Update Post'
|
||||
},
|
||||
|
||||
notificationMap: {
|
||||
'draft': 'has been saved as a draft',
|
||||
'published': 'has been published',
|
||||
'scheduled': 'has been scheduled',
|
||||
'queue': 'has been added to the queue',
|
||||
'publish-on': 'will be published'
|
||||
'draft': 'saved as a draft',
|
||||
'published': 'published'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
|
@ -94,66 +95,72 @@
|
|||
toggleStatus: function () {
|
||||
var self = this,
|
||||
keys = Object.keys(this.statusMap),
|
||||
model = this.model,
|
||||
prevStatus = this.model.get('status'),
|
||||
model = self.model,
|
||||
prevStatus = model.get('status'),
|
||||
currentIndex = keys.indexOf(prevStatus),
|
||||
newIndex;
|
||||
|
||||
newIndex = currentIndex + 1 > keys.length - 1 ? 0 : currentIndex + 1;
|
||||
|
||||
if (keys[currentIndex + 1] === 'scheduled') { // TODO: Remove once scheduled posts work
|
||||
newIndex = currentIndex + 2 > keys.length - 1 ? 0 : currentIndex + 1;
|
||||
} else {
|
||||
newIndex = currentIndex + 1 > keys.length - 1 ? 0 : currentIndex + 1;
|
||||
}
|
||||
this.setActiveStatus(keys[newIndex], this.statusMap[keys[newIndex]], prevStatus);
|
||||
|
||||
this.savePost({
|
||||
status: keys[newIndex]
|
||||
}).then(function () {
|
||||
Ghost.notifications.addItem({
|
||||
type: 'success',
|
||||
message: 'Your post ' + this.notificationMap[newIndex] + '.',
|
||||
message: 'Your post has been ' + self.notificationMap[newIndex] + '.',
|
||||
status: 'passive'
|
||||
});
|
||||
}, function (xhr) {
|
||||
var status = keys[newIndex];
|
||||
// Show a notification about the error
|
||||
self.reportSaveError(xhr, model, status);
|
||||
// Set the button text back to previous
|
||||
model.set({ status: prevStatus });
|
||||
});
|
||||
},
|
||||
|
||||
setActiveStatus: function (status, displayText) {
|
||||
// Set the publish button's action
|
||||
$('.js-post-button')
|
||||
.attr('data-status', status)
|
||||
.text(displayText);
|
||||
setActiveStatus: function (newStatus, displayText, currentStatus) {
|
||||
var isPublishing = (newStatus === 'published' && currentStatus !== 'published'),
|
||||
isUnpublishing = (newStatus === 'draft' && currentStatus === 'published'),
|
||||
// Controls when background of button has the splitbutton-delete/button-delete classes applied
|
||||
isImportantStatus = (isPublishing || isUnpublishing);
|
||||
|
||||
$('.js-publish-splitbutton')
|
||||
.removeClass(isImportantStatus ? 'splitbutton-save' : 'splitbutton-delete')
|
||||
.addClass(isImportantStatus ? 'splitbutton-delete' : 'splitbutton-save');
|
||||
|
||||
// Set the publish button's action and proper coloring
|
||||
$('.js-publish-button')
|
||||
.attr('data-status', newStatus)
|
||||
.text(displayText)
|
||||
.removeClass(isImportantStatus ? 'button-save' : 'button-delete')
|
||||
.addClass(isImportantStatus ? 'button-delete' : 'button-save');
|
||||
|
||||
// Remove the animated popup arrow
|
||||
$('.splitbutton-save > a')
|
||||
$('.js-publish-splitbutton > a')
|
||||
.removeClass('active');
|
||||
|
||||
// Set the active action in the popup
|
||||
$('.splitbutton-save .editor-options li')
|
||||
$('.js-publish-splitbutton .editor-options li')
|
||||
.removeClass('active')
|
||||
.filter(['li[data-set-status="', status, '"]'].join(''))
|
||||
.filter(['li[data-set-status="', newStatus, '"]'].join(''))
|
||||
.addClass('active');
|
||||
},
|
||||
|
||||
handleStatus: function (e) {
|
||||
if (e) { e.preventDefault(); }
|
||||
var status = $(e.currentTarget).attr('data-set-status');
|
||||
var status = $(e.currentTarget).attr('data-set-status'),
|
||||
currentStatus = this.model.get('status');
|
||||
|
||||
this.setActiveStatus(status, this.statusMap[status]);
|
||||
this.setActiveStatus(status, this.statusMap[status], currentStatus);
|
||||
|
||||
// Dismiss the popup menu
|
||||
$('body').find('.overlay:visible').fadeOut();
|
||||
},
|
||||
|
||||
handlePostButton: function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var status = $(e.currentTarget).attr("data-status");
|
||||
if (e) { e.preventDefault(); }
|
||||
var status = $(e.currentTarget).attr('data-status');
|
||||
|
||||
this.updatePost(status);
|
||||
},
|
||||
|
@ -167,32 +174,23 @@
|
|||
// Default to same status if not passed in
|
||||
status = status || prevStatus;
|
||||
|
||||
if (status === 'publish-on') {
|
||||
return Ghost.notifications.addItem({
|
||||
type: 'alert',
|
||||
message: 'Scheduled publishing not supported yet.',
|
||||
status: 'passive'
|
||||
});
|
||||
}
|
||||
if (status === 'queue') {
|
||||
return Ghost.notifications.addItem({
|
||||
type: 'alert',
|
||||
message: 'Scheduled publishing not supported yet.',
|
||||
status: 'passive'
|
||||
});
|
||||
}
|
||||
|
||||
this.model.trigger('willSave');
|
||||
model.trigger('willSave');
|
||||
|
||||
this.savePost({
|
||||
status: status
|
||||
}).then(function () {
|
||||
Ghost.notifications.addItem({
|
||||
type: 'success',
|
||||
message: ['Your post ', notificationMap[status], '.'].join(''),
|
||||
message: ['Your post has been ', notificationMap[status], '.'].join(''),
|
||||
status: 'passive'
|
||||
});
|
||||
// Refresh publish button and all relevant controls with updated status.
|
||||
self.render();
|
||||
}, function (xhr) {
|
||||
// Set the model status back to previous
|
||||
model.set({ status: prevStatus });
|
||||
// Set appropriate button status
|
||||
self.setActiveStatus(status, self.statusMap[status], prevStatus);
|
||||
// Show a notification about the error
|
||||
self.reportSaveError(xhr, model, status);
|
||||
});
|
||||
|
@ -219,7 +217,8 @@
|
|||
|
||||
reportSaveError: function (response, model, status) {
|
||||
var title = model.get('title') || '[Untitled]',
|
||||
message = 'Your post: ' + title + ' has not been ' + status;
|
||||
notificationStatus = this.notificationMap[status],
|
||||
message = 'Your post: ' + title + ' has not been ' + notificationStatus;
|
||||
|
||||
if (response) {
|
||||
// Get message from response
|
||||
|
@ -236,11 +235,27 @@
|
|||
});
|
||||
},
|
||||
|
||||
setStatusLabels: function (statusMap) {
|
||||
_.each(statusMap, function (label, status) {
|
||||
$('li[data-set-status="' + status + '"] > a').text(label);
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var status = this.model.get('status');
|
||||
|
||||
// Assume that we're creating a new post
|
||||
if (status !== 'published') {
|
||||
this.statusMap = this.createStatusMap;
|
||||
} else {
|
||||
this.statusMap = this.updateStatusMap;
|
||||
}
|
||||
|
||||
// Populate the publish menu with the appropriate verbiage
|
||||
this.setStatusLabels(this.statusMap);
|
||||
|
||||
// Default the selected publish option to the current status of the post.
|
||||
this.setActiveStatus(status, this.statusMap[status]);
|
||||
this.setActiveStatus(status, this.statusMap[status], status);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -64,14 +64,12 @@
|
|||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="entry-actions" class="splitbutton-save">
|
||||
<button type="button" class="button-save js-post-button"></button>
|
||||
<section id="entry-actions" class="js-publish-splitbutton splitbutton-save">
|
||||
<button type="button" class="js-publish-button button-save"></button>
|
||||
<a class="options up" data-toggle="ul" href="#"><span class="hidden">Options</span></a>
|
||||
<ul class="editor-options overlay" style="display:none">
|
||||
<li data-set-status="published"><a href="#">Publish Now</a></li>
|
||||
<li data-set-status="queue"><a href="#">Add to Queue</a></li>
|
||||
<li data-set-status="publish-on"><a href="#">Publish on...</a></li>
|
||||
<li data-set-status="draft"><a href="#">Save Draft</a></li>
|
||||
<li data-set-status="published"><a href="#"></a></li>
|
||||
<li data-set-status="draft"><a href="#"></a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@ casper.test.begin("Ghost editor is correct", 10, function suite(test) {
|
|||
}
|
||||
|
||||
// test saving with no data
|
||||
casper.thenClick('.button-save');
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
test.assert(true, 'Save without title results in error notification as expected');
|
||||
|
@ -39,7 +39,7 @@ casper.test.begin("Ghost editor is correct", 10, function suite(test) {
|
|||
casper.on('resource.received', handleResource);
|
||||
});
|
||||
|
||||
casper.thenClick('.button-save');
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForResource(/posts/, function checkPostWasCreated() {
|
||||
var urlRegExp = new RegExp("^" + url + "ghost\/editor\/[0-9]*");
|
||||
|
@ -153,6 +153,142 @@ casper.test.begin('Title Trimming', function suite(test) {
|
|||
}, trimmedTitle, 'Entry title should match expected value.');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
casper.test.begin('Publish menu - new post', function suite(test) {
|
||||
test.filename = 'publish_menu_new_post.png';
|
||||
|
||||
casper.start(url + 'ghost/editor/', function testTitleAndUrl() {
|
||||
test.assertTitle('', 'Ghost admin has no title');
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
// ... check default option status, label, class
|
||||
casper.then(function () {
|
||||
test.assertExists('.js-publish-splitbutton');
|
||||
test.assertExists('.js-publish-splitbutton.splitbutton-save');
|
||||
test.assertExists('.js-publish-button');
|
||||
test.assertExists('.js-publish-button.button-save');
|
||||
test.assertSelectorHasText('.js-publish-button', 'Save Draft');
|
||||
test.assertEval(function() {
|
||||
return (__utils__.findOne('.js-publish-button').getAttribute('data-status') === 'draft');
|
||||
}, 'Publish button\'s initial status should be "draft"');
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
// ... click the menu
|
||||
this.click('.js-publish-splitbutton .options.up');
|
||||
// ... click publish
|
||||
this.click('.js-publish-splitbutton li[data-set-status="published"]');
|
||||
});
|
||||
|
||||
// ... check status, label, class
|
||||
casper.then(function () {
|
||||
test.assertExists('.js-publish-splitbutton.splitbutton-delete', 'Publish split button should have .splitbutton-delete');
|
||||
test.assertExists('.js-publish-button.button-delete', 'Publish button should have .button-delete');
|
||||
test.assertSelectorHasText('.js-publish-button', 'Publish Now');
|
||||
test.assertEval(function() {
|
||||
return (__utils__.findOne('.js-publish-button').getAttribute('data-status') === 'published');
|
||||
}, 'Publish button\'s updated status should be "published"');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
casper.test.begin('Publish menu - existing post', function suite(test) {
|
||||
test.filename = 'publish_menu_existing_post.png';
|
||||
|
||||
// Create a post, save it and test refreshed editor
|
||||
casper.start(url + 'ghost/editor/', function testTitleAndUrl() {
|
||||
test.assertTitle('', 'Ghost admin has no title');
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
casper.then(function createTestPost() {
|
||||
casper.sendKeys('#entry-title', testPost.title);
|
||||
casper.writeContentToCodeMirror(testPost.html);
|
||||
});
|
||||
|
||||
// We must wait after sending keys to CodeMirror
|
||||
casper.wait(1000, function doneWait() {
|
||||
this.echo("I've waited for 1 seconds.");
|
||||
});
|
||||
|
||||
// Create a post in draft status
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForResource(/posts/, function checkPostWasCreated() {
|
||||
var urlRegExp = new RegExp("^" + url + "ghost\/editor\/[0-9]*");
|
||||
test.assertUrlMatch(urlRegExp, 'got an id on our URL');
|
||||
});
|
||||
|
||||
// ... check option status, label, class now that we're *saved* as 'draft'
|
||||
casper.then(function () {
|
||||
test.assertExists('.js-publish-splitbutton');
|
||||
test.assertExists('.js-publish-splitbutton.splitbutton-save');
|
||||
test.assertExists('.js-publish-button');
|
||||
test.assertExists('.js-publish-button.button-save');
|
||||
test.assertSelectorHasText('.js-publish-button', 'Save Draft');
|
||||
test.assertEval(function() {
|
||||
return (__utils__.findOne('.js-publish-button').getAttribute('data-status') === 'draft');
|
||||
}, 'Publish button\'s initial status should be "draft"');
|
||||
});
|
||||
|
||||
// Open the publish options menu;
|
||||
casper.thenClick('.js-publish-splitbutton .options.up');
|
||||
|
||||
// Select the publish post button
|
||||
casper.thenClick('.js-publish-splitbutton li[data-set-status="published"]');
|
||||
|
||||
// ... check status, label, class
|
||||
casper.then(function () {
|
||||
test.assertExists('.js-publish-splitbutton.splitbutton-delete', 'Publish split button should have .splitbutton-delete');
|
||||
test.assertExists('.js-publish-button.button-delete', 'Publish button should have .button-delete');
|
||||
test.assertSelectorHasText('.js-publish-button', 'Publish Now');
|
||||
test.assertEval(function() {
|
||||
return (__utils__.findOne('.js-publish-button').getAttribute('data-status') === 'published');
|
||||
}, 'Publish button\'s updated status should be "published"');
|
||||
});
|
||||
|
||||
// Publish the post
|
||||
casper.thenClick('.js-publish-button');
|
||||
|
||||
casper.waitForResource(/posts/, function checkPostWasCreated() {
|
||||
var urlRegExp = new RegExp("^" + url + "ghost\/editor\/[0-9]*");
|
||||
test.assertUrlMatch(urlRegExp, 'got an id on our URL');
|
||||
});
|
||||
|
||||
// ... check option status, label, class for saved as 'published'
|
||||
casper.then(function () {
|
||||
test.assertExists('.js-publish-splitbutton');
|
||||
test.assertExists('.js-publish-splitbutton.splitbutton-save');
|
||||
test.assertExists('.js-publish-button');
|
||||
test.assertExists('.js-publish-button.button-save');
|
||||
test.assertSelectorHasText('.js-publish-button', 'Update Post');
|
||||
test.assertEval(function() {
|
||||
return (__utils__.findOne('.js-publish-button').getAttribute('data-status') === 'published');
|
||||
}, 'Publish button\'s initial status on an already published post should be "published"');
|
||||
});
|
||||
|
||||
// Open the publish options menu
|
||||
casper.thenClick('.js-publish-splitbutton .options.up');
|
||||
|
||||
// Click the 'unpublish' option
|
||||
casper.thenClick('.js-publish-splitbutton li[data-set-status="draft"]');
|
||||
|
||||
// ... check status, label, class
|
||||
casper.then(function () {
|
||||
test.assertExists('.js-publish-splitbutton.splitbutton-delete', 'Publish split button should have .splitbutton-delete');
|
||||
test.assertExists('.js-publish-button.button-delete', 'Publish button should have .button-delete');
|
||||
test.assertSelectorHasText('.js-publish-button', 'Unpublish');
|
||||
test.assertEval(function() {
|
||||
return (__utils__.findOne('.js-publish-button').getAttribute('data-status') === 'draft');
|
||||
}, 'Publish button\'s updated status should be "draft"');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ casper.test.begin("Ghost edit draft flow works correctly", 7, function suite(tes
|
|||
this.echo("I've waited for 1 seconds.");
|
||||
});
|
||||
|
||||
casper.thenClick('.button-save');
|
||||
casper.thenClick('.js-publish-button');
|
||||
casper.waitForResource(/posts/);
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
|
@ -46,7 +46,7 @@ casper.test.begin("Ghost edit draft flow works correctly", 7, function suite(tes
|
|||
test.assertUrlMatch(/editor/, "Ghost sucessfully loaded the editor page again");
|
||||
});
|
||||
|
||||
casper.thenClick('.button-save');
|
||||
casper.thenClick('.js-publish-button');
|
||||
casper.waitForResource(/posts/);
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
|
|
Loading…
Reference in a new issue