From ec597f02965fd8734c8f94c7c35ec7df2d54873c Mon Sep 17 00:00:00 2001 From: Ryan McCarvill Date: Mon, 24 Oct 2016 23:55:55 +1300 Subject: [PATCH] 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 --- .../app/mixins/editor-base-controller.js | 15 ++++ ghost/admin/app/routes/editor/edit.js | 7 -- ghost/admin/app/styles/layouts/editor.css | 32 ++++++++- ghost/admin/app/styles/patterns/global.css | 1 - ghost/admin/app/templates/editor/edit.hbs | 50 ++++++++----- ghost/admin/package.json | 2 +- ghost/admin/tests/acceptance/editor-test.js | 72 +++++++++---------- 7 files changed, 116 insertions(+), 63 deletions(-) diff --git a/ghost/admin/app/mixins/editor-base-controller.js b/ghost/admin/app/mixins/editor-base-controller.js index a7a81c6fa9..2da71d3510 100644 --- a/ghost/admin/app/mixins/editor-base-controller.js +++ b/ghost/admin/app/mixins/editor-base-controller.js @@ -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(); + } } } }); diff --git a/ghost/admin/app/routes/editor/edit.js b/ghost/admin/app/routes/editor/edit.js index c555485b9b..c74974c1d5 100644 --- a/ghost/admin/app/routes/editor/edit.js +++ b/ghost/admin/app/routes/editor/edit.js @@ -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: { diff --git a/ghost/admin/app/styles/layouts/editor.css b/ghost/admin/app/styles/layouts/editor.css index b11859c349..8abaab53c4 100644 --- a/ghost/admin/app/styles/layouts/editor.css +++ b/ghost/admin/app/styles/layouts/editor.css @@ -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; +} diff --git a/ghost/admin/app/styles/patterns/global.css b/ghost/admin/app/styles/patterns/global.css index 09b138857d..403aed78e4 100644 --- a/ghost/admin/app/styles/patterns/global.css +++ b/ghost/admin/app/styles/patterns/global.css @@ -57,7 +57,6 @@ *:before, *:after { box-sizing: border-box; - user-select: none; } html { diff --git a/ghost/admin/app/templates/editor/edit.hbs b/ghost/admin/app/templates/editor/edit.hbs index 8ab2c101a5..d1719c9a42 100644 --- a/ghost/admin/app/templates/editor/edit.hbs +++ b/ghost/admin/app/templates/editor/edit.hbs @@ -1,13 +1,16 @@
-
- {{#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}} - + +
+
+ {{#if model.isPublished}} + Published + {{else if model.isScheduled}} + Scheduled + {{else}} + Draft {{/if}} + +
- {{ghost-editor - value=(readonly model.scratch) - onChange=(action (mut model.scratch)) - onFirstChange=(action "autoSaveNew") - onTeardown=(action "cancelTimers") - shouldFocusEditor=shouldFocusEditor - apiRoot=apiRoot - assetPath=assetPath - }} +
+
+ {{#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}} + + {{/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 + }} +
+
{{#if showDeletePostModal}} diff --git a/ghost/admin/package.json b/ghost/admin/package.json index dd7c9e9a93..82fc6eecf4 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -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", diff --git a/ghost/admin/tests/acceptance/editor-test.js b/ghost/admin/tests/acceptance/editor-test.js index 486fd2b2d9..189cbe7ecb 100644 --- a/ghost/admin/tests/acceptance/editor-test.js +++ b/ghost/admin/tests/acceptance/editor-test.js @@ -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() { }); }); -}); \ No newline at end of file +});