Merge pull request #3022 from novaugust/codemirror-shortcuts

Implement Markdown Shortcuts
This commit is contained in:
Hannah Wolfe 2014-06-22 10:42:37 +01:00
commit 42c9a272a7
7 changed files with 200 additions and 35 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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);
});
});
},

View 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)
});
}
}
};