mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 11:22:19 +03:00
Isolate all markdown editor behaviour into gh-editor
component
no issue - move all existing markdown editor behaviour out of the editor controller and isolate it into a single component that can be swapped out - split the `register/remove` functions of the `shortcuts-route` out into a separate `shortcuts` mixin
This commit is contained in:
parent
9b522457ef
commit
0cbd7d5c68
@ -32,7 +32,7 @@ export default TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, {
|
||||
|
||||
this.setFocus();
|
||||
|
||||
this.sendAction('setEditor', this);
|
||||
this.attrs.setEditor(this);
|
||||
|
||||
run.scheduleOnce('afterRender', this, this.afterRenderEvent);
|
||||
},
|
||||
@ -43,22 +43,6 @@ export default TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable editing in the textarea (used while an upload is in progress)
|
||||
*/
|
||||
disable() {
|
||||
let textarea = this.get('element');
|
||||
textarea.setAttribute('readonly', 'readonly');
|
||||
},
|
||||
|
||||
/**
|
||||
* Reenable editing in the textarea
|
||||
*/
|
||||
enable() {
|
||||
let textarea = this.get('element');
|
||||
textarea.removeAttribute('readonly');
|
||||
},
|
||||
|
||||
actions: {
|
||||
toggleCopyHTMLModal(generatedHTML) {
|
||||
this.attrs.toggleCopyHTMLModal(generatedHTML);
|
||||
|
@ -1,20 +1,25 @@
|
||||
import Ember from 'ember';
|
||||
import ShortcutsMixin from 'ghost/mixins/shortcuts';
|
||||
import imageManager from 'ghost/utils/ed-image-manager';
|
||||
import editorShortcuts from 'ghost/utils/editor-shortcuts';
|
||||
|
||||
const {Component, computed, run} = Ember;
|
||||
const {equal} = computed;
|
||||
|
||||
export default Component.extend({
|
||||
export default Component.extend(ShortcutsMixin, {
|
||||
tagName: 'section',
|
||||
classNames: ['gh-view'],
|
||||
classNames: ['view-container', 'view-editor'],
|
||||
|
||||
activeTab: 'markdown',
|
||||
editor: null,
|
||||
editorDisabled: undefined,
|
||||
editorScrollInfo: null, // updated when gh-ed-editor component scrolls
|
||||
height: null, // updated when markdown is rendered
|
||||
shouldFocusEditor: false,
|
||||
showCopyHTMLModal: false,
|
||||
copyHTMLModalContent: null,
|
||||
|
||||
// updated when gh-ed-editor component scrolls
|
||||
editorScrollInfo: null,
|
||||
// updated when markdown is rendered
|
||||
height: null,
|
||||
activeTab: 'markdown',
|
||||
shortcuts: editorShortcuts,
|
||||
|
||||
markdownActive: equal('activeTab', 'markdown'),
|
||||
previewActive: equal('activeTab', 'preview'),
|
||||
@ -25,8 +30,7 @@ export default Component.extend({
|
||||
// stays in sync
|
||||
scrollPosition: computed('editorScrollInfo', 'height', function () {
|
||||
let scrollInfo = this.get('editorScrollInfo');
|
||||
let $previewContent = this.get('$previewContent');
|
||||
let $previewViewPort = this.get('$previewViewPort');
|
||||
let {$previewContent, $previewViewPort} = this;
|
||||
|
||||
if (!scrollInfo || !$previewContent || !$previewViewPort) {
|
||||
return 0;
|
||||
@ -41,21 +45,23 @@ export default Component.extend({
|
||||
return previewPosition;
|
||||
}),
|
||||
|
||||
scheduleAfterRender() {
|
||||
run.scheduleOnce('afterRender', this, this.afterRenderEvent);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.scheduleAfterRender();
|
||||
this.registerShortcuts();
|
||||
run.scheduleOnce('afterRender', this, this._cacheElements);
|
||||
},
|
||||
|
||||
afterRenderEvent() {
|
||||
let $previewViewPort = this.$('.js-entry-preview-content');
|
||||
willDestroyElement() {
|
||||
if (this.attrs.onTeardown) {
|
||||
this.attrs.onTeardown();
|
||||
}
|
||||
this.removeShortcuts();
|
||||
},
|
||||
|
||||
_cacheElements() {
|
||||
// cache these elements for use in other methods
|
||||
this.set('$previewViewPort', $previewViewPort);
|
||||
this.set('$previewContent', this.$('.js-rendered-markdown'));
|
||||
this.$previewViewPort = this.$('.js-entry-preview-content');
|
||||
this.$previewContent = this.$('.js-rendered-markdown');
|
||||
},
|
||||
|
||||
actions: {
|
||||
@ -63,6 +69,52 @@ export default Component.extend({
|
||||
this.set('activeTab', tab);
|
||||
},
|
||||
|
||||
updateScrollInfo(scrollInfo) {
|
||||
this.set('editorScrollInfo', scrollInfo);
|
||||
},
|
||||
|
||||
updateHeight(height) {
|
||||
this.set('height', height);
|
||||
},
|
||||
|
||||
// set from a `sendAction` on the gh-ed-editor component,
|
||||
// so that we get a reference for handling uploads.
|
||||
setEditor(editor) {
|
||||
this.set('editor', editor);
|
||||
},
|
||||
|
||||
disableEditor() {
|
||||
this.set('editorDisabled', true);
|
||||
},
|
||||
|
||||
enableEditor() {
|
||||
this.set('editorDisabled', undefined);
|
||||
},
|
||||
|
||||
// The actual functionality is implemented in utils/ed-editor-shortcuts
|
||||
editorShortcut(options) {
|
||||
if (this.editor.$().is(':focus')) {
|
||||
this.editor.shortcut(options.type);
|
||||
}
|
||||
},
|
||||
|
||||
// Match the uploaded file to a line in the editor, and update that line with a path reference
|
||||
// ensuring that everything ends up in the correct place and format.
|
||||
handleImgUpload(e, resultSrc) {
|
||||
let editor = this.get('editor');
|
||||
let editorValue = editor.getValue();
|
||||
let replacement = imageManager.getSrcRange(editorValue, e.target);
|
||||
let cursorPosition;
|
||||
|
||||
if (replacement) {
|
||||
cursorPosition = replacement.start + resultSrc.length + 1;
|
||||
if (replacement.needsParens) {
|
||||
resultSrc = `(${resultSrc})`;
|
||||
}
|
||||
editor.replaceSelection(resultSrc, replacement.start, replacement.end, cursorPosition);
|
||||
}
|
||||
},
|
||||
|
||||
toggleCopyHTMLModal(generatedHTML) {
|
||||
this.set('copyHTMLModalContent', generatedHTML);
|
||||
this.toggleProperty('showCopyHTMLModal');
|
||||
|
@ -61,7 +61,7 @@ export default Mixin.create({
|
||||
*/
|
||||
scrollHandler() {
|
||||
this.set('scrollThrottle', run.throttle(this, () => {
|
||||
this.sendAction('updateScrollInfo', this.getScrollInfo());
|
||||
this.attrs.updateScrollInfo(this.getScrollInfo());
|
||||
}, 10));
|
||||
},
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Ember from 'ember';
|
||||
import PostModel from 'ghost/models/post';
|
||||
import boundOneWay from 'ghost/utils/bound-one-way';
|
||||
import imageManager from 'ghost/utils/ed-image-manager';
|
||||
|
||||
const {Mixin, RSVP, computed, inject, observer, run} = Ember;
|
||||
const {alias} = computed;
|
||||
@ -17,7 +16,6 @@ PostModel.eachAttribute(function (name) {
|
||||
export default Mixin.create({
|
||||
_autoSaveId: null,
|
||||
_timedSaveId: null,
|
||||
editor: null,
|
||||
submitting: false,
|
||||
|
||||
showLeaveEditorModal: false,
|
||||
@ -126,7 +124,7 @@ export default Mixin.create({
|
||||
let markdown = model.get('markdown');
|
||||
let title = model.get('title');
|
||||
let titleScratch = model.get('titleScratch');
|
||||
let scratch = this.get('editor').getValue();
|
||||
let scratch = this.get('model.scratch');
|
||||
let changedAttributes;
|
||||
|
||||
if (!this.tagNamesEqual()) {
|
||||
@ -253,31 +251,9 @@ export default Mixin.create({
|
||||
},
|
||||
|
||||
actions: {
|
||||
save(options) {
|
||||
let prevStatus = this.get('model.status');
|
||||
let isNew = this.get('model.isNew');
|
||||
cancelTimers() {
|
||||
let autoSaveId = this._autoSaveId;
|
||||
let timedSaveId = this._timedSaveId;
|
||||
let psmController = this.get('postSettingsMenuController');
|
||||
let promise, status;
|
||||
|
||||
options = options || {};
|
||||
|
||||
// when navigating quickly between pages autoSave will occasionally
|
||||
// try to run after the editor has been torn down so bail out here
|
||||
// before we throw errors
|
||||
if (!this.get('editor').$()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
this.toggleProperty('submitting');
|
||||
|
||||
if (options.backgroundSave) {
|
||||
// do not allow a post's status to be set to published by a background save
|
||||
status = 'draft';
|
||||
} else {
|
||||
status = this.get('willPublish') ? 'published' : 'draft';
|
||||
}
|
||||
|
||||
if (autoSaveId) {
|
||||
run.cancel(autoSaveId);
|
||||
@ -288,10 +264,30 @@ export default Mixin.create({
|
||||
run.cancel(timedSaveId);
|
||||
this._timedSaveId = null;
|
||||
}
|
||||
},
|
||||
|
||||
save(options) {
|
||||
let prevStatus = this.get('model.status');
|
||||
let isNew = this.get('model.isNew');
|
||||
let psmController = this.get('postSettingsMenuController');
|
||||
let promise, status;
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.toggleProperty('submitting');
|
||||
|
||||
if (options.backgroundSave) {
|
||||
// do not allow a post's status to be set to published by a background save
|
||||
status = 'draft';
|
||||
} else {
|
||||
status = this.get('willPublish') ? 'published' : 'draft';
|
||||
}
|
||||
|
||||
this.send('cancelTimers');
|
||||
|
||||
// Set the properties that are indirected
|
||||
// set markdown equal to what's in the editor, minus the image markers.
|
||||
this.set('model.markdown', this.get('editor').getValue());
|
||||
this.set('model.markdown', this.get('model.scratch'));
|
||||
this.set('model.status', status);
|
||||
|
||||
// Set a default title
|
||||
@ -345,53 +341,12 @@ export default Mixin.create({
|
||||
}
|
||||
},
|
||||
|
||||
// set from a `sendAction` on the gh-ed-editor component,
|
||||
// so that we get a reference for handling uploads.
|
||||
setEditor(editor) {
|
||||
this.set('editor', editor);
|
||||
},
|
||||
|
||||
// fired from the gh-ed-preview component when an image upload starts
|
||||
disableEditor() {
|
||||
this.get('editor').disable();
|
||||
},
|
||||
|
||||
// fired from the gh-ed-preview component when an image upload finishes
|
||||
enableEditor() {
|
||||
this.get('editor').enable();
|
||||
},
|
||||
|
||||
// Match the uploaded file to a line in the editor, and update that line with a path reference
|
||||
// ensuring that everything ends up in the correct place and format.
|
||||
handleImgUpload(e, resultSrc) {
|
||||
let editor = this.get('editor');
|
||||
let editorValue = editor.getValue();
|
||||
let replacement = imageManager.getSrcRange(editorValue, e.target);
|
||||
let cursorPosition;
|
||||
|
||||
if (replacement) {
|
||||
cursorPosition = replacement.start + resultSrc.length + 1;
|
||||
if (replacement.needsParens) {
|
||||
resultSrc = `(${resultSrc})`;
|
||||
}
|
||||
editor.replaceSelection(resultSrc, replacement.start, replacement.end, cursorPosition);
|
||||
}
|
||||
},
|
||||
|
||||
autoSaveNew() {
|
||||
if (this.get('model.isNew')) {
|
||||
this.send('save', {silent: true, backgroundSave: true});
|
||||
}
|
||||
},
|
||||
|
||||
updateEditorScrollInfo(scrollInfo) {
|
||||
this.set('editorScrollInfo', scrollInfo);
|
||||
},
|
||||
|
||||
updateHeight(height) {
|
||||
this.set('height', height);
|
||||
},
|
||||
|
||||
toggleLeaveEditorModal(transition) {
|
||||
this.set('leaveEditorTransition', transition);
|
||||
this.toggleProperty('showLeaveEditorModal');
|
||||
|
@ -1,13 +1,19 @@
|
||||
import Ember from 'ember';
|
||||
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
import editorShortcuts from 'ghost/utils/editor-shortcuts';
|
||||
import ctrlOrCmd from 'ghost/utils/ctrl-or-cmd';
|
||||
|
||||
const {Mixin, RSVP, run} = Ember;
|
||||
|
||||
let generalShortcuts = {};
|
||||
generalShortcuts[`${ctrlOrCmd}+alt+p`] = 'publish';
|
||||
generalShortcuts['alt+shift+z'] = 'toggleZenMode';
|
||||
|
||||
export default Mixin.create(styleBody, ShortcutsRoute, {
|
||||
classNames: ['editor'],
|
||||
|
||||
shortcuts: generalShortcuts,
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.get('controller').send('save');
|
||||
@ -24,14 +30,6 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
|
||||
Ember.$('body').toggleClass('zen');
|
||||
},
|
||||
|
||||
// The actual functionality is implemented in utils/ed-editor-shortcuts
|
||||
editorShortcut(options) {
|
||||
// Only fire editor shortcuts when the editor has focus.
|
||||
if (this.get('controller.editor').$().is(':focus')) {
|
||||
this.get('controller.editor').shortcut(options.type);
|
||||
}
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
let controller = this.get('controller');
|
||||
let scratch = controller.get('model.scratch');
|
||||
@ -97,8 +95,6 @@ export default Mixin.create(styleBody, ShortcutsRoute, {
|
||||
});
|
||||
},
|
||||
|
||||
shortcuts: editorShortcuts,
|
||||
|
||||
attachModelHooks(controller, model) {
|
||||
// this will allow us to track when the model is saved and update the controller
|
||||
// so that we can be sure controller.hasDirtyAttributes is correct, without having to update the
|
||||
|
@ -1,16 +1,8 @@
|
||||
/* global key */
|
||||
import Ember from 'ember';
|
||||
import ShortcutsMixin from 'ghost/mixins/shortcuts';
|
||||
|
||||
const {Mixin, run, typeOf} = Ember;
|
||||
const {Mixin} = Ember;
|
||||
|
||||
// Configure KeyMaster to respond to all shortcuts,
|
||||
// even inside of
|
||||
// input, textarea, and select.
|
||||
key.filter = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
key.setScope('default');
|
||||
/**
|
||||
* Only routes can implement shortcuts.
|
||||
* If you need to trigger actions on the controller,
|
||||
@ -44,39 +36,7 @@ key.setScope('default');
|
||||
* To have all your shortcut work in all scopes, give it the scope "all".
|
||||
* Find out more at the keymaster docs
|
||||
*/
|
||||
export default Mixin.create({
|
||||
registerShortcuts() {
|
||||
let shortcuts = this.get('shortcuts');
|
||||
|
||||
Object.keys(shortcuts).forEach((shortcut) => {
|
||||
let scope = shortcuts[shortcut].scope || 'default';
|
||||
let action = shortcuts[shortcut];
|
||||
let options;
|
||||
|
||||
if (typeOf(action) !== 'string') {
|
||||
options = action.options;
|
||||
action = action.action;
|
||||
}
|
||||
|
||||
key(shortcut, scope, (event) => {
|
||||
// stop things like ctrl+s from actually opening a save dialogue
|
||||
event.preventDefault();
|
||||
run(this, function () {
|
||||
this.send(action, options);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeShortcuts() {
|
||||
let shortcuts = this.get('shortcuts');
|
||||
|
||||
Object.keys(shortcuts).forEach((shortcut) => {
|
||||
let scope = shortcuts[shortcut].scope || 'default';
|
||||
key.unbind(shortcut, scope);
|
||||
});
|
||||
},
|
||||
|
||||
export default Mixin.create(ShortcutsMixin, {
|
||||
activate() {
|
||||
this._super(...arguments);
|
||||
this.registerShortcuts();
|
||||
|
79
ghost/admin/app/mixins/shortcuts.js
Normal file
79
ghost/admin/app/mixins/shortcuts.js
Normal file
@ -0,0 +1,79 @@
|
||||
/* global key */
|
||||
import Ember from 'ember';
|
||||
|
||||
const {Mixin, run, typeOf} = Ember;
|
||||
|
||||
// Configure KeyMaster to respond to all shortcuts,
|
||||
// even inside of
|
||||
// input, textarea, and select.
|
||||
key.filter = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
key.setScope('default');
|
||||
/**
|
||||
* Only routes can implement shortcuts.
|
||||
* If you need to trigger actions on the controller,
|
||||
* simply call them with `this.get('controller').send('action')`.
|
||||
*
|
||||
* To implement shortcuts, add this mixin to your `extend()`,
|
||||
* and implement a `shortcuts` hash.
|
||||
* In this hash, keys are shortcut combinations and values are route action names.
|
||||
* (see [keymaster docs](https://github.com/madrobby/keymaster/blob/master/README.markdown)),
|
||||
*
|
||||
* ```javascript
|
||||
* shortcuts: {
|
||||
* 'ctrl+s, command+s': 'save',
|
||||
* 'ctrl+alt+z': 'toggleZenMode'
|
||||
* }
|
||||
* ```
|
||||
* For more complex actions, shortcuts can instead have their value
|
||||
* be an object like {action, options}
|
||||
* ```javascript
|
||||
* shortcuts: {
|
||||
* 'ctrl+k': {action: 'markdownShortcut', options: 'createLink'}
|
||||
* }
|
||||
* ```
|
||||
* You can set the scope of your shortcut by passing a scope property.
|
||||
* ```javascript
|
||||
* shortcuts : {
|
||||
* 'enter': {action : 'confirmModal', scope: 'modal'}
|
||||
* }
|
||||
* ```
|
||||
* If you don't specify a scope, we use a default scope called "default".
|
||||
* To have all your shortcut work in all scopes, give it the scope "all".
|
||||
* Find out more at the keymaster docs
|
||||
*/
|
||||
export default Mixin.create({
|
||||
registerShortcuts() {
|
||||
let shortcuts = this.get('shortcuts');
|
||||
|
||||
Object.keys(shortcuts).forEach((shortcut) => {
|
||||
let scope = shortcuts[shortcut].scope || 'default';
|
||||
let action = shortcuts[shortcut];
|
||||
let options;
|
||||
|
||||
if (typeOf(action) !== 'string') {
|
||||
options = action.options;
|
||||
action = action.action;
|
||||
}
|
||||
|
||||
key(shortcut, scope, (event) => {
|
||||
// stop things like ctrl+s from actually opening a save dialogue
|
||||
event.preventDefault();
|
||||
run(this, function () {
|
||||
this.send(action, options);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeShortcuts() {
|
||||
let shortcuts = this.get('shortcuts');
|
||||
|
||||
Object.keys(shortcuts).forEach((shortcut) => {
|
||||
let scope = shortcuts[shortcut].scope || 'default';
|
||||
key.unbind(shortcut, scope);
|
||||
});
|
||||
}
|
||||
});
|
@ -1,4 +1,46 @@
|
||||
{{yield this (action 'toggleCopyHTMLModal')}}
|
||||
<section class="entry-markdown js-entry-markdown {{if markdownActive 'active'}}">
|
||||
<header class="floatingheader">
|
||||
<span class="desktop-tabs"><a class="markdown-help-label" href="" title="Markdown Help" {{action (route-action "toggleMarkdownHelpModal")}}>Markdown</a></span>
|
||||
<span class="mobile-tabs">
|
||||
<a href="#" {{action 'selectTab' 'markdown'}} class="{{if markdownActive 'active'}}">Markdown</a>
|
||||
<a href="#" {{action 'selectTab' 'preview'}} class="{{if previewActive 'active'}}">Preview</a>
|
||||
</span>
|
||||
<a class="markdown-help-icon" href="" title="Markdown Help" {{action (route-action "toggleMarkdownHelpModal")}}><i class="icon-markdown"></i></a>
|
||||
</header>
|
||||
<section id="entry-markdown-content" class="entry-markdown-content">
|
||||
{{gh-ed-editor classNames="markdown-editor js-markdown-editor"
|
||||
tabindex="1"
|
||||
spellcheck="true"
|
||||
value=value
|
||||
setEditor=(action "setEditor")
|
||||
updateScrollInfo=(action "updateScrollInfo")
|
||||
toggleCopyHTMLModal=(action "toggleCopyHTMLModal")
|
||||
onFocusIn=editorFocused
|
||||
height=height
|
||||
focus=shouldFocusEditor
|
||||
readonly=editorDisabled}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="entry-preview js-entry-preview {{if previewActive 'active'}}">
|
||||
<header class="floatingheader">
|
||||
<span class="desktop-tabs"><a target="_blank" href="{{previewUrl}}">Preview</a></span>
|
||||
<span class="mobile-tabs">
|
||||
<a href="#" {{action 'selectTab' 'markdown'}} class="{{if markdownActive 'active'}}">Markdown</a>
|
||||
<a href="#" {{action 'selectTab' 'preview'}} class="{{if previewActive 'active'}}">Preview</a>
|
||||
</span>
|
||||
<span class="entry-word-count">{{gh-count-words value}}</span>
|
||||
</header>
|
||||
<section class="entry-preview-content js-entry-preview-content">
|
||||
{{gh-ed-preview classNames="rendered-markdown js-rendered-markdown"
|
||||
markdown=value
|
||||
scrollPosition=scrollPosition
|
||||
updateHeight=(action "updateHeight")
|
||||
uploadStarted=(action "disableEditor")
|
||||
uploadFinished=(action "enableEditor")
|
||||
uploadSuccess=(action "handleImgUpload")}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{{#if showCopyHTMLModal}}
|
||||
{{gh-fullscreen-modal "copy-html"
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{#gh-editor editorScrollInfo=editorScrollInfo as |ghEditor toggleCopyHTMLModal|}}
|
||||
<section class="gh-view">
|
||||
<header class="view-header">
|
||||
{{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}}
|
||||
{{gh-trim-focus-input type="text" id="entry-title" placeholder="Your Post Title" value=model.titleScratch tabindex="1" focus=shouldFocusTitle}}
|
||||
@ -20,47 +20,11 @@
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="view-container view-editor">
|
||||
<section class="entry-markdown js-entry-markdown {{if ghEditor.markdownActive 'active'}}">
|
||||
<header class="floatingheader">
|
||||
<span class="desktop-tabs"><a class="markdown-help-label" href="" title="Markdown Help" {{action "toggleMarkdownHelpModal"}}>Markdown</a></span>
|
||||
<span class="mobile-tabs">
|
||||
<a href="#" {{action 'selectTab' 'markdown' target=ghEditor}} class="{{if ghEditor.markdownActive 'active'}}">Markdown</a>
|
||||
<a href="#" {{action 'selectTab' 'preview' target=ghEditor}} class="{{if ghEditor.previewActive 'active'}}">Preview</a>
|
||||
</span>
|
||||
<a class="markdown-help-icon" href="" title="Markdown Help" {{action "toggleMarkdownHelpModal"}}><i class="icon-markdown"></i></a>
|
||||
</header>
|
||||
<section id="entry-markdown-content" class="entry-markdown-content">
|
||||
{{gh-ed-editor classNames="markdown-editor js-markdown-editor"
|
||||
tabindex="1"
|
||||
spellcheck="true"
|
||||
value=model.scratch
|
||||
setEditor="setEditor"
|
||||
updateScrollInfo="updateEditorScrollInfo"
|
||||
toggleCopyHTMLModal=toggleCopyHTMLModal
|
||||
onFocusIn="autoSaveNew"
|
||||
height=height
|
||||
focus=shouldFocusEditor}}
|
||||
{{gh-editor value=model.scratch
|
||||
shouldFocusEditor=shouldFocusEditor
|
||||
editorFocused=(action "autoSaveNew")
|
||||
onTeardown=(action "cancelTimers")}}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="entry-preview js-entry-preview {{if ghEditor.previewActive 'active'}}">
|
||||
<header class="floatingheader">
|
||||
<span class="desktop-tabs"><a target="_blank" href="{{model.previewUrl}}">Preview</a></span>
|
||||
<span class="mobile-tabs">
|
||||
<a href="#" {{action 'selectTab' 'markdown' target=ghEditor}} class="{{if ghEditor.markdownActive 'active'}}">Markdown</a>
|
||||
<a href="#" {{action 'selectTab' 'preview' target=ghEditor}} class="{{if ghEditor.previewActive 'active'}}">Preview</a>
|
||||
</span>
|
||||
<span class="entry-word-count">{{gh-count-words model.scratch}}</span>
|
||||
</header>
|
||||
<section class="entry-preview-content js-entry-preview-content">
|
||||
{{gh-ed-preview classNames="rendered-markdown js-rendered-markdown"
|
||||
markdown=model.scratch scrollPosition=ghEditor.scrollPosition updateHeight="updateHeight"
|
||||
uploadStarted="disableEditor" uploadFinished="enableEditor" uploadSuccess="handleImgUpload"}}
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
{{/gh-editor}}
|
||||
|
||||
{{#if showDeletePostModal}}
|
||||
{{gh-fullscreen-modal "delete-post"
|
||||
|
@ -1,14 +1,10 @@
|
||||
// # Editor shortcuts
|
||||
// Loaded by EditorBaseRoute, which is a shortcuts route
|
||||
// Loaded by gh-editor component
|
||||
// This map is used to ensure the right action is called by each shortcut
|
||||
import ctrlOrCmd from 'ghost/utils/ctrl-or-cmd';
|
||||
|
||||
let shortcuts = {};
|
||||
|
||||
// General editor shortcuts
|
||||
shortcuts[`${ctrlOrCmd}+alt+p`] = 'publish';
|
||||
shortcuts['alt+shift+z'] = 'toggleZenMode';
|
||||
|
||||
// Markdown Shortcuts
|
||||
|
||||
// Text
|
||||
|
@ -9,9 +9,15 @@ describeComponent(
|
||||
'gh-editor',
|
||||
'Unit: Component: gh-editor',
|
||||
{
|
||||
unit: true
|
||||
unit: true,
|
||||
// specify the other units that are required for this test
|
||||
// needs: ['component:foo', 'helper:bar']
|
||||
needs: [
|
||||
'component:gh-ed-editor',
|
||||
'component:gh-ed-preview',
|
||||
'helper:gh-count-words',
|
||||
'helper:route-action',
|
||||
'service:notifications'
|
||||
]
|
||||
},
|
||||
function () {
|
||||
it('renders', function () {
|
||||
|
Loading…
Reference in New Issue
Block a user