diff --git a/index.html b/index.html
index 3945ae4..d45b7f5 100644
--- a/index.html
+++ b/index.html
@@ -48,6 +48,24 @@
+
+
Enjoy live editing (+markdown)
@@ -81,6 +99,7 @@ Enjoy live editing (+markdown)
// config
var options = {
+ // toolbar: document.getElementById('custom-toolbar'),
editor: document.querySelector('[data-toggle="pen"]'),
debug: true,
list: [
diff --git a/src/pen.css b/src/pen.css
index f3c699f..77a91aa 100644
--- a/src/pen.css
+++ b/src/pen.css
@@ -39,7 +39,7 @@
background: transparent;
background-image: none;
}
-
+.pen-menu { min-width: 320px; }
.pen-menu, .pen-input{font-size:14px;line-height:1;}
.pen-menu{white-space:nowrap;box-shadow:1px 2px 3px -2px #222;background:#333;background-image:linear-gradient(to bottom, #222, #333);opacity:0.9;position:fixed;height:36px;border:1px solid #333;border-radius:3px;display:none;z-index:1000;}
.pen-menu:after {top:100%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none;}
@@ -82,7 +82,6 @@
line-height: 1em;
margin-left: .2em;
}
-
.pen-menu .icon-location:before { content: '\e815'; } /* '' */
.pen-menu .icon-fit:before { content: '\e80f'; } /* '' */
.pen-menu .icon-bold:before { content: '\e805'; } /* '' */
@@ -117,7 +116,6 @@
.pen-menu .icon-h6:before { content: 'H6'; }
.pen-menu .icon-p:before { content: 'P'; }
.pen-menu .icon-insertimage:before { width:1.8em;margin:0;position:relative;top:-2px;content:'IMG';font-size:12px;border:1px solid #fff;padding:2px;border-radius:2px; }
-
.pen {
position: relative;
}
diff --git a/src/pen.js b/src/pen.js
index 1c8d621..0336370 100644
--- a/src/pen.js
+++ b/src/pen.js
@@ -80,6 +80,7 @@
var defaults = {
class: 'pen',
debug: false,
+ toolbar: null, // custom toolbar
stay: config.stay || !config.debug,
stayMsg: 'Are you going to leave here?',
textarea: '',
@@ -123,7 +124,7 @@
ctx._range.collapse(false);
// hide menu when a image was inserted
- if(name === 'insertimage') ctx._menu.style.display = 'none';
+ if(name === 'insertimage' && ctx._menu) toggleNode(ctx._menu, true);
return commandOverall(ctx, name, val);
}
@@ -140,71 +141,95 @@
}
function initToolbar(ctx) {
- var icons = '';
-
- utils.forEach(ctx.config.list, function (name) {
- var klass = 'pen-icon icon-' + name;
- icons += '';
- if (/(?:createlink)|(?:insertimage)/.test(name)) icons += '';
- }, true);
-
- ctx._menu = doc.createElement('div');
- ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu');
- ctx._menu.innerHTML = icons;
- ctx._menu.style.display = 'none';
+ var icons = '', inputStr = '';
+
+ ctx._toolbar = ctx.config.toolbar;
+ if (!ctx._toolbar) {
+ var toolList = ctx.config.list;
+ utils.forEach(toolList, function (name) {
+ var klass = 'pen-icon icon-' + name;
+ icons += '';
+ }, true);
+ if (toolList.indexOf('createlink') >= 0 || toolList.indexOf('createlink') >= 0)
+ icons += inputStr;
+ } else if (ctx._toolbar.querySelectorAll('[data-action=createlink]').length ||
+ ctx._toolbar.querySelectorAll('[data-action=insertimage]').length) {
+ icons += inputStr;
+ }
- doc.body.appendChild(ctx._menu);
+ if (icons) {
+ ctx._menu = doc.createElement('div');
+ ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu');
+ ctx._menu.innerHTML = icons;
+ ctx._inputBar = ctx._menu.querySelector('input');
+ toggleNode(ctx._menu, true);
+ doc.body.appendChild(ctx._menu);
+ }
+ if (ctx._toolbar && ctx._inputBar) toggleNode(ctx._inputBar);
}
function initEvents(ctx) {
- var menu = ctx._menu, editor = ctx.config.editor;
-
- var setpos = function() {
- if (menu.style.display === 'block') ctx.menu();
- };
-
- // change menu offset when window resize / scroll
- addListener(ctx, root, 'resize', setpos);
- addListener(ctx, root, 'scroll', setpos);
+ var toolbar = ctx._toolbar || ctx._menu, editor = ctx.config.editor;
var toggleMenu = utils.delayExec(function() {
- if (!selection.isCollapsed) {
- //show menu
- ctx.menu().highlight();
- } else {
- //hide menu
- ctx._menu.style.display = 'none';
- }
+ ctx.highlight().menu();
});
+ var outsideClick = function() {};
- var toggle = function(delay) {
+ function updateStatus(delay) {
ctx._range = ctx.getRange();
toggleMenu(delay);
- };
+ }
- // toggle toolbar on mouse select
- var selecting = false;
- addListener(ctx, editor, 'mousedown', function() {
- selecting = true;
- });
- addListener(ctx, editor, 'mouseleave', function() {
- if (selecting) toggle(800);
- selecting = false;
- });
- addListener(ctx, editor, 'mouseup', function() {
- if (selecting) toggle(100);
- selecting = false;
- });
+ if (ctx._menu) {
+ var setpos = function() {
+ if (ctx._menu.style.display === 'block') ctx.menu();
+ };
- // Hide menu when focusing outside of editor
- addListener(ctx, doc, 'click', function(e) {
- if (!containsNode(editor, e.target) && !containsNode(menu, e.target)) toggleMenu(100);
- });
+ // change menu offset when window resize / scroll
+ addListener(ctx, root, 'resize', setpos);
+ addListener(ctx, root, 'scroll', setpos);
+
+ // toggle toolbar on mouse select
+ var selecting = false;
+ addListener(ctx, editor, 'mousedown', function() {
+ selecting = true;
+ });
+ addListener(ctx, editor, 'mouseleave', function() {
+ if (selecting) updateStatus(800);
+ selecting = false;
+ });
+ addListener(ctx, editor, 'mouseup', function() {
+ if (selecting) updateStatus(100);
+ selecting = false;
+ });
+ // Hide menu when focusing outside of editor
+ outsideClick = function(e) {
+ if (ctx._menu && !containsNode(editor, e.target) && !containsNode(ctx._menu, e.target)) {
+ removeListener(ctx, doc, 'click', outsideClick);
+ toggleMenu(100);
+ }
+ };
+ } else {
+ addListener(ctx, editor, 'click', function() {
+ updateStatus(0);
+ });
+ }
- // toggle toolbar on key select
addListener(ctx, editor, 'keyup', function(e) {
- if (e.which === 8 && ctx.isEmpty()) lineBreak(ctx, true);
- else toggle(400);
+ if (e.which === 8 && ctx.isEmpty()) return lineBreak(ctx, true);
+ // toggle toolbar on key select
+ if (e.which !== 13 || e.shiftKey) return updateStatus(400);
+ var node = getNode(ctx, true);
+ if (!node || !node.nextSibling || !lineBreakReg.test(node.nodeName)) return;
+ if (node.nodeName !== node.nextSibling.nodeName) return;
+ // hack for webkit, make 'enter' behavior like as firefox.
+ if (node.lastChild.nodeName !== 'BR') node.appendChild(doc.createElement('br'));
+ utils.forEach(node.nextSibling.childNodes, function(child) {
+ if (child) node.appendChild(child);
+ }, true);
+ node.parentNode.removeChild(node.nextSibling);
+ focusNode(ctx, node.lastChild, ctx.getRange());
});
// check line break
@@ -212,56 +237,61 @@
editor.classList.remove('pen-placeholder');
if (e.which !== 13 || e.shiftKey) return;
var node = getNode(ctx, true);
- if (node && lineBreakReg.test(node.nodeName)) {
- e.preventDefault();
- var p = doc.createElement('p');
- p.innerHTML = '
';
- if (!node.nextSibling) {
- editor.appendChild(p);
- } else {
- editor.insertBefore(p, node.nextSibling);
- }
- focusNode(ctx, p, ctx.getRange());
- }
+ if (!node || !lineBreakReg.test(node.nodeName)) return;
+ var lastChild = node.lastChild;
+ if (!lastChild || !lastChild.previousSibling) return;
+ if (lastChild.previousSibling.textContent || lastChild.textContent) return;
+ // quit block mode for 2 'enter'
+ e.preventDefault();
+ var p = doc.createElement('p');
+ p.innerHTML = '
';
+ node.removeChild(lastChild);
+ if (!node.nextSibling) node.parentNode.appendChild(p);
+ else node.parentNode.insertBefore(p, node.nextSibling);
+ focusNode(ctx, p, ctx.getRange());
});
var menuApply = function(action, value) {
ctx.execCommand(action, value);
ctx._range = ctx.getRange();
- if (!selection.isCollapsed) ctx.highlight().menu();
+ ctx.highlight().menu();
};
// toggle toolbar on key select
- addListener(ctx, menu, 'click', function(e) {
+ addListener(ctx, toolbar, 'click', function(e) {
var action = e.target.getAttribute('data-action');
if (!action) return;
if (!/(?:createlink)|(?:insertimage)/.test(action)) return menuApply(action);
+ if (!ctx._inputBar) return;
// create link
- var input = menu.getElementsByTagName('input')[0];
-
- input.style.display = 'block';
- input.focus();
+ var input = ctx._inputBar;
+ if (toolbar === ctx._menu) toggleNode(input);
+ else {
+ ctx._inputActive = true;
+ ctx.menu();
+ }
+ if (ctx._menu.style.display === 'none') return;
- var createlink = function(input) {
- input.style.display = 'none';
+ setTimeout(function() { input.focus(); }, 400);
+ var createlink = function() {
+ var inputValue = input.value;
- if (input.value) {
- var inputValue = input.value
+ if (!inputValue) action = 'unlink';
+ else {
+ inputValue = input.value
.replace(strReg.whiteSpace, '')
.replace(strReg.mailTo, 'mailto:$1')
.replace(strReg.http, 'http://$1');
-
- return menuApply(action, inputValue);
}
-
- action = 'unlink';
- menuApply(action);
+ menuApply(action, inputValue);
+ if (toolbar === ctx._menu) toggleNode(input, false);
+ else toggleNode(ctx._menu, true);
};
input.onkeypress = function(e) {
- if (e.which === 13) return createlink(e.target);
+ if (e.which === 13) return createlink();
};
});
@@ -269,6 +299,7 @@
// listen for placeholder
addListener(ctx, editor, 'focus', function() {
if (ctx.isEmpty()) lineBreak(ctx, true);
+ addListener(ctx, doc, 'click', outsideClick);
});
addListener(ctx, editor, 'blur', function() {
@@ -310,6 +341,19 @@
});
}
+ function removeListener(ctx, target, type, listener) {
+ var events = ctx._events[type];
+ if (!events) {
+ var _index = ctx._eventTargets.indexOf(target);
+ if (_index >= 0) events = ctx._eventsCache[_index][type];
+ }
+ if (!events) return ctx;
+ var index = events.indexOf(listener);
+ if (index >= 0) events.splice(index, 1);
+ target.removeEventListener(type, listener, false);
+ return ctx;
+ }
+
function removeAllListeners(ctx) {
utils.forEach(this._events, function (events) {
events.length = 0;
@@ -361,7 +405,7 @@
function effectNode(ctx, el, returnAsNodeName) {
var nodes = [];
el = el || ctx.config.editor;
- while (el !== ctx.config.editor) {
+ while (el && el !== ctx.config.editor) {
if (el.nodeName.match(effectNodeReg)) {
nodes.push(returnAsNodeName ? el.nodeName.toLowerCase() : el);
}
@@ -416,6 +460,10 @@
return {links: count, text: str};
}
+ function toggleNode(node, hide) {
+ node.style.display = hide ? 'none' : 'block';
+ }
+
Pen = function(config) {
if (!config) throw new Error('Can\'t find config');
@@ -472,11 +520,12 @@
Pen.prototype.isEmpty = function(node) {
node = node || this.config.editor;
- return !(node.querySelector('img')) && !(node.querySelector('blockquote')) && !trim(node.textContent);
+ return !(node.querySelector('img')) && !(node.querySelector('blockquote')) &&
+ !(node.querySelector('li')) && !trim(node.textContent);
};
Pen.prototype.getContent = function() {
- return trim(this.config.editor.innerHTML);
+ return this.isEmpty() ? '' : trim(this.config.editor.innerHTML);
};
Pen.prototype.setContent = function(html) {
@@ -522,10 +571,6 @@
};
Pen.prototype.execCommand = function(name, value) {
- // if (this.isEmpty()) {
- // this.config.editor.innerHTML = '';
- // this.config.editor.classList.remove('pen-placeholder');
- // }
name = name.toLowerCase();
this.setRange();
@@ -574,39 +619,40 @@
// highlight menu
Pen.prototype.highlight = function() {
- var node = selection.focusNode
- , effects = effectNode(this, node)
- , menu = this._menu
- , linkInput = menu.querySelector('input')
- , highlight;
-
+ var toolbar = this._toolbar || this._menu
+ , node = getNode(this);
// remove all highlights
- utils.forEach(menu.querySelectorAll('.active'), function(el) {
+ utils.forEach(toolbar.querySelectorAll('.active'), function(el) {
el.classList.remove('active');
}, true);
- if (linkInput) {
+ if (!node) return this;
+
+ var effects = effectNode(this, node)
+ , inputBar = this._inputBar
+ , highlight;
+
+ if (inputBar && toolbar === this._menu) {
// display link input if createlink enabled
- linkInput.style.display = 'none';
- // reset link input value
- linkInput.value = '';
+ inputBar.style.display = 'none';
}
+ // reset link input value
+ inputBar.value = '';
highlight = function(str) {
- var selector = '.icon-' + str
- , el = menu.querySelector(selector);
+ if (!str) return;
+ var el = toolbar.querySelector('[data-action=' + str + ']');
return el && el.classList.add('active');
};
-
utils.forEach(effects, function(item) {
var tag = item.nodeName.toLowerCase();
switch(tag) {
case 'a':
- menu.querySelector('input').value = item.getAttribute('href');
+ if (inputBar) inputBar.value = item.getAttribute('href');
tag = 'createlink';
break;
case 'img':
- menu.querySelector('input').value = item.getAttribute('src');
+ if (inputBar) inputBar.value = item.getAttribute('src');
tag = 'insertimage';
break;
case 'i':
@@ -618,6 +664,7 @@
case 'b':
tag = 'bold';
break;
+ case 'pre':
case 'code':
tag = 'code';
break;
@@ -639,7 +686,15 @@
// show menu
Pen.prototype.menu = function() {
-
+ if (!this._menu) return this;
+ if (selection.isCollapsed) {
+ this._menu.style.display = 'none'; //hide menu
+ this._inputActive = false;
+ return this;
+ }
+ if (this._toolbar) {
+ if (!this._inputBar || !this._inputActive) return this;
+ }
var offset = this._range.getBoundingClientRect()
, menuPadding = 10
, top = offset.top - menuPadding
@@ -701,7 +756,7 @@
removeAllListeners(this);
try {
selection.removeAllRanges();
- this._menu.parentNode.removeChild(this._menu);
+ if (this._menu) this._menu.parentNode.removeChild(this._menu);
} catch (e) {/* IE throws error sometimes*/}
} else {
initToolbar(this);