New editor layout (#355)

- the title is now part of the content
- new ways to navigate from the title to the content
- the new editor contains updated toolbar behavior
- the new editor contains markdown like commands
This commit is contained in:
Ryan McCarvill 2016-10-24 23:55:55 +13:00 committed by Kevin Ansfield
parent cef6f2dc0e
commit 7872faea87
7 changed files with 116 additions and 63 deletions

View File

@ -16,6 +16,8 @@ import PostModel from 'ghost-admin/models/post';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import {isVersionMismatchError} from 'ghost-admin/services/ajax';
import {isInvalidError} from 'ember-ajax/errors';
import $ from 'jquery';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
const {resolve} = RSVP;
@ -40,6 +42,12 @@ export default Mixin.create({
clock: injectService(),
slugGenerator: injectService(),
cards: [], // for apps
atoms: [], // for apps
toolbar: [], // for apps
apiRoot: ghostPaths().apiRoot,
assetPath: ghostPaths().assetRoot,
init() {
this._super(...arguments);
window.onbeforeunload = () => {
@ -543,6 +551,13 @@ export default Mixin.create({
toggleReAuthenticateModal() {
this.toggleProperty('showReAuthenticateModal');
},
titleKeyDown(event) {
if (event.keyCode === 13 || event.keyCode === 40) {
// if the enter key or down key are pressed then focus on the editor
$('.__mobiledoc-editor').focus();
}
}
}
});

View File

@ -3,7 +3,6 @@ import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import base from 'ghost-admin/mixins/editor-base-route';
import isNumber from 'ghost-admin/utils/isNumber';
import isFinite from 'ghost-admin/utils/isFinite';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
export default AuthenticatedRoute.extend(base, {
titleToken: 'Editor',
@ -53,13 +52,7 @@ export default AuthenticatedRoute.extend(base, {
setupController(controller) {
this._super(...arguments);
controller.set('shouldFocusEditor', this.get('_transitionedFromNew'));
controller.set('cards' , []);
controller.set('atoms' , []);
controller.set('toolbar' , []);
controller.set('apiRoot', ghostPaths().apiRoot);
controller.set('assetPath', ghostPaths().assetRoot);
},
actions: {

View File

@ -7,6 +7,7 @@
.gh-editor-title {
flex-grow: 1;
margin-bottom: 2vw;
}
.gh-editor-title input {
@ -16,7 +17,7 @@
border: 0;
background: transparent;
color: var(--darkgrey);
font-size: 2.6rem;
font-size: 3.2rem;
font-weight: bold;
}
@ -400,3 +401,32 @@
.modal-markdown-help-table th {
text-align: left;
}
/* NEW editor
/* ---------------------------------------------------------- */
.gh-editor-header {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 100;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 65px;
}
.gh-editor-container {
position: relative;
overflow-y: auto;
padding: 100px 4vw 40px;
width: 100%;
}
.gh-editor-inner {
margin: 0 auto;
max-width: 700px;
}

View File

@ -57,7 +57,6 @@
*:before,
*:after {
box-sizing: border-box;
user-select: none;
}
html {

View File

@ -1,13 +1,16 @@
<section class="gh-view">
<header class="view-header">
{{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}}
{{gh-trim-focus-input model.titleScratch type="text" id="entry-title" placeholder="Your Post Title" tabindex="1" shouldFocus=shouldFocusTitle focus-out="updateTitle" update=(action (perform updateTitle))}}
{{/gh-view-title}}
{{#if scheduleCountdown}}
<time datetime="{{post.publishedAtUTC}}" class="gh-notification gh-notification-schedule">
Post will be published {{scheduleCountdown}}.
</time>
<header class="gh-editor-header">
<div class="gh-editor-status">
{{#if model.isPublished}}
Published
{{else if model.isScheduled}}
Scheduled
{{else}}
Draft
{{/if}}
</div>
<section class="view-actions">
<button type="button" class="post-settings" title="Post Settings" {{action "openSettingsMenu"}}>
<i class="icon-settings"></i>
@ -29,15 +32,28 @@
}}
</section>
</header>
{{ghost-editor
value=(readonly model.scratch)
onChange=(action (mut model.scratch))
onFirstChange=(action "autoSaveNew")
onTeardown=(action "cancelTimers")
shouldFocusEditor=shouldFocusEditor
apiRoot=apiRoot
assetPath=assetPath
}}
<div class="gh-editor-container">
<div class="gh-editor-inner">
{{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}}
{{gh-trim-focus-input model.titleScratch type="text" id="entry-title" placeholder="Your Post Title" tabindex="1" shouldFocus=shouldFocusTitle focus-out="updateTitle" update=(action (perform updateTitle)) keyDown=(action "titleKeyDown")}}
{{/gh-view-title}}
{{#if scheduleCountdown}}
<time datetime="{{post.publishedAtUTC}}" class="gh-notification gh-notification-schedule">
Post will be published {{scheduleCountdown}}.
</time>
{{/if}}
{{ghost-editor
value=(readonly model.scratch)
onChange=(action (mut model.scratch))
onFirstChange=(action "autoSaveNew")
onTeardown=(action "cancelTimers")
shouldFocusEditor=shouldFocusEditor
apiRoot=apiRoot
assetPath=assetPath
tabindex=2
}}
</div>
</div>
</section>
{{#if showDeletePostModal}}

View File

@ -74,7 +74,7 @@
"ember-wormhole": "0.5.0",
"emberx-file-input": "1.1.0",
"fs-extra": "0.30.0",
"ghost-editor": "0.0.11",
"ghost-editor": "0.0.14",
"glob": "7.1.1",
"grunt": "1.0.1",
"grunt-bg-shell": "2.3.3",

View File

@ -110,7 +110,7 @@ describe('Acceptance: Editor', function() {
fillIn('input[name="post-setting-date"]', '10 May 16 @ 10:00');
triggerEvent('input[name="post-setting-date"]', 'blur');
// saving
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'date after saving')
@ -128,9 +128,9 @@ describe('Acceptance: Editor', function() {
// checking the flow of the saving button for a draft
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft')
.to.be.true;
@ -142,21 +142,21 @@ describe('Acceptance: Editor', function() {
andThen(() => {
expect(find('.post-save-publish').hasClass('active'), 'highlights the selected active button state')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from draft to published')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from draft to published')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button after click on \'publish now\'')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button after click on \'publish now\'')
.to.equal('Publish Now');
});
// Publish the post
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button after publishing')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button after publishing')
.to.equal('Update Post');
expect(find('.post-save-publish').hasClass('active'), 'highlights the default active button state for a published post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
});
@ -178,7 +178,7 @@ describe('Acceptance: Editor', function() {
});
// saving
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'date value restored')
@ -189,7 +189,7 @@ describe('Acceptance: Editor', function() {
fillIn('input[name="post-setting-date"]', '10 May 16 @ 10:00');
triggerEvent('input[name="post-setting-date"]', 'blur');
// saving
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'new date after saving')
@ -210,7 +210,7 @@ describe('Acceptance: Editor', function() {
triggerEvent('#activeTimezone', 'change');
// save the settings
click('.view-header .btn.btn-blue');
click('.btn.btn-blue');
andThen(() => {
expect(find('#activeTimezone option:selected').text().trim(), 'new timezone after saving')
@ -242,21 +242,21 @@ describe('Acceptance: Editor', function() {
andThen(() => {
expect(find('.post-save-draft').hasClass('active'), 'highlights the active button state for a draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from published to draft')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from published to draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for post to unpublish')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for post to unpublish')
.to.equal('Unpublish');
});
// Unpublish the post
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for draft')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for draft')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
});
@ -287,24 +287,24 @@ describe('Acceptance: Editor', function() {
andThen(() => {
expect(find('.post-save-schedule').hasClass('active'), 'highlights the active button state for a draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from published to draft')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from published to draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for post to schedule')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for post to schedule')
.to.equal('Schedule Post');
});
// click on schedule post and save
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
// Dropdown menu should be 'Update Post' and 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post')
.to.equal('Update Post');
expect(find('.post-save-schedule').hasClass('active'), 'highlights the default active button state for a scheduled post')
.to.be.true;
expect(find('.post-save-draft').text().trim(), 'not active option should say \'Unschedule\'')
.to.equal('Unschedule');
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
@ -315,23 +315,23 @@ describe('Acceptance: Editor', function() {
click('.post-save-draft a');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button to unscheduled post')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button to unscheduled post')
.to.equal('Unschedule');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a scheduled post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
.to.be.true;
});
// click on unschedule post and save
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for a draft')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for a draft')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
.to.be.false;
// expect no countdown notification after unscheduling
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
@ -376,7 +376,7 @@ describe('Acceptance: Editor', function() {
fillIn('input[name="post-setting-date"]', plusTenMin);
triggerEvent('input[name="post-setting-date"]', 'blur');
click('.post-save-schedule a');
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(
@ -405,7 +405,7 @@ describe('Acceptance: Editor', function() {
// Test title validation
fillIn('input[id="entry-title"]', Array(160).join('a'));
triggerEvent('input[id="entry-title"]', 'blur');
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(
@ -435,13 +435,13 @@ describe('Acceptance: Editor', function() {
expect(find('input[name="post-setting-date"]').val(), 'scheduled date')
.to.equal(compareDate);
// Dropdown menu should be 'Update Post' and 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post')
.to.equal('Update Post');
expect(find('.post-save-schedule').hasClass('active'), 'highlights the default active button state for a scheduled post')
.to.be.true;
expect(find('.post-save-draft').text().trim(), 'not active option should say \'Unschedule\'')
.to.equal('Unschedule');
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
@ -461,7 +461,7 @@ describe('Acceptance: Editor', function() {
andThen(() => {
// Save button should say 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post in status freeze mode')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post in status freeze mode')
.to.equal('Unschedule');
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
@ -488,7 +488,7 @@ describe('Acceptance: Editor', function() {
andThen(() => {
// Save button should say 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post in status freeze mode')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post in status freeze mode')
.to.equal('Unschedule');
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
@ -499,16 +499,16 @@ describe('Acceptance: Editor', function() {
});
// click on Unschedule
click('.view-header .btn.btn-sm.js-publish-button');
click('.btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.markdown-editor').val(), 'changed text in markdown editor')
.to.equal('Let\'s make some markdown changes');
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for a draft')
expect(find('.btn.btn-sm.js-publish-button').text().trim(), 'text in save button for a draft')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
expect(find('.btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
.to.be.false;
// expect no countdown notification after unscheduling
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
@ -519,4 +519,4 @@ describe('Acceptance: Editor', function() {
});
});
});
});