This repository has been archived by the owner on Nov 28, 2022. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ new publish menu and date/time picker (#588)
closes TryGhost/Ghost#8249 - replaces the old split-button publish/schedule/update button with a less confusing menu system - adds a `{{gh-date-time-picker}}` component that contains a datepicker with separate time input - replaces the date text input in the post settings menu with `{{gh-date-time-picker}}` - disabled when post is scheduled, only way to update a scheduled time is via the publish menu - validates date is in the past when draft/published so there's no confusion with scheduling - displays saving status in top-left of editor screen - refactor editor (auto)saving processes to use ember-concurrency Other minor changes: - adds `post.publishedAtBlog{TZ,Date,Time}` properties to Post model to allow working with `publishedAt` datetime in the selected blog timezone rather than UTC - adds a `beforeSave` hook to `validation-engine` that is called after successful validation and before the Ember Data save call is made - adds validation of `publishedAtBlog{Date,Time}` to post validator - prevent gh-task-button showing last task state on first render - fixes bug where clicking into and out of the published date input in the PSM without making any changes saves a published date for draft posts
- Loading branch information
1 parent
0a09c24
commit 34cb65d
Showing
48 changed files
with
1,628 additions
and
894 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import Component from 'ember-component'; | ||
import computed, {reads, or} from 'ember-computed'; | ||
import injectService from 'ember-service/inject'; | ||
import moment from 'moment'; | ||
import {isBlank, isEmpty} from 'ember-utils'; | ||
|
||
export default Component.extend({ | ||
settings: injectService(), | ||
|
||
tagName: '', | ||
|
||
date: '', | ||
time: '', | ||
errors: null, | ||
dateErrorProperty: null, | ||
timeErrorProperty: null, | ||
|
||
_time: '', | ||
_previousTime: '', | ||
_minDate: null, | ||
_maxDate: null, | ||
|
||
blogTimezone: reads('settings.activeTimezone'), | ||
hasError: or('dateError', 'timeError'), | ||
|
||
timezone: computed('blogTimezone', function () { | ||
let blogTimezone = this.get('blogTimezone'); | ||
return moment.utc().tz(blogTimezone).format('z'); | ||
}), | ||
|
||
dateError: computed('errors.[]', 'dateErrorProperty', function () { | ||
let errors = this.get('errors'); | ||
let property = this.get('dateErrorProperty'); | ||
|
||
if (!isEmpty(errors.errorsFor(property))) { | ||
return errors.errorsFor(property).get('firstObject').message; | ||
} | ||
}), | ||
|
||
timeError: computed('errors.[]', 'timeErrorProperty', function () { | ||
let errors = this.get('errors'); | ||
let property = this.get('timeErrorProperty'); | ||
|
||
if (!isEmpty(errors.errorsFor(property))) { | ||
return errors.errorsFor(property).get('firstObject').message; | ||
} | ||
}), | ||
|
||
didReceiveAttrs() { | ||
let date = this.get('date'); | ||
let time = this.get('time'); | ||
let minDate = this.get('minDate'); | ||
let maxDate = this.get('maxDate'); | ||
let blogTimezone = this.get('blogTimezone'); | ||
|
||
if (!isBlank(date)) { | ||
this.set('_date', moment(date)); | ||
} else { | ||
this.set('_date', moment().tz(blogTimezone)); | ||
} | ||
|
||
if (isBlank(time)) { | ||
this.set('_time', this.get('_date').format('HH:mm')); | ||
} else { | ||
this.set('_time', this.get('time')); | ||
} | ||
this.set('_previousTime', this.get('_time')); | ||
|
||
// unless min/max date is at midnight moment will diable that day | ||
if (minDate === 'now') { | ||
this.set('_minDate', moment(moment().format('YYYY-MM-DD'))); | ||
} else if (!isBlank(minDate)) { | ||
this.set('_minDate', moment(moment(minDate).format('YYYY-MM-DD'))); | ||
} else { | ||
this.set('_minDate', null); | ||
} | ||
|
||
if (maxDate === 'now') { | ||
this.set('_maxDate', moment(moment().format('YYYY-MM-DD'))); | ||
} else if (!isBlank(maxDate)) { | ||
this.set('_maxDate', moment(moment(maxDate).format('YYYY-MM-DD'))); | ||
} else { | ||
this.set('_maxDate', null); | ||
} | ||
}, | ||
|
||
actions: { | ||
// if date or time is set and the other property is blank set that too | ||
// so that we don't get "can't be blank" errors | ||
setDate(date) { | ||
if (date !== this.get('_date')) { | ||
this.get('setDate')(date); | ||
|
||
if (isBlank(this.get('time'))) { | ||
this.get('setTime')(this.get('_time')); | ||
} | ||
} | ||
}, | ||
|
||
setTime(time) { | ||
if (time !== this.get('_previousTime')) { | ||
this.get('setTime')(time); | ||
this.set('_previousTime', time); | ||
|
||
if (isBlank(this.get('date'))) { | ||
this.get('setDate')(this.get('_date')); | ||
} | ||
} | ||
} | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import Component from 'ember-component'; | ||
import {task, timeout} from 'ember-concurrency'; | ||
import computed, {reads} from 'ember-computed'; | ||
|
||
// TODO: reduce when in testing mode | ||
const SAVE_TIMEOUT_MS = 3000; | ||
|
||
export default Component.extend({ | ||
post: null, | ||
isScheduled: reads('post.isScheduled'), | ||
isSaving: false, | ||
|
||
'data-test-editor-post-status': true, | ||
|
||
_isSaving: false, | ||
|
||
isPublished: computed('post.{isPublished,pastScheduledTime}', function () { | ||
let isPublished = this.get('post.isPublished'); | ||
let pastScheduledTime = this.get('post.pastScheduledTime'); | ||
|
||
return isPublished || pastScheduledTime; | ||
}), | ||
|
||
// isSaving will only be true briefly whilst the post is saving, | ||
// we want to ensure that the "Saving..." message is shown for at least | ||
// a few seconds so that it's noticeable | ||
didReceiveAttrs() { | ||
if (this.get('isSaving')) { | ||
this.get('showSavingMessage').perform(); | ||
} | ||
}, | ||
|
||
showSavingMessage: task(function* () { | ||
this.set('_isSaving', true); | ||
yield timeout(SAVE_TIMEOUT_MS); | ||
this.set('_isSaving', false); | ||
}).drop() | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.