Ghost/ghost/admin/app/components/gh-cm-editor.js
Ryan McCarvill 5724a94fbc improved card selection behaviour (#608)
Refs: https://github.com/TryGhost/Ghost/issues/8191
Refs: https://github.com/TryGhost/Ghost/issues/8194

Changes the selection behaviour of mobiledoc-cards:
If you navigate to a card with a keyboard or click on the new editor toolbar it "hard selects".
If you click into the body of a card to edit it it "soft selects".

When a card is "hard selected" you can navigate out of the card and to the previous or following blocks within the mobiledoc with the keyboard, you can delete the current card with the backspace or delete button, and you can create a new block following the card with the enter key.

When a card is soft selected it is simply displayed as selected and allows the user to edit content within the card.

New card toolbar:
Allows a user to delete the card, save the card, and "hard select" a card.

New title behaviour:
Pressing the enter key within the title "splits" the title at the cursor point, if multiple characters are selected they are first deleted, and creates a new paragraph at the top of the document with the trailing characters after the split.

gh-cm-editor updates:
Adds an on-focus event to gh-cm-editor
2017-03-30 15:29:08 +01:00

84 lines
2.4 KiB
JavaScript

/* global CodeMirror */
import Component from 'ember-component';
import run, {bind, scheduleOnce} from 'ember-runloop';
import injectService from 'ember-service/inject';
import RSVP from 'rsvp';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import {InvokeActionMixin} from 'ember-invoke-action';
const CmEditorComponent = Component.extend(InvokeActionMixin, {
classNameBindings: ['isFocused:focused'],
_value: boundOneWay('value'), // make sure a value exists
isFocused: false,
// options for the editor
lineNumbers: true,
indentUnit: 4,
mode: 'htmlmixed',
theme: 'xq-light',
_editor: null, // reference to CodeMirror editor
lazyLoader: injectService(),
didInsertElement() {
this._super(...arguments);
let loader = this.get('lazyLoader');
RSVP.all([
loader.loadStyle('codemirror', 'assets/codemirror/codemirror.css'),
loader.loadScript('codemirror', 'assets/codemirror/codemirror.js')
]).then(() => {
scheduleOnce('afterRender', this, function () {
this._initCodeMirror();
});
});
},
_initCodeMirror() {
let options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme');
let editor = new CodeMirror(this.element, options);
editor.getDoc().setValue(this.get('_value'));
// events
editor.on('focus', () => {
run(this, function() {
this.set('isFocused', true);
this.invokeAction('focus-in', editor.getDoc().getValue());
});
});
editor.on('blur', bind(this, 'set', 'isFocused', false));
editor.on('change', () => {
run(this, function () {
this.invokeAction('update', editor.getDoc().getValue());
});
});
this._editor = editor;
},
willDestroyElement() {
this._super(...arguments);
// Ensure the editor exists before trying to destroy it. This fixes
// an error that occurs if codemirror hasn't finished loading before
// the component is destroyed.
if (this._editor) {
let editor = this._editor.getWrapperElement();
editor.parentNode.removeChild(editor);
this._editor = null;
}
}
});
CmEditorComponent.reopenClass({
positionalParams: ['value']
});
export default CmEditorComponent;