Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ricomonster/741d2f615574c291e2fb051f1be6ae79 to your computer and use it in GitHub Desktop.
Save ricomonster/741d2f615574c291e2fb051f1be6ae79 to your computer and use it in GitHub Desktop.
var shortcuts = {
bold: 'Cmd-B',
italic: 'Cmd-I',
link: 'Cmd-K',
image: 'Cmd-Alt-I',
quote: "Cmd-'",
'ordered-list': 'Cmd-Alt-L',
'unordered-list': 'Cmd-L',
'undo': 'Cmd-Z',
'redo': 'Cmd-Y'
};
var toolbar = [
'bold', 'italic', '|',
'quote', 'unordered-list', 'ordered-list', '|',
'link', 'image', '|',
'undo', 'redo', '|',
{name: 'info', action: 'http://lab.lepture.com/editor/markdown'},
'preview',
'fullscreen'
];
var isMac = /Mac/.test(navigator.platform);
function Editor(options) {
this.init(options);
}
Editor.prototype.init = function(options) {
options = options || {};
if (options.element) {
this.element = options.element;
}
options.toolbar = options.toolbar || toolbar;
// you can customize toolbar with object
// [{name: 'bold', shortcut: 'Ctrl-B', className: 'icon-bold'}]
if (!options.hasOwnProperty('status')) {
options.status = ['lines', 'words', 'cursor'];
}
this.options = options;
// if there's a node element, do the render
if (this.element)
this.render(this.element);
};
Editor.prototype.render = function(el) {
if (!el) {
el = this.element || document.getElementsByTagName('textarea')[0];
}
this.element = el;
var options = this.options;
var self = this;
var keyMaps = {};
for (var key in shortcuts) {
(function(key) {
keyMaps[fixShortcut(shortcuts[key])] = function(cm) {
self.action(key, cm);
};
})(key);
}
keyMaps["Enter"] = "newlineAndIndentContinueMarkdownList";
this.codemirror = CodeMirror.fromTextArea(el, {
mode: 'markdown',
theme: 'paper',
indentWithTabs: true,
lineNumbers: false,
lineWrapping: true,
extraKeys: keyMaps
});
if (options.toolbar !== false) {
this.createToolbar();
}
if (options.status !== false) {
this.createStatusbar();
}
this.codemirror.refresh();
};
Editor.prototype.createToolbar = function(tools) {
tools = tools || this.options.toolbar;
if (!tools || tools.length === 0) return;
var bar = document.createElement('div');
bar.className = 'editor-toolbar';
var self = this;
var el;
self.toolbar = {};
for (var i = 0; i < tools.length; i++) {
(function(tool) {
var name, shortcut, action, className;
if (tool.name) {
name = tool.name;
shortcut = tool.shortcut;
action = tool.action;
className = tool.className;
} else {
name = tool;
}
el = createIcon(name, {className: className, shortcut: shortcut});
// bind events, special for info
if (action) {
if (typeof action === 'function') {
el.onclick = action;
} else if (typeof action === 'string') {
el.href = action;
el.target = '_blank';
}
} else {
el.onclick = function() {
return self.action(name);
};
}
self.toolbar[name] = el;
bar.appendChild(el);
})(tools[i]);
}
var cm = this.codemirror;
cm.on('cursorActivity', function() {
var stat = getState(cm);
for (var key in self.toolbar) {
(function(key) {
var el = self.toolbar[key];
if (stat[key]) {
el.classList.add('active');
} else {
el.classList.remove('active');
}
})(key);
}
});
var cmWrapper = cm.getWrapperElement();
cmWrapper.parentNode.insertBefore(bar, cmWrapper);
return bar;
};
Editor.prototype.createStatusbar = function(status) {
status = status || this.options.status;
if (!status || status.length === 0) return;
var bar = document.createElement('div');
bar.className = 'editor-statusbar';
var pos, cm = this.codemirror;
for (var i = 0; i < status.length; i++) {
(function(name) {
var el = document.createElement('span');
el.className = name;
if (name === 'words') {
el.innerHTML = '0';
cm.on('update', function() {
el.innerHTML = cm.getValue().length;
});
} else if (name === 'lines') {
el.innerHTML = '0';
cm.on('update', function() {
el.innerHTML = cm.lineCount();
});
} else if (name === 'cursor') {
el.innerHTML = '1:1';
cm.on('cursorActivity', function() {
pos = cm.getCursor();
el.innerHTML = (pos.line + 1) + ':' + (pos.ch + 1);
});
}
bar.appendChild(el);
})(status[i]);
}
var cmWrapper = this.codemirror.getWrapperElement();
cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling);
return bar;
};
Editor.prototype.action = function(name, cm) {
cm = cm || this.codemirror;
if (!cm) return;
var stat = getState(cm);
var replaceSelection = function(start, end) {
var text;
var startPoint = cm.getCursor('start');
var endPoint = cm.getCursor('end');
if (stat[name]) {
text = cm.getLine(startPoint.line);
start = text.slice(0, startPoint.ch);
end = text.slice(startPoint.ch);
if (name === 'bold') {
start = start.replace(/^(.*)?(\*|\_){2}(\S+.*)?$/, '$1$3');
end = end.replace(/^(.*\S+)?(\*|\_){2}(\s+.*)?$/, '$1$3');
startPoint.ch -= 2;
endPoint.ch -= 2;
} else if (name === 'italic') {
start = start.replace(/^(.*)?(\*|\_)(\S+.*)?$/, '$1$3');
end = end.replace(/^(.*\S+)?(\*|\_)(\s+.*)?$/, '$1$3');
startPoint.ch -= 1;
endPoint.ch -= 1;
}
cm.setLine(startPoint.line, start + end);
cm.setSelection(startPoint, endPoint);
cm.focus();
return;
}
if (end === null) {
end = '';
} else {
end = end || start;
}
text = cm.getSelection();
cm.replaceSelection(start + text + end);
startPoint.ch += start.length;
endPoint.ch += start.length;
cm.setSelection(startPoint, endPoint);
cm.focus();
};
var toggleLine = function() {
var startPoint = cm.getCursor('start');
var endPoint = cm.getCursor('end');
var repl = {
quote: /^(\s*)\>\s+/,
'unordered-list': /^(\s*)(\*|\-|\+)\s+/,
'ordered-list': /^(\s*)\d+\.\s+/
};
var map = {
quote: '> ',
'unordered-list': '* ',
'ordered-list': '1. '
};
for (var i = startPoint.line; i <= endPoint.line; i++) {
(function(i) {
var text = cm.getLine(i);
if (stat[name]) {
text = text.replace(repl[name], '$1');
} else {
text = map[name] + text;
}
cm.setLine(i, text);
})(i);
}
cm.focus();
};
switch (name) {
case 'bold':
replaceSelection('**');
break;
case 'italic':
replaceSelection('*');
break;
case 'code':
replaceSelection(' ');
break;
case 'link':
replaceSelection('[', '](http://)');
break;
case 'image':
replaceSelection('![', '](http://)');
break;
case 'quote':
case 'unordered-list':
case 'ordered-list':
toggleLine();
break;
case 'undo':
cm.undo();
cm.focus();
break;
case 'redo':
cm.redo();
cm.focus();
break;
case 'fullscreen':
toggleFullScreen(cm.getWrapperElement());
break;
}
};
function getState(cm, pos) {
pos = pos || cm.getCursor('start');
var stat = cm.getTokenAt(pos);
if (!stat.type) return {};
var types = stat.type.split(' ');
var ret = {}, data, text;
for (var i = 0; i < types.length; i++) {
data = types[i];
if (data === 'strong') {
ret.bold = true;
} else if (data === 'variable-2') {
text = cm.getLine(pos.line);
if (/^\s*\d+\.\s/.test(text)) {
ret['ordered-list'] = true;
} else {
ret['unordered-list'] = true;
}
} else if (data === 'atom') {
ret.quote = true;
} else if (data === 'em') {
ret.italic = true;
}
}
return ret;
}
function fixShortcut(text) {
if (isMac) {
text = text.replace('Ctrl', 'Cmd');
} else {
text = text.replace('Cmd', 'Ctrl');
}
return text;
}
function titleize(str) {
str = str.replace('-', ' ');
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
var createIcon = function(name, options) {
options = options || {};
var el;
if (name === '|') {
el = document.createElement('i');
el.className = 'separator';
el.innerHTML = '|';
return el;
}
el = document.createElement('a');
el.className = options.className || 'icon-' + name;
el.title = options.title || titleize(name);
var shortcut = options.shortcut || shortcuts[name];
if (shortcut) {
shortcut = fixShortcut(shortcut);
el.title += " \u25CF " + shortcut; // dot separator
if (isMac) {
el.title = el.title.replace("Cmd", "\u2318").replace("Shift", "\u21E7").replace('Alt', "\u2325");
}
}
return el;
};
function toggleFullScreen(el) {
// https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
var doc = document;
var isFull = doc.fullScreen || doc.mozFullScreen || doc.webkitFullScreen;
var request = function() {
if (el.requestFullScreen) {
el.requestFullScreen();
} else if (el.mozRequestFullScreen) {
el.mozRequestFullScreen();
} else if (el.webkitRequestFullScreen) {
el.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
};
var cancel = function() {
if (doc.cancelFullScreen) {
doc.cancelFullScreen();
} else if (doc.mozCancelFullScreen) {
doc.mozCancelFullScreen();
} else if (doc.webkitCancelFullScreen) {
doc.webkitCancelFullScreen();
}
};
if (!isFull) {
request();
} else if (cancel) {
cancel();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment