mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Added "Save" button to editor for scheduled and published posts
refs https://github.com/TryGhost/Team/issues/1597 - added "Save" button to editor for scheduled and published posts when any edits have been made - shows "Saving..." then "Saved" for 3 seconds before disappearing - replaces "Saving..." indicator shown in status bar on the left - added `showIcon` argument to `<GhTaskButton>` so it can be used for text style buttons - changed editor status behaviour to only show "Scheduled" by default with the full text shown on hover
This commit is contained in:
parent
8a303fe411
commit
756f5094b4
8 changed files with 82 additions and 29 deletions
|
@ -10,6 +10,19 @@
|
|||
<span>Publish</span>
|
||||
</button>
|
||||
{{else}}
|
||||
{{#if (or @hasUnsavedChanges this.saveButtonTaskGroup.isRunning)}}
|
||||
<GhTaskButton
|
||||
@task={{this.saveTask}}
|
||||
@runningText="Saving..."
|
||||
@class="gh-btn gh-btn-editor gh-publishmenu-trigger"
|
||||
@idleClass="green"
|
||||
@runningClass="midlightgrey"
|
||||
@successClass="midlightgrey"
|
||||
@failureClass="red"
|
||||
@showIcon={{false}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor darkgrey gh-publishmenu-trigger"
|
||||
|
|
|
@ -3,13 +3,15 @@ import EmailFailedError from 'ghost-admin/errors/email-failed-error';
|
|||
import PublishFlowModal from './modals/publish-flow';
|
||||
import PublishOptionsResource from 'ghost-admin/helpers/publish-options';
|
||||
import UpdateFlowModal from './modals/update-flow';
|
||||
import envConfig from 'ghost-admin/config/environment';
|
||||
import moment from 'moment';
|
||||
import {action, get} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {task, taskGroup, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
import {use} from 'ember-could-get-used-to-this';
|
||||
|
||||
const SHOW_SAVE_STATUS_DURATION = 3000;
|
||||
const CONFIRM_EMAIL_POLL_LENGTH = 1000;
|
||||
const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000;
|
||||
|
||||
|
@ -374,7 +376,7 @@ export default class PublishManagement extends Component {
|
|||
|
||||
this.publishFlowModal = this.modals.open(PublishFlowModal, {
|
||||
publishOptions: this.publishOptions,
|
||||
saveTask: this.saveTask
|
||||
saveTask: this.publishTask
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -388,28 +390,28 @@ export default class PublishManagement extends Component {
|
|||
if (!this.updateFlowModal || this.updateFlowModal.isClosing) {
|
||||
this.updateFlowModal = this.modals.open(UpdateFlowModal, {
|
||||
publishOptions: this.publishOptions,
|
||||
saveTask: this.saveTask,
|
||||
saveTask: this.publishTask,
|
||||
revertToDraftTask: this.revertToDraftTask
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask({taskName = 'saveTask'} = {}) {
|
||||
*publishTask({taskName = 'saveTask'} = {}) {
|
||||
const willEmail = this.publishOptions.willEmail;
|
||||
|
||||
// clean up blank editor cards
|
||||
// apply cloned mobiledoc
|
||||
// apply scratch values
|
||||
// generate slug if needed (should never happen - publish flow can't be opened on new posts)
|
||||
yield this.args.beforeSave();
|
||||
yield this.args.beforePublish();
|
||||
|
||||
// apply publish options (with undo on failure)
|
||||
// save with the required query params for emailing
|
||||
const result = yield this.publishOptions[taskName].perform();
|
||||
|
||||
// perform any post-save cleanup for the editor
|
||||
yield this.args.afterSave(result);
|
||||
yield this.args.afterPublish(result);
|
||||
|
||||
// if emailed, wait until it has been submitted so we can show a failure message if needed
|
||||
if (willEmail && this.publishOptions.post.email) {
|
||||
|
@ -419,6 +421,21 @@ export default class PublishManagement extends Component {
|
|||
return result;
|
||||
}
|
||||
|
||||
// used by the non-publish "Save" button shown for scheduled/published posts
|
||||
@task({group: 'saveButtonTaskGroup'})
|
||||
*saveTask() {
|
||||
yield this.args.saveTask.perform();
|
||||
this.saveButtonTimeoutTask.perform();
|
||||
return true;
|
||||
}
|
||||
|
||||
@task({group: 'saveButtonTaskGroup'})
|
||||
*saveButtonTimeoutTask() {
|
||||
yield timeout(envConfig.environment === 'test' ? 1 : SHOW_SAVE_STATUS_DURATION);
|
||||
}
|
||||
|
||||
@taskGroup saveButtonTaskGroup;
|
||||
|
||||
@task
|
||||
*confirmEmailTask() {
|
||||
const post = this.publishOptions.post;
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<div data-test-editor-post-status ...attributes>
|
||||
{{#if this.isSaving}}
|
||||
<div role="tooltip" {{on "mouseover" this.onMouseover}} {{on "mouseleave" this.onMouseleave}} data-test-editor-post-status ...attributes>
|
||||
{{#if (and this.isSaving @post.isDraft)}}
|
||||
Saving...
|
||||
{{else if @post.isSent}}
|
||||
Sent to {{gh-pluralize @post.email.emailCount "member"}}
|
||||
{{else if (and @post.emailOnly @post.isScheduled)}}
|
||||
Scheduled
|
||||
{{#if this.isHovered}}
|
||||
<time datetime="{{@post.publishedAtUTC}}" class="ml1 green f8" data-test-schedule-countdown>
|
||||
Will be sent to <GhRecipientFilterCount @filter={{@post.emailRecipientFilter}} />
|
||||
to be sent to <GhRecipientFilterCount @filter={{@post.emailRecipientFilter}} />
|
||||
{{this.scheduledTime}}
|
||||
</time>
|
||||
{{/if}}
|
||||
{{else if (or @post.isPublished @post.pastScheduledTime)}}
|
||||
Published
|
||||
{{#if (or (eq @post.email.status "submitting") (eq @post.email.status "submitting"))}}
|
||||
|
@ -17,11 +20,14 @@
|
|||
{{/if}}
|
||||
{{else if @post.isScheduled}}
|
||||
<time datetime="{{@post.publishedAtUTC}}" class="ml1 green f8" data-test-schedule-countdown>
|
||||
Will be published
|
||||
Scheduled
|
||||
{{#if this.isHovered}}
|
||||
to be published
|
||||
{{#if (and @post.emailRecipientFilter (not @post.email))}}
|
||||
and sent to <GhRecipientFilterCount @filter={{@post.emailRecipientFilter}} />
|
||||
{{/if}}
|
||||
{{this.scheduledTime}}
|
||||
{{/if}}
|
||||
</time>
|
||||
{{else if @post.isNew}}
|
||||
New
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Component from '@glimmer/component';
|
||||
import config from 'ghost-admin/config/environment';
|
||||
import {action, get} from '@ember/object';
|
||||
import {formatPostTime} from 'ghost-admin/helpers/gh-format-post-time';
|
||||
import {get} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
@ -10,6 +10,8 @@ export default class GhEditorPostStatusComponent extends Component {
|
|||
@service clock;
|
||||
@service settings;
|
||||
|
||||
@tracked isHovered = false;
|
||||
|
||||
@tracked _isSaving = false;
|
||||
|
||||
// this.args.isSaving will only be true briefly whilst the post is saving,
|
||||
|
@ -34,6 +36,16 @@ export default class GhEditorPostStatusComponent extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
@action
|
||||
onMouseover() {
|
||||
this.isHovered = true;
|
||||
}
|
||||
|
||||
@action
|
||||
onMouseleave() {
|
||||
this.isHovered = false;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*showSavingMessage() {
|
||||
this._isSaving = true;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
isFailure=this.isFailure
|
||||
)}}
|
||||
{{else}}
|
||||
{{#if this.isRunning}}<span data-test-task-button-state="running">{{svg-jar "spinner" class="gh-icon-spinner"}}{{this.runningText}}</span>{{/if}}
|
||||
{{#if this.isRunning}}<span data-test-task-button-state="running">{{#if this.showIcon}}{{svg-jar "spinner" class="gh-icon-spinner"}} {{/if}}{{this.runningText}}</span>{{/if}}
|
||||
{{#if this.isIdle}}<span data-test-task-button-state="idle">{{this.buttonText}}</span>{{/if}}
|
||||
{{#if this.isSuccess}}<span {{did-insert this.handleReset}} data-test-task-button-state="success">{{svg-jar "check-circle"}} {{this.successText}}</span>{{/if}}
|
||||
{{#if this.isFailure}}<span data-test-task-button-state="failure">{{svg-jar "retry"}} {{this.failureText}}</span>{{/if}}
|
||||
{{#if this.isSuccess}}<span {{did-insert this.handleReset}} data-test-task-button-state="success">{{#if this.showIcon}}{{svg-jar "check-circle"}} {{/if}}{{this.successText}}</span>{{/if}}
|
||||
{{#if this.isFailure}}<span data-test-task-button-state="failure">{{#if this.showIcon}}{{svg-jar "retry"}} {{/if}}{{this.failureText}}</span>{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
@ -34,6 +34,7 @@ const GhTaskButton = Component.extend({
|
|||
buttonText: 'Save',
|
||||
idleClass: '',
|
||||
runningClass: '',
|
||||
showIcon: true,
|
||||
showSuccess: true, // set to false if you want the spinner to show until a transition occurs
|
||||
autoReset: true, // set to false if you want don't want task button to reset after timeout
|
||||
successText: 'Saved',
|
||||
|
|
|
@ -48,8 +48,10 @@
|
|||
{{#if (feature "publishingFlow")}}
|
||||
<EditorLabs::PublishManagement
|
||||
@post={{this.post}}
|
||||
@beforeSave={{perform this.beforeSaveTask}}
|
||||
@afterSave={{this.afterSave}}
|
||||
@hasUnsavedChanges={{this.hasDirtyAttributes}}
|
||||
@beforePublish={{perform this.beforeSaveTask}}
|
||||
@afterPublish={{this.afterSave}}
|
||||
@saveTask={{this.saveTask}}
|
||||
/>
|
||||
{{else}}
|
||||
<GhPublishmenu
|
||||
|
|
|
@ -361,8 +361,9 @@ describe('Acceptance: Editor', function () {
|
|||
).to.not.exist;
|
||||
|
||||
// expect countdown to show warning that post is scheduled to be published
|
||||
await triggerEvent('[data-test-editor-post-status]', 'mouseover');
|
||||
expect(find('[data-test-schedule-countdown]').textContent.trim(), 'notification countdown')
|
||||
.to.match(/Will be published\s+in (4|5) minutes/);
|
||||
.to.match(/to be published\s+in (4|5) minutes/);
|
||||
|
||||
expect(
|
||||
find('[data-test-publishmenu-trigger]').textContent.trim(),
|
||||
|
@ -372,7 +373,7 @@ describe('Acceptance: Editor', function () {
|
|||
expect(
|
||||
find('[data-test-editor-post-status]').textContent.trim(),
|
||||
'scheduled post status'
|
||||
).to.match(/Will be published\s+in (4|5) minutes/);
|
||||
).to.match(/to be published\s+in (4|5) minutes/);
|
||||
|
||||
// Re-schedule
|
||||
await click('[data-test-publishmenu-trigger]');
|
||||
|
@ -399,7 +400,7 @@ describe('Acceptance: Editor', function () {
|
|||
expect(
|
||||
find('[data-test-editor-post-status]').textContent.trim(),
|
||||
'scheduled status text'
|
||||
).to.match(/Will be published\s+in (4|5) minutes/);
|
||||
).to.match(/to be published\s+in (4|5) minutes/);
|
||||
|
||||
// unschedule
|
||||
await click('[data-test-publishmenu-trigger]');
|
||||
|
@ -556,8 +557,9 @@ describe('Acceptance: Editor', function () {
|
|||
expect(find('[data-test-publishmenu-trigger]').textContent.trim(), 'text in save button for scheduled post')
|
||||
.to.equal('Scheduled');
|
||||
// expect countdown to show warning, that post is scheduled to be published
|
||||
await triggerEvent('[data-test-editor-post-status]', 'mouseover');
|
||||
expect(find('[data-test-schedule-countdown]').textContent.trim(), 'notification countdown')
|
||||
.to.match(/Will be published\s+in (4|5) minutes/);
|
||||
.to.match(/to be published\s+in (4|5) minutes/);
|
||||
});
|
||||
|
||||
it('shows author token input and allows changing of authors in PSM', async function () {
|
||||
|
|
Loading…
Add table
Reference in a new issue