mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
Merge pull request #3022 from novaugust/codemirror-shortcuts
Implement Markdown Shortcuts
This commit is contained in:
commit
42c9a272a7
@ -2,6 +2,7 @@
|
||||
|
||||
import MarkerManager from 'ghost/mixins/marker-manager';
|
||||
import setScrollClassName from 'ghost/utils/set-scroll-classname';
|
||||
import 'ghost/utils/codemirror-shortcuts';
|
||||
|
||||
var onChangeHandler = function (cm, changeObj) {
|
||||
var line,
|
||||
|
@ -1,16 +1,5 @@
|
||||
import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
|
||||
import MarkerManager from 'ghost/mixins/marker-manager';
|
||||
|
||||
var EditorEditController = Ember.ObjectController.extend(EditorControllerMixin, MarkerManager, {
|
||||
init: function () {
|
||||
var self = this;
|
||||
|
||||
this._super();
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
return self.get('isDirty') ? self.unloadDirtyMessage() : null;
|
||||
};
|
||||
}
|
||||
});
|
||||
var EditorEditController = Ember.ObjectController.extend(EditorControllerMixin);
|
||||
|
||||
export default EditorEditController;
|
||||
|
@ -1,17 +1,6 @@
|
||||
import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
|
||||
import MarkerManager from 'ghost/mixins/marker-manager';
|
||||
|
||||
var EditorNewController = Ember.ObjectController.extend(EditorControllerMixin, MarkerManager, {
|
||||
init: function () {
|
||||
var self = this;
|
||||
|
||||
this._super();
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
return self.get('isDirty') ? self.unloadDirtyMessage() : null;
|
||||
};
|
||||
},
|
||||
|
||||
var EditorNewController = Ember.ObjectController.extend(EditorControllerMixin, {
|
||||
actions: {
|
||||
/**
|
||||
* Redirect to editor after the first save
|
||||
|
@ -15,6 +15,15 @@ Ember.get(PostModel, 'attributes').forEach(function (name) {
|
||||
watchedProps.push('tags.[]');
|
||||
|
||||
var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
|
||||
init: function () {
|
||||
var self = this;
|
||||
|
||||
this._super();
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
return self.get('isDirty') ? self.unloadDirtyMessage() : null;
|
||||
};
|
||||
},
|
||||
/**
|
||||
* By default, a post will not change its publish state.
|
||||
* Only with a user-set value (via setSaveType action)
|
||||
|
@ -1,12 +1,8 @@
|
||||
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
|
||||
|
||||
var EditorRouteBase = Ember.Mixin.create(styleBody, ShortcutsRoute, {
|
||||
shortcuts: {
|
||||
'ctrl+s, command+s': 'save',
|
||||
'ctrl+alt+p': 'publish',
|
||||
'ctrl+alt+z': 'toggleZenMode'
|
||||
},
|
||||
actions: {
|
||||
save: function () {
|
||||
this.get('controller').send('save');
|
||||
@ -18,8 +14,44 @@ var EditorRouteBase = Ember.Mixin.create(styleBody, ShortcutsRoute, {
|
||||
},
|
||||
toggleZenMode: function () {
|
||||
Ember.$('body').toggleClass('zen');
|
||||
},
|
||||
//The actual functionality is implemented in utils/codemirror-shortcuts
|
||||
codeMirrorShortcut: function (options) {
|
||||
this.get('controller.codemirror').shortcut(options.type);
|
||||
}
|
||||
},
|
||||
|
||||
shortcuts: {
|
||||
//General Editor shortcuts
|
||||
'ctrl+s, command+s': 'save',
|
||||
'ctrl+alt+p': 'publish',
|
||||
'ctrl+alt+z': 'toggleZenMode',
|
||||
//CodeMirror Markdown Shortcuts
|
||||
'ctrl+alt+u': {action: 'codeMirrorShortcut', options: {type: 'strike'}},
|
||||
'ctrl+alt+1': {action: 'codeMirrorShortcut', options: {type: 'h1'}},
|
||||
'ctrl+alt+2': {action: 'codeMirrorShortcut', options: {type: 'h2'}},
|
||||
'ctrl+alt+3': {action: 'codeMirrorShortcut', options: {type: 'h3'}},
|
||||
'ctrl+alt+4': {action: 'codeMirrorShortcut', options: {type: 'h4'}},
|
||||
'ctrl+alt+5': {action: 'codeMirrorShortcut', options: {type: 'h5'}},
|
||||
'ctrl+alt+6': {action: 'codeMirrorShortcut', options: {type: 'h6'}},
|
||||
'ctrl+shift+i': {action: 'codeMirrorShortcut', options: {type: 'image'}},
|
||||
'ctrl+q': {action: 'codeMirrorShortcut', options: {type: 'blockquote'}},
|
||||
'ctrl+shift+1': {action: 'codeMirrorShortcut', options: {type: 'currentDate'}},
|
||||
'ctrl+b, command+b': {action: 'codeMirrorShortcut', options: {type: 'bold'}},
|
||||
'ctrl+i, command+i': {action: 'codeMirrorShortcut', options: {type: 'italic'}},
|
||||
'ctrl+k, command+k': {action: 'codeMirrorShortcut', options: {type: 'link'}},
|
||||
'ctrl+l': {action: 'codeMirrorShortcut', options: {type: 'list'}},
|
||||
/** Currently broken CodeMirror Markdown shortcuts.
|
||||
* some may be broken due to a conflict with CodeMirror commands
|
||||
* (see http://codemirror.net/doc/manual.html#commands)
|
||||
'ctrl+U': {action: 'codeMirrorShortcut', options: {type: 'uppercase'}},
|
||||
'ctrl+shift+U': {action: 'codeMirrorShortcut', options: {type: 'lowercase'}},
|
||||
'ctrl+alt+shift+U': {action: 'codeMirrorShortcut', options: {type: 'titlecase'}}
|
||||
'ctrl+alt+W': {action: 'codeMirrorShortcut', options: {type: 'selectword'}},
|
||||
'ctrl+alt+c': {action: 'codeMirrorShortcut', options: {type: 'copyHTML'}},
|
||||
'ctrl+enter': {action: 'codeMirrorShortcut', options: {type: 'newLine'}},
|
||||
*/
|
||||
}
|
||||
});
|
||||
|
||||
export default EditorRouteBase;
|
||||
export default EditorRouteBase;
|
@ -14,12 +14,20 @@ key.filter = function () {
|
||||
*
|
||||
* To implement shortcuts, add this mixin to your `extend()`,
|
||||
* and implement a `shortcuts` hash.
|
||||
* In this hash, keys are shortcut combinations
|
||||
* (see [keymaster docs](https://github.com/madrobby/keymaster/blob/master/README.markdown)), and values are controller action names.
|
||||
* 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+p': 'toggleZenMode'
|
||||
* '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'}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ -30,10 +38,16 @@ var ShortcutsRoute = Ember.Mixin.create({
|
||||
|
||||
Ember.keys(shortcuts).forEach(function (shortcut) {
|
||||
key(shortcut, function (event) {
|
||||
var action = shortcuts[shortcut],
|
||||
options;
|
||||
if (Ember.typeOf(action) !== 'string') {
|
||||
options = action.options;
|
||||
action = action.action;
|
||||
}
|
||||
|
||||
//stop things like ctrl+s from actually opening a save dialogue
|
||||
event.preventDefault();
|
||||
//map the shortcut to its action
|
||||
self.send(shortcuts[shortcut], event);
|
||||
self.send(action, options);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
131
ghost/admin/utils/codemirror-shortcuts.js
Normal file
131
ghost/admin/utils/codemirror-shortcuts.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* global CodeMirror, moment */
|
||||
/** Set up a shortcut function to be called via router actions.
|
||||
* See editor-route-base
|
||||
*/
|
||||
|
||||
//Used for simple, noncomputational replace-and-go! shortcuts.
|
||||
// See default case in shortcut function below.
|
||||
CodeMirror.prototype.simpleShortcutSyntax = {
|
||||
bold: '**$1**',
|
||||
italic: '*$1*',
|
||||
strike: '~~$1~~',
|
||||
code: '`$1`',
|
||||
link: '[$1](http://)',
|
||||
image: '![$1](http://)',
|
||||
blockquote: '> $1'
|
||||
};
|
||||
CodeMirror.prototype.shortcut = function (type) {
|
||||
var text = this.getSelection(),
|
||||
cursor = this.getCursor(),
|
||||
line = this.getLine(cursor.line),
|
||||
fromLineStart = {line: cursor.line, ch: 0},
|
||||
md, letterCount, textIndex, position;
|
||||
switch (type) {
|
||||
case 'h1':
|
||||
this.replaceRange('# ' + line, fromLineStart);
|
||||
this.setCursor(cursor.line, cursor.ch + 2);
|
||||
return;
|
||||
case 'h2':
|
||||
this.replaceRange('## ' + line, fromLineStart);
|
||||
this.setCursor(cursor.line, cursor.ch + 3);
|
||||
return;
|
||||
case 'h3':
|
||||
this.replaceRange('### ' + line, fromLineStart);
|
||||
this.setCursor(cursor.line, cursor.ch + 4);
|
||||
return;
|
||||
case 'h4':
|
||||
this.replaceRange('#### ' + line, fromLineStart);
|
||||
this.setCursor(cursor.line, cursor.ch + 5);
|
||||
return;
|
||||
case 'h5':
|
||||
this.replaceRange('##### ' + line, fromLineStart);
|
||||
this.setCursor(cursor.line, cursor.ch + 6);
|
||||
return;
|
||||
case 'h6':
|
||||
this.replaceRange('###### ' + line, fromLineStart);
|
||||
this.setCursor(cursor.line, cursor.ch + 7);
|
||||
return;
|
||||
case 'link':
|
||||
md = this.simpleShortcutSyntax.link.replace('$1', text);
|
||||
this.replaceSelection(md, 'end');
|
||||
if (!text) {
|
||||
this.setCursor(cursor.line, cursor.ch + 1);
|
||||
} else {
|
||||
textIndex = line.indexOf(text, cursor.ch - text.length);
|
||||
position = textIndex + md.length - 1;
|
||||
this.setSelection({
|
||||
line: cursor.line,
|
||||
ch: position - 7
|
||||
}, {
|
||||
line: cursor.line,
|
||||
ch: position
|
||||
});
|
||||
}
|
||||
return;
|
||||
case 'image':
|
||||
md = this.simpleShortcutSyntax.image.replace('$1', text);
|
||||
if (line !== '') {
|
||||
md = '\n\n' + md;
|
||||
}
|
||||
this.replaceSelection(md, 'end');
|
||||
cursor = this.getCursor();
|
||||
this.setSelection({line: cursor.line, ch: cursor.ch - 8}, {line: cursor.line, ch: cursor.ch - 1});
|
||||
return;
|
||||
case 'list':
|
||||
md = text.replace(/^(\s*)(\w\W*)/gm, '$1* $2');
|
||||
this.replaceSelection(md, 'end');
|
||||
return;
|
||||
case 'currentDate':
|
||||
md = moment(new Date()).format('D MMMM YYYY');
|
||||
this.replaceSelection(md, 'end');
|
||||
return;
|
||||
/** @TODO
|
||||
case 'uppercase':
|
||||
md = text.toLocaleUpperCase();
|
||||
break;
|
||||
case 'lowercase':
|
||||
md = text.toLocaleLowerCase();
|
||||
break;
|
||||
case 'titlecase':
|
||||
md = text.toTitleCase();
|
||||
break;
|
||||
case 'selectword':
|
||||
word = this.getTokenAt(cursor);
|
||||
if (!/\w$/g.test(word.string)) {
|
||||
this.setSelection({line: cursor.line, ch: word.start}, {line: cursor.line, ch: word.end - 1});
|
||||
} else {
|
||||
this.setSelection({line: cursor.line, ch: word.start}, {line: cursor.line, ch: word.end});
|
||||
}
|
||||
break;
|
||||
case 'copyHTML':
|
||||
converter = new Showdown.converter();
|
||||
if (text) {
|
||||
md = converter.makeHtml(text);
|
||||
} else {
|
||||
md = converter.makeHtml(this.getValue());
|
||||
}
|
||||
|
||||
$(".modal-copyToHTML-content").text(md).selectText();
|
||||
break;
|
||||
case 'newLine':
|
||||
if (line !== "") {
|
||||
this.replaceRange(line + "\n\n", fromLineStart);
|
||||
}
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
if (this.simpleShortcutSyntax[type]) {
|
||||
md = this.simpleShortcutSyntax[type].replace('$1', text);
|
||||
}
|
||||
}
|
||||
if (md) {
|
||||
this.replaceSelection(md, 'end');
|
||||
if (!text) {
|
||||
letterCount = md.length;
|
||||
this.setCursor({
|
||||
line: cursor.line,
|
||||
ch: cursor.ch + (letterCount / 2)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user