diff --git a/build/jquery.notebook.min.css b/build/jquery.notebook.min.css
index a0fb2d4..3d722cc 100644
--- a/build/jquery.notebook.min.css
+++ b/build/jquery.notebook.min.css
@@ -1 +1 @@
-.jquery-notebook.editor{position:relative;outline:0;min-height:50px}.jquery-notebook.editor p{z-index:2;position:relative;min-height:1px}.jquery-notebook.editor p.placeholder{position:absolute;z-index:1;opacity:.5;pointer-events:none}.jquery-notebook p:first-child,.jquery-notebook p.placeholder+p{margin-top:0}.jquery-notebook.bubble{position:absolute;z-index:3;opacity:1;height:43px;left:0;top:0;opacity:0;border-radius:5px;-webkit-box-shadow:2px 2px 2px -1px rgba(0,0,0,.7),inset 0 0 1px rgba(255,255,255,.07),inset 0 0 2px rgba(255,255,255,.15000000000000002);box-shadow:2px 2px 2px -1px rgba(0,0,0,.7),inset 0 0 1px rgba(255,255,255,.07),inset 0 0 2px rgba(255,255,255,.15000000000000002);background:#323232;-webkit-transition:.5s ease-in-out;transition:.5s ease-in-out}.jquery-notebook.bubble.active{opacity:1}.jquery-notebook.bubble:after{content:'';display:block;position:absolute;left:50%;bottom:-3px;margin-left:-4px;width:8px;height:8px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);background:#323232;-webkit-box-shadow:0 0 2px #262625;box-shadow:0 0 2px #262625}.jquery-notebook.bubble ul{padding:0;margin:0;list-style:none}.jquery-notebook.bubble ul li{display:inline}.jquery-notebook.bubble button{outline:0;border:0;background:transparent;width:41px;height:42px;font-family:FontAwesome;color:#fff;font-size:14pt;cursor:pointer;margin-top:0}.jquery-notebook.bubble button.active{color:#1ed228}.jquery-notebook.bubble button.bold:after{content:"\f032"}.jquery-notebook.bubble button.italic:after{content:"\f033"}.jquery-notebook.bubble button.underline:after{content:"\f0cd"}.jquery-notebook.bubble button.anchor:after{content:"\f0c1"}.jquery-notebook.bubble button.paste:after{content:"\f0ea"}.jquery-notebook.bubble button.h1:after{font-family:inherit;content:"h1"}.jquery-notebook.bubble button.h2:after{font-family:inherit;content:"h2"}.jquery-notebook.bubble button.ul:after{font-family:inherit;content:"\f0ca"}.jquery-notebook.bubble button.ol:after{font-family:inherit;content:"\f0cb"}.jquery-notebook .link-area{display:none}.jquery-notebook .link-area button{float:left}.jquery-notebook .link-area button:after{content:"\f00d"}.jquery-notebook input[type=text]{width:220px;height:28px;margin:6px 0 0 10px;background:0;border:0;outline:0;color:#fff;font-size:14px;float:left}
\ No newline at end of file
+.jquery-notebook-container{position:relative}.jquery-notebook.editor{position:relative;outline:0;min-height:50px}.jquery-notebook.editor p{z-index:2;position:relative;min-height:1px}.jquery-notebook.editor p.placeholder{position:absolute;z-index:1;opacity:.5;pointer-events:none}.jquery-notebook p:first-child,.jquery-notebook p.placeholder+p{margin-top:0}.jquery-notebook.bubble{position:absolute;z-index:3;opacity:1;height:43px;left:0;top:0;opacity:0;border-radius:5px;border:1px solid #1C1C1B;-webkit-box-shadow:0 2px 3px rgba(0,0,0,.2),inset 0 0 2px rgba(255,255,255,.2);box-shadow:0 2px 3px rgba(0,0,0,.2),inset 0 0 2px rgba(255,255,255,.2);background:#373735;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(55,55,53,1)),color-stop(100%,rgba(40,40,39,1)));background:-webkit-linear-gradient(top,rgba(55,55,53,1) 0,rgba(40,40,39,1) 100%);background:-webkit-gradient(linear,left top,left bottom,from(rgba(55,55,53,1)),to(rgba(40,40,39,1)));background:-webkit-linear-gradient(top,rgba(55,55,53,1) 0,rgba(40,40,39,1) 100%);background:linear-gradient(to bottom,rgba(55,55,53,1) 0,rgba(40,40,39,1) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#373735', endColorstr='#282827', GradientType=0);-webkit-transition:.15s ease-in-out;transition:.15s ease-in-out;-webkit-transition:.15s ease-in-out}.jquery-notebook.bubble.active{opacity:1}.jquery-notebook.bubble.jump{-webkit-transition:opacity .15s;transition:opacity .15s;-webkit-transition:opacity .15s}.jquery-notebook.bubble:after{content:'';display:block;position:absolute;left:50%;bottom:-6px;margin-left:-8px;width:16px;height:16px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);background:#282827;-webkit-box-shadow:2px 2px 1px rgba(0,0,0,.1);box-shadow:2px 2px 1px rgba(0,0,0,.1)}.jquery-notebook.bubble ul{padding:0;margin:0;list-style:none}.jquery-notebook.bubble ul li{display:inline}.jquery-notebook.bubble button{outline:0;border:0;background:transparent;width:41px;height:42px;font-family:FontAwesome;color:#fff;font-size:14pt;cursor:pointer}.jquery-notebook.bubble button.active{color:#60D778}.jquery-notebook.bubble button.bold:after{content:"\f032"}.jquery-notebook.bubble button.italic:after{content:"\f033"}.jquery-notebook.bubble button.underline:after{content:"\f0cd"}.jquery-notebook.bubble button.anchor:after{content:"\f0c1"}.jquery-notebook.bubble button.paste:after{content:"\f0ea"}.jquery-notebook.bubble button.h1:after{font-family:inherit;content:"h1"}.jquery-notebook.bubble button.h2:after{font-family:inherit;content:"h2"}.jquery-notebook.bubble button.ul:after{font-family:inherit;content:"\f0ca"}.jquery-notebook.bubble button.ol:after{font-family:inherit;content:"\f0cb"}.jquery-notebook .link-area{display:none}.jquery-notebook .link-area button{float:left}.jquery-notebook .link-area button:after{content:"\f00d"}.jquery-notebook input[type=text]{width:220px;height:28px;margin:6px 0 0 10px;background:0;border:0;outline:0;color:#fff;font-size:14px;float:left}
\ No newline at end of file
diff --git a/build/jquery.notebook.min.js b/build/jquery.notebook.min.js
index 62583b2..d999ceb 100644
--- a/build/jquery.notebook.min.js
+++ b/build/jquery.notebook.min.js
@@ -1,403 +1 @@
-! function(e, t, n) {
- var o, i = function() {
- var e = function(e) {
- return e && "none" != e ? e.match(/(-?[0-9\.]+)/g) : [1, 0, 0, 1, 0, 0]
- }, t = function(e) {
- return e.css("-webkit-transform") || e.css("transform") || e.css("-moz-transform") || e.css("-o-transform") || e.css("-ms-transform")
- }, n = function(n) {
- var o = t(n);
- return e(o)
- }, o = function(e, t) {
- e.css("-webkit-transform", t), e.css("-moz-transform", t), e.css("-o-transform", t), e.css("-ms-transform", t), e.css("transform", t)
- }, i = function(e) {
- return "matrix(" + e[0] + ", " + e[1] + ", " + e[2] + ", " + e[3] + ", " + e[4] + ", " + e[5] + ")"
- }, a = function(e) {
- var t = n(e);
- return {
- x: parseInt(t[4]),
- y: parseInt(t[5])
- }
- }, l = function(e, t) {
- var a = n(e);
- a[0] = a[3] = t;
- var l = i(a);
- o(e, l)
- }, c = function(e, t, a) {
- var l = n(e);
- l[4] = t, l[5] = a;
- var c = i(l);
- o(e, c)
- }, r = function(e, t) {
- var a = n(e),
- l = t * (Math.PI / 180),
- c = -1 * l;
- a[1] = l, a[2] = c;
- var r = i(a);
- o(e, r)
- };
- return {
- scale: l,
- translate: c,
- rotate: r,
- getTranslate: a
- }
- }(),
- a = "MacIntel" == n.navigator.platform,
- l = 0,
- c = 0,
- r = {
- command: !1,
- shift: !1,
- isSelecting: !1
- }, s = {
- 66: "bold",
- 73: "italic",
- 85: "underline",
- 112: "h1",
- 113: "h2",
- 122: "undo"
- }, u = {
- keyboard: {
- isCommand: function(e, t, n) {
- a && e.metaKey || !a && e.ctrlKey ? t() : n()
- },
- isShift: function(e, t, n) {
- e.shiftKey ? t() : n()
- },
- isModifier: function(e, t) {
- var n = e.which,
- o = s[n];
- o && t.call(this, o)
- },
- isEnter: function(e, t) {
- 13 === e.which && t()
- },
- isArrow: function(e, t) {
- (e.which >= 37 || e.which <= 40) && t()
- }
- },
- html: {
- addTag: function(n, o, i, a) {
- var l = e(t.createElement(o));
- return l.attr("contenteditable", Boolean(a)), l.append(" "), n.append(l), i && (r.focusedElement = n.children().last(), u.cursor.set(n, 0, r.focusedElement)), l
- }
- },
- cursor: {
- set: function(e, o, i) {
- var a;
- if (t.createRange) {
- a = t.createRange();
- var l = n.getSelection(),
- c = e.children().last(),
- r = c.html().length - 1,
- s = i ? i[0] : c[0],
- u = "undefined" != typeof o ? o : r;
- a.setStart(s, u), a.collapse(!0), l.removeAllRanges(), l.addRange(a)
- } else a = t.body.createTextRange(), a.moveToElementText(i), a.collapse(!1), a.select()
- }
- },
- selection: {
- save: function() {
- if (n.getSelection) {
- var e = n.getSelection();
- if (e.rangeCount > 0) return e.getRangeAt(0)
- } else if (t.selection && t.selection.createRange) return t.selection.createRange();
- return null
- },
- restore: function(e) {
- if (e)
- if (n.getSelection) {
- var o = n.getSelection();
- o.removeAllRanges(), o.addRange(e)
- } else t.selection && e.select && e.select()
- },
- getText: function() {
- var e = "";
- return n.getSelection ? e = n.getSelection().toString() : t.getSelection ? e = t.getSelection().toString() : t.selection && (e = t.selection.createRange().text), e
- },
- clear: function() {
- window.getSelection ? window.getSelection().empty ? window.getSelection().empty() : window.getSelection().removeAllRanges && window.getSelection().removeAllRanges() : document.selection && document.selection.empty()
- },
- getContainer: function(e) {
- return n.getSelection && e && e.commonAncestorContainer ? e.commonAncestorContainer : t.selection && e && e.parentElement ? e.parentElement() : null
- }
- },
- validation: {
- isUrl: function(e) {
- return /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(e)
- }
- }
- }, d = {
- updatePos: function(t, o) {
- var a = n.getSelection(),
- l = a.getRangeAt(0),
- c = l.getBoundingClientRect(),
- r = o.width(),
- s = o.height(),
- u = (t.offset().left, {
- x: c.left + c.width / 2 - r / 2,
- y: c.top - s - 8 + e(document).scrollTop()
- });
- i.translate(o, u.x, u.y)
- },
- updateState: function(e, t) {
- t.find("button").removeClass("active");
- var o = n.getSelection(),
- i = [];
- d.checkForFormatting(o.focusNode, i);
- for (var a = {
- b: "bold",
- i: "italic",
- h1: "h1",
- h2: "h2",
- a: "anchor",
- ul: "ul",
- ol: "ol"
- }, l = 0; l < i.length; l++) {
- var c = i[l];
- t.find("button." + a[c]).addClass("active")
- }
- },
- checkForFormatting: function(e, t) {
- var n = ["b", "i", "u", "h1", "h2", "ol", "ul", "li", "a"];
- ("#text" === e.nodeName || -1 != n.indexOf(e.nodeName.toLowerCase())) && ("#text" != e.nodeName && t.push(e.nodeName.toLowerCase()), d.checkForFormatting(e.parentNode, t))
- },
- buildMenu: function(t, n) {
- var i = u.html.addTag(n, "ul", !1, !1);
- for (var a in o.modifiers) {
- var l = u.html.addTag(i, "li", !1, !1),
- c = u.html.addTag(l, "button", !1, !1);
- c.attr("editor-command", o.modifiers[a]), c.addClass(o.modifiers[a])
- }
- n.find("button").click(function(n) {
- n.preventDefault();
- var o = e(this).attr("editor-command");
- m.commands[o].call(t, n)
- });
- var r = u.html.addTag(n, "div", !1, !1);
- r.addClass("link-area");
- var s = u.html.addTag(r, "input", !1, !1);
- s.attr({
- type: "text"
- });
- var d = u.html.addTag(r, "button", !1, !1);
- d.click(function(t) {
- t.preventDefault();
- e(this).closest(".editor");
- e(this).closest(".link-area").hide(), e(this).closest(".bubble").find("ul").show()
- })
- },
- show: function() {
- var t = e(this).parent().find(".bubble");
- t.length || (t = u.html.addTag(e(this).parent(), "div", !1, !1), t.addClass("jquery-notebook bubble"), d.buildMenu(this, t)), t.show(), d.updateState(this, t), t.addClass("active"), d.updatePos(e(this), t)
- },
- update: function() {
- var t = e(this).parent().find(".bubble");
- d.updateState(this, t)
- },
- clear: function() {
- var t = e(this).parent().find(".bubble");
- t.hasClass("active") && (t.removeClass("active"), d.hideLinkInput.call(this), d.showButtons.call(this), setTimeout(function() {
- t.hasClass("active") || t.hide()
- }, 500))
- },
- hideButtons: function() {
- e(this).parent().find(".bubble").find("ul").hide()
- },
- showButtons: function() {
- e(this).parent().find(".bubble").find("ul").show()
- },
- showLinkInput: function(t) {
- d.hideButtons.call(this);
- var n = this,
- o = e(this).parent().find(".bubble").find("input[type=text]"),
- i = o.closest(".jquery-notebook").find("button.anchor").hasClass("active");
- o.unbind("keydown"), o.keydown(function(o) {
- var a = e(this);
- u.keyboard.isEnter(o, function() {
- o.preventDefault();
- var e = a.val();
- u.validation.isUrl(e) ? (o.url = e, m.commands.createLink(o, t), d.clear.call(n)) : "" === e && i && (m.commands.removeLink(o, t), d.clear.call(n))
- })
- }), o.bind("paste", function() {
- var t = e(this);
- setTimeout(function() {
- var e = t.val();
- /http:\/\/https?:\/\//.test(e) && (e = e.substring(7), t.val(e))
- }, 1)
- });
- var a = "http://";
- if (i) {
- var l = e(u.selection.getContainer(t)).closest("a");
- a = l.prop("href") || a
- }
- e(this).parent().find(".link-area").show(), o.val(a).focus()
- },
- hideLinkInput: function() {
- e(this).parent().find(".bubble").find(".link-area").hide()
- }
- }, f = {
- bindEvents: function(t) {
- t.keydown(h.keydown), t.keyup(h.keyup), t.focus(h.focus), t.bind("paste", m.paste), t.mousedown(h.mouseClick), t.mouseup(h.mouseUp), t.mousemove(h.mouseMove), t.blur(h.blur), e("body").mouseup(function(e) {
- e.target == e.currentTarget && r.isSelecting && h.mouseUp.call(t, e)
- })
- },
- setPlaceholder: function(t) {
- if (/^\s*$/.test(e(this).text())) {
- e(this).empty();
- var n = u.html.addTag(e(this), "p").addClass("placeholder");
- n.append(e(this).attr("editor-placeholder")), u.html.addTag(e(this), "p", "undefined" != typeof t.focus ? t.focus : !1, !0)
- } else e(this).find(".placeholder").remove()
- },
- removePlaceholder: function() {
- e(this).find(".placeholder").remove()
- },
- preserveElementFocus: function() {
- var e = n.getSelection() ? n.getSelection().anchorNode : t.activeElement;
- if (e) {
- var o = e.parentNode,
- i = o !== r.focusedElement,
- a = this.children,
- l = 0;
- o === this && (o = e);
- for (var c = 0; c < a.length; c++)
- if (o === a[c]) {
- l = c;
- break
- }
- i && (r.focusedElement = o, r.focusedElementIndex = l)
- }
- },
- prepare: function(e, t) {
- if (o = t, e.attr("editor-mode", o.mode), e.attr("editor-placeholder", o.placeholder), e.attr("contenteditable", !0), e.css("position", "relative"), e.addClass("jquery-notebook editor"), f.setPlaceholder.call(e, {}), f.preserveElementFocus.call(e), o.autoFocus === !0) {
- var n = e.find("p:not(.placeholder)");
- u.cursor.set(e, 0, n)
- }
- }
- }, h = {
- keydown: function(e) {
- var t = this;
- r.command && 65 === e.which && setTimeout(function() {
- d.show.call(t)
- }, 50), u.keyboard.isCommand(e, function() {
- r.command = !0
- }, function() {
- r.command = !1
- }), u.keyboard.isShift(e, function() {
- r.shift = !0
- }, function() {
- r.shift = !1
- }), u.keyboard.isModifier.call(this, e, function(t) {
- r.command && m.commands[t].call(this, e)
- }), r.shift ? u.keyboard.isArrow.call(this, e, function() {
- setTimeout(function() {
- var e = u.selection.getText();
- "" !== e ? d.show.call(t) : d.clear.call(t)
- }, 100)
- }) : u.keyboard.isArrow.call(this, e, function() {
- d.clear.call(t)
- }), 13 === e.which && m.enterKey.call(this, e), 27 === e.which && d.clear.call(this), 86 === e.which && m.paste.call(this, e)
- },
- keyup: function(t) {
- u.keyboard.isCommand(t, function() {
- r.command = !1
- }, function() {
- r.command = !0
- }), f.preserveElementFocus.call(this), f.removePlaceholder.call(this), /^\s*$/.test(e(this).text()) && (e(this).empty(), u.html.addTag(e(this), "p", !0, !0))
- },
- focus: function() {
- r.command = !1, r.shift = !1
- },
- mouseClick: function(t) {
- var n = this;
- if (r.isSelecting = !0, 2 === t.button) return setTimeout(function() {
- d.show.call(n)
- }, 50), void t.preventDefault();
- if (e(this).find(".bubble:visible").length) {
- var o = e(this).find(".bubble:visible"),
- i = o.offset().left,
- a = o.offset().top,
- s = o.width(),
- u = o.height();
- if (l > i && i + s > l && c > a && a + u > c) return
- }
- },
- mouseUp: function(e) {
- e.preventDefault();
- var t = this;
- r.isSelecting = !1, setTimeout(function() {
- var e = u.selection.save();
- e.collapsed ? d.clear.call(t) : d.show.call(t)
- }, 50)
- },
- mouseMove: function(e) {
- l = e.pageX, c = e.pageY
- },
- blur: function() {
- f.setPlaceholder.call(this, {
- focus: !1
- })
- }
- }, m = {
- commands: {
- bold: function(e) {
- e.preventDefault(), t.execCommand("bold", !1), d.update.call(this)
- },
- italic: function(e) {
- e.preventDefault(), t.execCommand("italic", !1), d.update.call(this)
- },
- underline: function(e) {
- e.preventDefault(), t.execCommand("underline", !1), d.update.call(this)
- },
- anchor: function(e) {
- e.preventDefault();
- var t = u.selection.save();
- d.showLinkInput.call(this, t)
- },
- createLink: function(e, n) {
- u.selection.restore(n), t.execCommand("createLink", !1, e.url), d.update.call(this)
- },
- removeLink: function(t, n) {
- var o = e(u.selection.getContainer(n)).closest("a");
- o.contents().first().unwrap()
- },
- h1: function(n) {
- n.preventDefault(), e(window.getSelection().anchorNode.parentNode).is("h1") ? t.execCommand("formatBlock", !1, "
") : t.execCommand("formatBlock", !1, "
"), d.update.call(this)
- },
- h2: function(n) {
- n.preventDefault(), e(window.getSelection().anchorNode.parentNode).is("h2") ? t.execCommand("formatBlock", !1, "
") : t.execCommand("formatBlock", !1, "
"), d.update.call(this)
- },
- ul: function(e) {
- e.preventDefault(), t.execCommand("insertUnorderedList", !1), d.update.call(this)
- },
- ol: function(e) {
- e.preventDefault(), t.execCommand("insertOrderedList", !1), d.update.call(this)
- },
- undo: function(e) {
- e.preventDefault(), t.execCommand("undo", !1)
- }
- },
- enterKey: function(t) {
- return "inline" === e(this).attr("editor-mode") ? void t.preventDefault() : void 0
- },
- paste: function() {
- var t = e(this);
- setTimeout(function() {
- t.find("*").each(function() {
- var t = e(this);
- e.each(this.attributes, function() {
- "class" === this.name && t.hasClass("placeholder") || t.removeAttr(this.name)
- })
- })
- }, 100)
- }
- };
- e.fn.notebook = function(t) {
- return t = e.extend({}, e.fn.notebook.defaults, t), f.prepare(this, t), f.bindEvents(this), this
- }, e.fn.notebook.defaults = {
- autoFocus: !1,
- placeholder: "Your text here...",
- mode: "multiline",
- modifiers: ["bold", "italic", "underline", "h1", "h2", "ol", "ul", "anchor"]
- }
-}(jQuery, document, window);
\ No newline at end of file
+!function(e,t,n){var o,i=function(){var e=function(e){return e&&"none"!=e?e.match(/(-?[0-9\.]+)/g):[1,0,0,1,0,0]},t=function(e){return e.css("-webkit-transform")||e.css("transform")||e.css("-moz-transform")||e.css("-o-transform")||e.css("-ms-transform")},n=function(n){var o=t(n);return e(o)},o=function(e,t){e.css("-webkit-transform",t),e.css("-moz-transform",t),e.css("-o-transform",t),e.css("-ms-transform",t),e.css("transform",t)},i=function(e){return"matrix("+e[0]+", "+e[1]+", "+e[2]+", "+e[3]+", "+e[4]+", "+e[5]+")"},a=function(e){var t=n(e);return{x:parseInt(t[4]),y:parseInt(t[5])}},l=function(e,t){var a=n(e);a[0]=a[3]=t;var l=i(a);o(e,l)},s=function(e,t,a){var l=n(e);l[4]=t,l[5]=a;var s=i(l);o(e,s)},r=function(e,t){var a=n(e),l=t*(Math.PI/180),s=-1*l;a[1]=l,a[2]=s;var r=i(a);o(e,r)};return{scale:l,translate:s,rotate:r,getTranslate:a}}(),a="MacIntel"==n.navigator.platform,l=0,s=0,r={command:!1,shift:!1,isSelecting:!1},c={66:"bold",73:"italic",85:"underline",112:"h1",113:"h2",122:"undo"},u={keyboard:{isCommand:function(e,t,n){a&&e.metaKey||!a&&e.ctrlKey?t():n()},isShift:function(e,t,n){e.shiftKey?t():n()},isModifier:function(e,t){var n=e.which,o=c[n];o&&t.call(this,o)},isEnter:function(e,t){13===e.which&&t()},isArrow:function(e,t){(e.which>=37||e.which<=40)&&t()}},html:{addTag:function(n,o,i,a){var l=e(t.createElement(o));return l.attr("contenteditable",Boolean(a)),l.append(" "),n.append(l),i&&(r.focusedElement=n.children().last(),u.cursor.set(n,0,r.focusedElement)),l}},cursor:{set:function(e,o,i){var a;if(t.createRange){a=t.createRange();var l=n.getSelection(),s=e.children().last(),r=s.html().length-1,c=i?i[0]:s[0],u="undefined"!=typeof o?o:r;a.setStart(c,u),a.collapse(!0),l.removeAllRanges(),l.addRange(a)}else a=t.body.createTextRange(),a.moveToElementText(i),a.collapse(!1),a.select()}},selection:{save:function(){if(n.getSelection){var e=n.getSelection();if(e.rangeCount>0)return e.getRangeAt(0)}else if(t.selection&&t.selection.createRange)return t.selection.createRange();return null},restore:function(e){if(e)if(n.getSelection){var o=n.getSelection();o.removeAllRanges(),o.addRange(e)}else t.selection&&e.select&&e.select()},getText:function(){var e="";return n.getSelection?e=n.getSelection().toString():t.getSelection?e=t.getSelection().toString():t.selection&&(e=t.selection.createRange().text),e},clear:function(){window.getSelection?window.getSelection().empty?window.getSelection().empty():window.getSelection().removeAllRanges&&window.getSelection().removeAllRanges():document.selection&&document.selection.empty()},getContainer:function(e){return n.getSelection&&e&&e.commonAncestorContainer?e.commonAncestorContainer:t.selection&&e&&e.parentElement?e.parentElement():null}},validation:{isUrl:function(e){return/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(e)}}},d={updatePos:function(t,o){var a=n.getSelection(),l=a.getRangeAt(0),s=l.getBoundingClientRect(),r=o.width(),c=o.height(),u=(t.offset().left,{x:s.left+s.width/2-r/2,y:s.top-c-8+e(document).scrollTop()});i.translate(o,u.x,u.y)},updateState:function(e,t){t.find("button").removeClass("active");var o=n.getSelection(),i=[];d.checkForFormatting(o.focusNode,i);for(var a={b:"bold",i:"italic",u:"underline",h1:"h1",h2:"h2",a:"anchor",ul:"ul",ol:"ol"},l=0;l'),o=n,t.attr("editor-mode",o.mode),t.attr("editor-placeholder",o.placeholder),t.attr("contenteditable",!0),t.css("position","relative"),t.addClass("jquery-notebook editor"),f.setPlaceholder.call(t,{}),f.preserveElementFocus.call(t),o.autoFocus===!0){var i=t.find("p:not(.placeholder)");u.cursor.set(t,0,i)}}},h={keydown:function(e){var t=this;r.command&&65===e.which&&setTimeout(function(){d.show.call(t)},50),u.keyboard.isCommand(e,function(){r.command=!0},function(){r.command=!1}),u.keyboard.isShift(e,function(){r.shift=!0},function(){r.shift=!1}),u.keyboard.isModifier.call(this,e,function(t){r.command&&m.commands[t].call(this,e)}),r.shift?u.keyboard.isArrow.call(this,e,function(){setTimeout(function(){var e=u.selection.getText();""!==e?d.show.call(t):d.clear.call(t)},100)}):u.keyboard.isArrow.call(this,e,function(){d.clear.call(t)}),13===e.which&&m.enterKey.call(this,e),27===e.which&&d.clear.call(this),86===e.which&&m.paste.call(this,e)},keyup:function(t){u.keyboard.isCommand(t,function(){r.command=!1},function(){r.command=!0}),f.preserveElementFocus.call(this),f.removePlaceholder.call(this),/^\s*$/.test(e(this).text())&&(e(this).empty(),u.html.addTag(e(this),"p",!0,!0))},focus:function(){r.command=!1,r.shift=!1},mouseClick:function(t){var n=this;if(r.isSelecting=!0,2===t.button)return setTimeout(function(){d.show.call(n)},50),void t.preventDefault();if(e(this).find(".bubble:visible").length){var o=e(this).find(".bubble:visible"),i=o.offset().left,a=o.offset().top,c=o.width(),u=o.height();if(l>i&&i+c>l&&s>a&&a+u>s)return}},mouseUp:function(e){e.preventDefault();var t=this;r.isSelecting=!1,setTimeout(function(){var e=u.selection.save();e.collapsed?d.clear.call(t):d.show.call(t)},50)},mouseMove:function(e){l=e.pageX,s=e.pageY},blur:function(){f.setPlaceholder.call(this,{focus:!1})}},m={commands:{bold:function(e){e.preventDefault(),t.execCommand("bold",!1),d.update.call(this)},italic:function(e){e.preventDefault(),t.execCommand("italic",!1),d.update.call(this)},underline:function(e){e.preventDefault(),t.execCommand("underline",!1),d.update.call(this)},anchor:function(e){e.preventDefault();var t=u.selection.save();d.showLinkInput.call(this,t)},createLink:function(e,n){u.selection.restore(n),t.execCommand("createLink",!1,e.url),d.update.call(this)},removeLink:function(t,n){var o=e(u.selection.getContainer(n)).closest("a");o.contents().first().unwrap()},h1:function(n){n.preventDefault(),e(window.getSelection().anchorNode.parentNode).is("h1")?t.execCommand("formatBlock",!1,""):t.execCommand("formatBlock",!1,"
"),d.update.call(this)},h2:function(n){n.preventDefault(),e(window.getSelection().anchorNode.parentNode).is("h2")?t.execCommand("formatBlock",!1,"
"):t.execCommand("formatBlock",!1,"
"),d.update.call(this)},ul:function(e){e.preventDefault(),t.execCommand("insertUnorderedList",!1),d.update.call(this)},ol:function(e){e.preventDefault(),t.execCommand("insertOrderedList",!1),d.update.call(this)},undo:function(e){e.preventDefault(),t.execCommand("undo",!1)}},enterKey:function(t){return"inline"===e(this).attr("editor-mode")?void t.preventDefault():void 0},paste:function(){var t=e(this);setTimeout(function(){t.find("*").each(function(){var t=e(this);e.each(this.attributes,function(){"class"===this.name&&t.hasClass("placeholder")||t.removeAttr(this.name)})})},100)}};e.fn.notebook=function(t){return t=e.extend({},e.fn.notebook.defaults,t),f.prepare(this,t),f.bindEvents(this),this},e.fn.notebook.defaults={autoFocus:!1,placeholder:"Your text here...",mode:"multiline",modifiers:["bold","italic","underline","h1","h2","ol","ul","anchor"]}}(jQuery,document,window);
\ No newline at end of file
diff --git a/src/js/jquery.notebook.css b/src/js/jquery.notebook.css
index 30753fe..129830e 100644
--- a/src/js/jquery.notebook.css
+++ b/src/js/jquery.notebook.css
@@ -1,3 +1,7 @@
+.jquery-notebook-container {
+ position: relative;
+}
+
.jquery-notebook.editor {
position: relative;
outline: none;
diff --git a/src/js/jquery.notebook.js b/src/js/jquery.notebook.js
index 3865821..11cb1d0 100644
--- a/src/js/jquery.notebook.js
+++ b/src/js/jquery.notebook.js
@@ -17,679 +17,681 @@
(function($, d, w) {
- /*
- * This module deals with the CSS transforms. As it is not possible to easily
- * combine the transform functions with JavaScript this module abstract those
- * functions and generates a raw transform matrix, combining the new transform
- * with the others that were previously applied to the element.
- */
-
- var transform = (function() {
- var matrixToArray = function(str) {
- if (!str || str == 'none') {
- return [1, 0, 0, 1, 0, 0];
- }
- return str.match(/(-?[0-9\.]+)/g);
- };
+ /*
+ * This module deals with the CSS transforms. As it is not possible to easily
+ * combine the transform functions with JavaScript this module abstract those
+ * functions and generates a raw transform matrix, combining the new transform
+ * with the others that were previously applied to the element.
+ */
- var getPreviousTransforms = function(elem) {
- return elem.css('-webkit-transform') || elem.css('transform') || elem.css('-moz-transform') ||
- elem.css('-o-transform') || elem.css('-ms-transform');
- };
+ var transform = (function() {
+ var matrixToArray = function(str) {
+ if (!str || str == 'none') {
+ return [1, 0, 0, 1, 0, 0];
+ }
+ return str.match(/(-?[0-9\.]+)/g);
+ };
- var getMatrix = function(elem) {
- var previousTransform = getPreviousTransforms(elem);
- return matrixToArray(previousTransform);
- };
-
- var applyTransform = function(elem, transform) {
- elem.css('-webkit-transform', transform);
- elem.css('-moz-transform', transform);
- elem.css('-o-transform', transform);
- elem.css('-ms-transform', transform);
- elem.css('transform', transform);
- };
-
- var buildTransformString = function(matrix) {
- return 'matrix(' + matrix[0] +
- ', ' + matrix[1] +
- ', ' + matrix[2] +
- ', ' + matrix[3] +
- ', ' + matrix[4] +
- ', ' + matrix[5] + ')';
- };
+ var getPreviousTransforms = function(elem) {
+ return elem.css('-webkit-transform') || elem.css('transform') || elem.css('-moz-transform') ||
+ elem.css('-o-transform') || elem.css('-ms-transform');
+ };
- var getTranslate = function(elem) {
- var matrix = getMatrix(elem);
- return {
- x: parseInt(matrix[4]),
- y: parseInt(matrix[5])
- };
- };
+ var getMatrix = function(elem) {
+ var previousTransform = getPreviousTransforms(elem);
+ return matrixToArray(previousTransform);
+ };
- var scale = function(elem, _scale) {
- var matrix = getMatrix(elem);
- matrix[0] = matrix[3] = _scale;
- var transform = buildTransformString(matrix);
- applyTransform(elem, transform);
- };
+ var applyTransform = function(elem, transform) {
+ elem.css('-webkit-transform', transform);
+ elem.css('-moz-transform', transform);
+ elem.css('-o-transform', transform);
+ elem.css('-ms-transform', transform);
+ elem.css('transform', transform);
+ };
- var translate = function(elem, x, y) {
- var matrix = getMatrix(elem);
- matrix[4] = x;
- matrix[5] = y;
- var transform = buildTransformString(matrix);
- applyTransform(elem, transform);
- };
+ var buildTransformString = function(matrix) {
+ return 'matrix(' + matrix[0] +
+ ', ' + matrix[1] +
+ ', ' + matrix[2] +
+ ', ' + matrix[3] +
+ ', ' + matrix[4] +
+ ', ' + matrix[5] + ')';
+ };
- var rotate = function(elem, deg) {
- var matrix = getMatrix(elem);
- var rad1 = deg * (Math.PI / 180);
- var rad2 = rad1 * -1;
- matrix[1] = rad1;
- matrix[2] = rad2;
- var transform = buildTransformString(matrix);
- applyTransform(elem, transform);
- };
+ var getTranslate = function(elem) {
+ var matrix = getMatrix(elem);
+ return {
+ x: parseInt(matrix[4]),
+ y: parseInt(matrix[5])
+ };
+ };
- return {
- scale: scale,
- translate: translate,
- rotate: rotate,
- getTranslate: getTranslate
- };
- })();
+ var scale = function(elem, _scale) {
+ var matrix = getMatrix(elem);
+ matrix[0] = matrix[3] = _scale;
+ var transform = buildTransformString(matrix);
+ applyTransform(elem, transform);
+ };
+
+ var translate = function(elem, x, y) {
+ var matrix = getMatrix(elem);
+ matrix[4] = x;
+ matrix[5] = y;
+ var transform = buildTransformString(matrix);
+ applyTransform(elem, transform);
+ };
+
+ var rotate = function(elem, deg) {
+ var matrix = getMatrix(elem);
+ var rad1 = deg * (Math.PI / 180);
+ var rad2 = rad1 * -1;
+ matrix[1] = rad1;
+ matrix[2] = rad2;
+ var transform = buildTransformString(matrix);
+ applyTransform(elem, transform);
+ };
+
+ return {
+ scale: scale,
+ translate: translate,
+ rotate: rotate,
+ getTranslate: getTranslate
+ };
+ })();
- var isMac = w.navigator.platform == 'MacIntel',
- mouseX = 0,
- mouseY = 0,
- cache = {
- command: false,
- shift: false,
- isSelecting: false
+ var isMac = w.navigator.platform == 'MacIntel',
+ mouseX = 0,
+ mouseY = 0,
+ cache = {
+ command: false,
+ shift: false,
+ isSelecting: false
+ },
+ modifiers = {
+ 66: 'bold',
+ 73: 'italic',
+ 85: 'underline',
+ 112: 'h1',
+ 113: 'h2',
+ 122: 'undo'
+ },
+ options,
+ utils = {
+ keyboard: {
+ isCommand: function(e, callbackTrue, callbackFalse) {
+ if (isMac && e.metaKey || !isMac && e.ctrlKey) {
+ callbackTrue();
+ } else {
+ callbackFalse();
+ }
+ },
+ isShift: function(e, callbackTrue, callbackFalse) {
+ if (e.shiftKey) {
+ callbackTrue();
+ } else {
+ callbackFalse();
+ }
+ },
+ isModifier: function(e, callback) {
+ var key = e.which,
+ cmd = modifiers[key];
+ if (cmd) {
+ callback.call(this, cmd);
+ }
},
- modifiers = {
- 66: 'bold',
- 73: 'italic',
- 85: 'underline',
- 112: 'h1',
- 113: 'h2',
- 122: 'undo'
+ isEnter: function(e, callback) {
+ if (e.which === 13) {
+ callback();
+ }
},
- options,
- utils = {
- keyboard: {
- isCommand: function(e, callbackTrue, callbackFalse) {
- if (isMac && e.metaKey || !isMac && e.ctrlKey) {
- callbackTrue();
- } else {
- callbackFalse();
- }
- },
- isShift: function(e, callbackTrue, callbackFalse) {
- if (e.shiftKey) {
- callbackTrue();
- } else {
- callbackFalse();
- }
- },
- isModifier: function(e, callback) {
- var key = e.which,
- cmd = modifiers[key];
- if (cmd) {
- callback.call(this, cmd);
- }
- },
- isEnter: function(e, callback) {
- if (e.which === 13) {
- callback();
- }
- },
- isArrow: function(e, callback) {
- if (e.which >= 37 || e.which <= 40) {
- callback();
- }
- }
- },
- html: {
- addTag: function(elem, tag, focus, editable) {
- var newElement = $(d.createElement(tag));
- newElement.attr('contenteditable', Boolean(editable));
- newElement.append(' ');
- elem.append(newElement);
- if (focus) {
- cache.focusedElement = elem.children().last();
- utils.cursor.set(elem, 0, cache.focusedElement);
- }
- return newElement;
- }
- },
- cursor: {
- set: function(editor, pos, elem) {
- var range;
- if (d.createRange) {
- range = d.createRange();
- var selection = w.getSelection(),
- lastChild = editor.children().last(),
- length = lastChild.html().length - 1,
- toModify = elem ? elem[0] : lastChild[0],
- theLength = typeof pos !== 'undefined' ? pos : length;
- range.setStart(toModify, theLength);
- range.collapse(true);
- selection.removeAllRanges();
- selection.addRange(range);
- } else {
- range = d.body.createTextRange();
- range.moveToElementText(elem);
- range.collapse(false);
- range.select();
- }
- }
- },
- selection: {
- save: function() {
- if (w.getSelection) {
- var sel = w.getSelection();
- if (sel.rangeCount > 0) {
- return sel.getRangeAt(0);
- }
- } else if (d.selection && d.selection.createRange) { // IE
- return d.selection.createRange();
- }
- return null;
- },
- restore: function(range) {
- if (range) {
- if (w.getSelection) {
- var sel = w.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- } else if (d.selection && range.select) { // IE
- range.select();
- }
- }
- },
- getText: function() {
- var txt = '';
- if (w.getSelection) {
- txt = w.getSelection().toString();
- } else if (d.getSelection) {
- txt = d.getSelection().toString();
- } else if (d.selection) {
- txt = d.selection.createRange().text;
- }
- return txt;
- },
- clear: function() {
- if (window.getSelection) {
- if (window.getSelection().empty) { // Chrome
- window.getSelection().empty();
- } else if (window.getSelection().removeAllRanges) { // Firefox
- window.getSelection().removeAllRanges();
- }
- } else if (document.selection) { // IE?
- document.selection.empty();
- }
- },
- getContainer: function(sel) {
- if (w.getSelection && sel && sel.commonAncestorContainer) {
- return sel.commonAncestorContainer;
- } else if (d.selection && sel && sel.parentElement) {
- return sel.parentElement();
- }
- return null;
- }
- },
- validation: {
- isUrl: function(url) {
- return (/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/).test(url);
- }
+ isArrow: function(e, callback) {
+ if (e.which >= 37 || e.which <= 40) {
+ callback();
+ }
+ }
+ },
+ html: {
+ addTag: function(elem, tag, focus, editable) {
+ var newElement = $(d.createElement(tag));
+ newElement.attr('contenteditable', Boolean(editable));
+ newElement.append(' ');
+ elem.append(newElement);
+ if (focus) {
+ cache.focusedElement = elem.children().last();
+ utils.cursor.set(elem, 0, cache.focusedElement);
+ }
+ return newElement;
+ }
+ },
+ cursor: {
+ set: function(editor, pos, elem) {
+ var range;
+ if (d.createRange) {
+ range = d.createRange();
+ var selection = w.getSelection(),
+ lastChild = editor.children().last(),
+ length = lastChild.html().length - 1,
+ toModify = elem ? elem[0] : lastChild[0],
+ theLength = typeof pos !== 'undefined' ? pos : length;
+ range.setStart(toModify, theLength);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ } else {
+ range = d.body.createTextRange();
+ range.moveToElementText(elem);
+ range.collapse(false);
+ range.select();
+ }
+ }
+ },
+ selection: {
+ save: function() {
+ if (w.getSelection) {
+ var sel = w.getSelection();
+ if (sel.rangeCount > 0) {
+ return sel.getRangeAt(0);
}
+ } else if (d.selection && d.selection.createRange) { // IE
+ return d.selection.createRange();
+ }
+ return null;
},
- bubble = {
- /*
- * This is called to position the bubble above the selection.
- */
- updatePos: function(editor, elem) {
- var sel = w.getSelection(),
- range = sel.getRangeAt(0),
- boundary = range.getBoundingClientRect(),
- bubbleWidth = elem.width(),
- bubbleHeight = elem.height(),
- offset = editor.offset().left,
- pos = {
- x: (boundary.left + boundary.width / 2) - bubbleWidth / 2,
- y: boundary.top - bubbleHeight - 8 + $(document).scrollTop()
- };
- transform.translate(elem, pos.x, pos.y);
- },
- /*
- * Updates the bubble to set the active formats for the current selection.
- */
- updateState: function(editor, elem) {
- elem.find('button').removeClass('active');
- var sel = w.getSelection(),
- formats = [];
- bubble.checkForFormatting(sel.focusNode, formats);
- var formatDict = {
- 'b': 'bold',
- 'i': 'italic',
- 'u': 'underline',
- 'h1': 'h1',
- 'h2': 'h2',
- 'a': 'anchor',
- 'ul': 'ul',
- 'ol': 'ol'
- };
- for (var i = 0; i < formats.length; i++) {
- var format = formats[i];
- elem.find('button.' + formatDict[format]).addClass('active');
- }
- },
- /*
- * Recursively navigates upwards in the DOM to find all the format
- * tags enclosing the selection.
- */
- checkForFormatting: function(currentNode, formats) {
- var validFormats = ['b', 'i', 'u', 'h1', 'h2', 'ol', 'ul', 'li', 'a'];
- if (currentNode.nodeName === '#text' ||
- validFormats.indexOf(currentNode.nodeName.toLowerCase()) != -1) {
- if (currentNode.nodeName != '#text') {
- formats.push(currentNode.nodeName.toLowerCase());
- }
- bubble.checkForFormatting(currentNode.parentNode, formats);
- }
- },
- buildMenu: function(editor, elem) {
- var ul = utils.html.addTag(elem, 'ul', false, false);
- for (var cmd in options.modifiers) {
- var li = utils.html.addTag(ul, 'li', false, false);
- var btn = utils.html.addTag(li, 'button', false, false);
- btn.attr('editor-command', options.modifiers[cmd]);
- btn.addClass(options.modifiers[cmd]);
- }
- elem.find('button').click(function(e) {
- e.preventDefault();
- var cmd = $(this).attr('editor-command');
- events.commands[cmd].call(editor, e);
- });
- var linkArea = utils.html.addTag(elem, 'div', false, false);
- linkArea.addClass('link-area');
- var linkInput = utils.html.addTag(linkArea, 'input', false, false);
- linkInput.attr({
- type: 'text'
- });
- var closeBtn = utils.html.addTag(linkArea, 'button', false, false);
- closeBtn.click(function(e) {
- e.preventDefault();
- var editor = $(this).closest('.editor');
- $(this).closest('.link-area').hide();
- $(this).closest('.bubble').find('ul').show();
- });
- },
- show: function() {
- var tag = $(this).parent().find('.bubble');
- if (!tag.length) {
- tag = utils.html.addTag($(this).parent(), 'div', false, false);
- tag.addClass('jquery-notebook bubble');
- bubble.buildMenu(this, tag);
- }
- tag.show();
- bubble.updateState(this, tag);
- if (!tag.hasClass('active')) {
- tag.addClass('jump');
- } else {
- tag.removeClass('jump');
- }
- bubble.updatePos($(this), tag);
- tag.addClass('active');
- },
- update: function() {
- var tag = $(this).parent().find('.bubble');
- bubble.updateState(this, tag);
- },
- clear: function() {
- var elem = $(this).parent().find('.bubble');
- if (!elem.hasClass('active')) return;
- elem.removeClass('active');
- bubble.hideLinkInput.call(this);
- bubble.showButtons.call(this);
- setTimeout(function() {
- if (elem.hasClass('active')) return;
- elem.hide();
- }, 500);
- },
- hideButtons: function() {
- $(this).parent().find('.bubble').find('ul').hide();
- },
- showButtons: function() {
- $(this).parent().find('.bubble').find('ul').show();
- },
- showLinkInput: function(selection) {
- bubble.hideButtons.call(this);
- var editor = this;
- var elem = $(this).parent().find('.bubble').find('input[type=text]');
- var hasLink = elem.closest('.jquery-notebook').find('button.anchor').hasClass('active');
- elem.unbind('keydown');
- elem.keydown(function(e) {
- var elem = $(this);
- utils.keyboard.isEnter(e, function() {
- e.preventDefault();
- var url = elem.val();
- if (utils.validation.isUrl(url)) {
- e.url = url;
- events.commands.createLink(e, selection);
- bubble.clear.call(editor);
- } else if (url === '' && hasLink) {
- events.commands.removeLink(e, selection);
- bubble.clear.call(editor);
- }
- });
- });
- elem.bind('paste', function(e) {
- var elem = $(this);
- setTimeout(function() {
- var text = elem.val();
- if (/http:\/\/https?:\/\//.test(text)) {
- text = text.substring(7);
- elem.val(text);
- }
- }, 1);
- });
- var linkText = 'http://';
- if (hasLink) {
- var anchor = $(utils.selection.getContainer(selection)).closest('a');
- linkText = anchor.prop('href') || linkText;
- }
- $(this).parent().find('.link-area').show();
- elem.val(linkText).focus();
- },
- hideLinkInput: function() {
- $(this).parent().find('.bubble').find('.link-area').hide();
+ restore: function(range) {
+ if (range) {
+ if (w.getSelection) {
+ var sel = w.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ } else if (d.selection && range.select) { // IE
+ range.select();
}
+ }
},
- actions = {
- bindEvents: function(elem) {
- elem.keydown(rawEvents.keydown);
- elem.keyup(rawEvents.keyup);
- elem.focus(rawEvents.focus);
- elem.bind('paste', events.paste);
- elem.mousedown(rawEvents.mouseClick);
- elem.mouseup(rawEvents.mouseUp);
- elem.mousemove(rawEvents.mouseMove);
- elem.blur(rawEvents.blur);
- $('body').mouseup(function(e) {
- if (e.target == e.currentTarget && cache.isSelecting) {
- rawEvents.mouseUp.call(elem, e);
- }
- });
- },
- setPlaceholder: function(e) {
- if (/^\s*$/.test($(this).text())) {
- $(this).empty();
- var placeholder = utils.html.addTag($(this), 'p').addClass('placeholder');
- placeholder.append($(this).attr('editor-placeholder'));
- utils.html.addTag($(this), 'p', typeof e.focus != 'undefined' ? e.focus : false, true);
- } else {
- $(this).find('.placeholder').remove();
- }
- },
- removePlaceholder: function(e) {
- $(this).find('.placeholder').remove();
- },
- preserveElementFocus: function() {
- var anchorNode = w.getSelection() ? w.getSelection().anchorNode : d.activeElement;
- if (anchorNode) {
- var current = anchorNode.parentNode,
- diff = current !== cache.focusedElement,
- children = this.children,
- elementIndex = 0;
- if (current === this) {
- current = anchorNode;
- }
- for (var i = 0; i < children.length; i++) {
- if (current === children[i]) {
- elementIndex = i;
- break;
- }
- }
- if (diff) {
- cache.focusedElement = current;
- cache.focusedElementIndex = elementIndex;
- }
- }
- },
- prepare: function(elem, customOptions) {
- options = customOptions;
- elem.attr('editor-mode', options.mode);
- elem.attr('editor-placeholder', options.placeholder);
- elem.attr('contenteditable', true);
- elem.css('position', 'relative');
- elem.addClass('jquery-notebook editor');
- actions.setPlaceholder.call(elem, {});
- actions.preserveElementFocus.call(elem);
- if (options.autoFocus === true) {
- var firstP = elem.find('p:not(.placeholder)');
- utils.cursor.set(elem, 0, firstP);
- }
+ getText: function() {
+ var txt = '';
+ if (w.getSelection) {
+ txt = w.getSelection().toString();
+ } else if (d.getSelection) {
+ txt = d.getSelection().toString();
+ } else if (d.selection) {
+ txt = d.selection.createRange().text;
+ }
+ return txt;
+ },
+ clear: function() {
+ if (window.getSelection) {
+ if (window.getSelection().empty) { // Chrome
+ window.getSelection().empty();
+ } else if (window.getSelection().removeAllRanges) { // Firefox
+ window.getSelection().removeAllRanges();
}
+ } else if (document.selection) { // IE?
+ document.selection.empty();
+ }
},
- rawEvents = {
- keydown: function(e) {
- var elem = this;
- if (cache.command && e.which === 65) {
- setTimeout(function() {
- bubble.show.call(elem);
- }, 50);
- }
- utils.keyboard.isCommand(e, function() {
- cache.command = true;
- }, function() {
- cache.command = false;
- });
- utils.keyboard.isShift(e, function() {
- cache.shift = true;
- }, function() {
- cache.shift = false;
- });
- utils.keyboard.isModifier.call(this, e, function(modifier) {
- if (cache.command) {
- events.commands[modifier].call(this, e);
- }
- });
+ getContainer: function(sel) {
+ if (w.getSelection && sel && sel.commonAncestorContainer) {
+ return sel.commonAncestorContainer;
+ } else if (d.selection && sel && sel.parentElement) {
+ return sel.parentElement();
+ }
+ return null;
+ }
+ },
+ validation: {
+ isUrl: function(url) {
+ return (/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/).test(url);
+ }
+ }
+ },
+ bubble = {
+ /*
+ * This is called to position the bubble above the selection.
+ */
+ updatePos: function(editor, elem) {
+ var sel = w.getSelection(),
+ range = sel.getRangeAt(0),
+ boundary = range.getBoundingClientRect(),
+ bubbleWidth = elem.width(),
+ bubbleHeight = elem.height(),
+ offset = editor.offset().left,
+ container = editor.parent().offset(),
+ pos = {
+ x: (boundary.left + boundary.width / 2) - (bubbleWidth / 2) - container.left,
+ y: boundary.top - bubbleHeight - 8 + $(document).scrollTop() - container.top
+ };
+ transform.translate(elem, pos.x, pos.y);
+ },
+ /*
+ * Updates the bubble to set the active formats for the current selection.
+ */
+ updateState: function(editor, elem) {
+ elem.find('button').removeClass('active');
+ var sel = w.getSelection(),
+ formats = [];
+ bubble.checkForFormatting(sel.focusNode, formats);
+ var formatDict = {
+ 'b': 'bold',
+ 'i': 'italic',
+ 'u': 'underline',
+ 'h1': 'h1',
+ 'h2': 'h2',
+ 'a': 'anchor',
+ 'ul': 'ul',
+ 'ol': 'ol'
+ };
+ for (var i = 0; i < formats.length; i++) {
+ var format = formats[i];
+ elem.find('button.' + formatDict[format]).addClass('active');
+ }
+ },
+ /*
+ * Recursively navigates upwards in the DOM to find all the format
+ * tags enclosing the selection.
+ */
+ checkForFormatting: function(currentNode, formats) {
+ var validFormats = ['b', 'i', 'u', 'h1', 'h2', 'ol', 'ul', 'li', 'a'];
+ if (currentNode.nodeName === '#text' ||
+ validFormats.indexOf(currentNode.nodeName.toLowerCase()) != -1) {
+ if (currentNode.nodeName != '#text') {
+ formats.push(currentNode.nodeName.toLowerCase());
+ }
+ bubble.checkForFormatting(currentNode.parentNode, formats);
+ }
+ },
+ buildMenu: function(editor, elem) {
+ var ul = utils.html.addTag(elem, 'ul', false, false);
+ for (var cmd in options.modifiers) {
+ var li = utils.html.addTag(ul, 'li', false, false);
+ var btn = utils.html.addTag(li, 'button', false, false);
+ btn.attr('editor-command', options.modifiers[cmd]);
+ btn.addClass(options.modifiers[cmd]);
+ }
+ elem.find('button').click(function(e) {
+ e.preventDefault();
+ var cmd = $(this).attr('editor-command');
+ events.commands[cmd].call(editor, e);
+ });
+ var linkArea = utils.html.addTag(elem, 'div', false, false);
+ linkArea.addClass('link-area');
+ var linkInput = utils.html.addTag(linkArea, 'input', false, false);
+ linkInput.attr({
+ type: 'text'
+ });
+ var closeBtn = utils.html.addTag(linkArea, 'button', false, false);
+ closeBtn.click(function(e) {
+ e.preventDefault();
+ var editor = $(this).closest('.editor');
+ $(this).closest('.link-area').hide();
+ $(this).closest('.bubble').find('ul').show();
+ });
+ },
+ show: function() {
+ var tag = $(this).parent().find('.bubble');
+ if (!tag.length) {
+ tag = utils.html.addTag($(this).parent(), 'div', false, false);
+ tag.addClass('jquery-notebook bubble');
+ bubble.buildMenu(this, tag);
+ }
+ tag.show();
+ bubble.updateState(this, tag);
+ if (!tag.hasClass('active')) {
+ tag.addClass('jump');
+ } else {
+ tag.removeClass('jump');
+ }
+ bubble.updatePos($(this), tag);
+ tag.addClass('active');
+ },
+ update: function() {
+ var tag = $(this).parent().find('.bubble');
+ bubble.updateState(this, tag);
+ },
+ clear: function() {
+ var elem = $(this).parent().find('.bubble');
+ if (!elem.hasClass('active')) return;
+ elem.removeClass('active');
+ bubble.hideLinkInput.call(this);
+ bubble.showButtons.call(this);
+ setTimeout(function() {
+ if (elem.hasClass('active')) return;
+ elem.hide();
+ }, 500);
+ },
+ hideButtons: function() {
+ $(this).parent().find('.bubble').find('ul').hide();
+ },
+ showButtons: function() {
+ $(this).parent().find('.bubble').find('ul').show();
+ },
+ showLinkInput: function(selection) {
+ bubble.hideButtons.call(this);
+ var editor = this;
+ var elem = $(this).parent().find('.bubble').find('input[type=text]');
+ var hasLink = elem.closest('.jquery-notebook').find('button.anchor').hasClass('active');
+ elem.unbind('keydown');
+ elem.keydown(function(e) {
+ var elem = $(this);
+ utils.keyboard.isEnter(e, function() {
+ e.preventDefault();
+ var url = elem.val();
+ if (utils.validation.isUrl(url)) {
+ e.url = url;
+ events.commands.createLink(e, selection);
+ bubble.clear.call(editor);
+ } else if (url === '' && hasLink) {
+ events.commands.removeLink(e, selection);
+ bubble.clear.call(editor);
+ }
+ });
+ });
+ elem.bind('paste', function(e) {
+ var elem = $(this);
+ setTimeout(function() {
+ var text = elem.val();
+ if (/http:\/\/https?:\/\//.test(text)) {
+ text = text.substring(7);
+ elem.val(text);
+ }
+ }, 1);
+ });
+ var linkText = 'http://';
+ if (hasLink) {
+ var anchor = $(utils.selection.getContainer(selection)).closest('a');
+ linkText = anchor.prop('href') || linkText;
+ }
+ $(this).parent().find('.link-area').show();
+ elem.val(linkText).focus();
+ },
+ hideLinkInput: function() {
+ $(this).parent().find('.bubble').find('.link-area').hide();
+ }
+ },
+ actions = {
+ bindEvents: function(elem) {
+ elem.keydown(rawEvents.keydown);
+ elem.keyup(rawEvents.keyup);
+ elem.focus(rawEvents.focus);
+ elem.bind('paste', events.paste);
+ elem.mousedown(rawEvents.mouseClick);
+ elem.mouseup(rawEvents.mouseUp);
+ elem.mousemove(rawEvents.mouseMove);
+ elem.blur(rawEvents.blur);
+ $('body').mouseup(function(e) {
+ if (e.target == e.currentTarget && cache.isSelecting) {
+ rawEvents.mouseUp.call(elem, e);
+ }
+ });
+ },
+ setPlaceholder: function(e) {
+ if (/^\s*$/.test($(this).text())) {
+ $(this).empty();
+ var placeholder = utils.html.addTag($(this), 'p').addClass('placeholder');
+ placeholder.append($(this).attr('editor-placeholder'));
+ utils.html.addTag($(this), 'p', typeof e.focus != 'undefined' ? e.focus : false, true);
+ } else {
+ $(this).find('.placeholder').remove();
+ }
+ },
+ removePlaceholder: function(e) {
+ $(this).find('.placeholder').remove();
+ },
+ preserveElementFocus: function() {
+ var anchorNode = w.getSelection() ? w.getSelection().anchorNode : d.activeElement;
+ if (anchorNode) {
+ var current = anchorNode.parentNode,
+ diff = current !== cache.focusedElement,
+ children = this.children,
+ elementIndex = 0;
+ if (current === this) {
+ current = anchorNode;
+ }
+ for (var i = 0; i < children.length; i++) {
+ if (current === children[i]) {
+ elementIndex = i;
+ break;
+ }
+ }
+ if (diff) {
+ cache.focusedElement = current;
+ cache.focusedElementIndex = elementIndex;
+ }
+ }
+ },
+ prepare: function(elem, customOptions) {
+ elem.wrap('
');
+ options = customOptions;
+ elem.attr('editor-mode', options.mode);
+ elem.attr('editor-placeholder', options.placeholder);
+ elem.attr('contenteditable', true);
+ elem.css('position', 'relative');
+ elem.addClass('jquery-notebook editor');
+ actions.setPlaceholder.call(elem, {});
+ actions.preserveElementFocus.call(elem);
+ if (options.autoFocus === true) {
+ var firstP = elem.find('p:not(.placeholder)');
+ utils.cursor.set(elem, 0, firstP);
+ }
+ }
+ },
+ rawEvents = {
+ keydown: function(e) {
+ var elem = this;
+ if (cache.command && e.which === 65) {
+ setTimeout(function() {
+ bubble.show.call(elem);
+ }, 50);
+ }
+ utils.keyboard.isCommand(e, function() {
+ cache.command = true;
+ }, function() {
+ cache.command = false;
+ });
+ utils.keyboard.isShift(e, function() {
+ cache.shift = true;
+ }, function() {
+ cache.shift = false;
+ });
+ utils.keyboard.isModifier.call(this, e, function(modifier) {
+ if (cache.command) {
+ events.commands[modifier].call(this, e);
+ }
+ });
- if (cache.shift) {
- utils.keyboard.isArrow.call(this, e, function() {
- setTimeout(function() {
- var txt = utils.selection.getText();
- if (txt !== '') {
- bubble.show.call(elem);
- } else {
- bubble.clear.call(elem);
- }
- }, 100);
- });
- } else {
- utils.keyboard.isArrow.call(this, e, function() {
- bubble.clear.call(elem);
- });
- }
+ if (cache.shift) {
+ utils.keyboard.isArrow.call(this, e, function() {
+ setTimeout(function() {
+ var txt = utils.selection.getText();
+ if (txt !== '') {
+ bubble.show.call(elem);
+ } else {
+ bubble.clear.call(elem);
+ }
+ }, 100);
+ });
+ } else {
+ utils.keyboard.isArrow.call(this, e, function() {
+ bubble.clear.call(elem);
+ });
+ }
- if (e.which === 13) {
- events.enterKey.call(this, e);
- }
- if (e.which === 27) {
- bubble.clear.call(this);
- }
- if (e.which === 86) {
- events.paste.call(this, e);
- }
- },
- keyup: function(e) {
- utils.keyboard.isCommand(e, function() {
- cache.command = false;
- }, function() {
- cache.command = true;
- });
- actions.preserveElementFocus.call(this);
- actions.removePlaceholder.call(this);
+ if (e.which === 13) {
+ events.enterKey.call(this, e);
+ }
+ if (e.which === 27) {
+ bubble.clear.call(this);
+ }
+ if (e.which === 86) {
+ events.paste.call(this, e);
+ }
+ },
+ keyup: function(e) {
+ utils.keyboard.isCommand(e, function() {
+ cache.command = false;
+ }, function() {
+ cache.command = true;
+ });
+ actions.preserveElementFocus.call(this);
+ actions.removePlaceholder.call(this);
- /*
- * This breaks the undo when the whole text is deleted but so far
- * it is the only way that I fould to solve the more serious bug
- * that the editor was losing the p elements after deleting the whole text
- */
- if (/^\s*$/.test($(this).text())) {
- $(this).empty();
- utils.html.addTag($(this), 'p', true, true);
- }
- },
- focus: function(e) {
- cache.command = false;
- cache.shift = false;
- },
- mouseClick: function(e) {
- var elem = this;
- cache.isSelecting = true;
- if (e.button === 2) {
- setTimeout(function() {
- bubble.show.call(elem);
- }, 50);
- e.preventDefault();
- return;
- }
- if ($(this).find('.bubble:visible').length) {
- var bubbleTag = $(this).find('.bubble:visible'),
- bubbleX = bubbleTag.offset().left,
- bubbleY = bubbleTag.offset().top,
- bubbleWidth = bubbleTag.width(),
- bubbleHeight = bubbleTag.height();
- if (mouseX > bubbleX && mouseX < bubbleX + bubbleWidth &&
- mouseY > bubbleY && mouseY < bubbleY + bubbleHeight) {
- return;
- }
- }
- },
- mouseUp: function(e) {
- e.preventDefault();
- var elem = this;
- cache.isSelecting = false;
- setTimeout(function() {
- var s = utils.selection.save();
- if (s.collapsed) {
- bubble.clear.call(elem);
- } else {
- bubble.show.call(elem);
- }
- }, 50);
- },
- mouseMove: function(e) {
- mouseX = e.pageX;
- mouseY = e.pageY;
- },
- blur: function(e) {
- actions.setPlaceholder.call(this, {
- focus: false
- });
- }
+ /*
+ * This breaks the undo when the whole text is deleted but so far
+ * it is the only way that I fould to solve the more serious bug
+ * that the editor was losing the p elements after deleting the whole text
+ */
+ if (/^\s*$/.test($(this).text())) {
+ $(this).empty();
+ utils.html.addTag($(this), 'p', true, true);
+ }
+ },
+ focus: function(e) {
+ cache.command = false;
+ cache.shift = false;
+ },
+ mouseClick: function(e) {
+ var elem = this;
+ cache.isSelecting = true;
+ if (e.button === 2) {
+ setTimeout(function() {
+ bubble.show.call(elem);
+ }, 50);
+ e.preventDefault();
+ return;
+ }
+ if ($(this).find('.bubble:visible').length) {
+ var bubbleTag = $(this).find('.bubble:visible'),
+ bubbleX = bubbleTag.offset().left,
+ bubbleY = bubbleTag.offset().top,
+ bubbleWidth = bubbleTag.width(),
+ bubbleHeight = bubbleTag.height();
+ if (mouseX > bubbleX && mouseX < bubbleX + bubbleWidth &&
+ mouseY > bubbleY && mouseY < bubbleY + bubbleHeight) {
+ return;
+ }
+ }
+ },
+ mouseUp: function(e) {
+ e.preventDefault();
+ var elem = this;
+ cache.isSelecting = false;
+ setTimeout(function() {
+ var s = utils.selection.save();
+ if (s.collapsed) {
+ bubble.clear.call(elem);
+ } else {
+ bubble.show.call(elem);
+ }
+ }, 50);
+ },
+ mouseMove: function(e) {
+ mouseX = e.pageX;
+ mouseY = e.pageY;
+ },
+ blur: function(e) {
+ actions.setPlaceholder.call(this, {
+ focus: false
+ });
+ }
+ },
+ events = {
+ commands: {
+ bold: function(e) {
+ e.preventDefault();
+ d.execCommand('bold', false);
+ bubble.update.call(this);
},
- events = {
- commands: {
- bold: function(e) {
- e.preventDefault();
- d.execCommand('bold', false);
- bubble.update.call(this);
- },
- italic: function(e) {
- e.preventDefault();
- d.execCommand('italic', false);
- bubble.update.call(this);
- },
- underline: function(e) {
- e.preventDefault();
- d.execCommand('underline', false);
- bubble.update.call(this);
- },
- anchor: function(e) {
- e.preventDefault();
- var s = utils.selection.save();
- bubble.showLinkInput.call(this, s);
- },
- createLink: function(e, s) {
- utils.selection.restore(s);
- d.execCommand('createLink', false, e.url);
- bubble.update.call(this);
- },
- removeLink: function(e, s) {
- var el = $(utils.selection.getContainer(s)).closest('a');
- el.contents().first().unwrap();
- },
- h1: function(e) {
- e.preventDefault();
- if ($(window.getSelection().anchorNode.parentNode).is('h1')) {
- d.execCommand('formatBlock', false, '
');
- } else {
- d.execCommand('formatBlock', false, '
');
- }
- bubble.update.call(this);
- },
- h2: function(e) {
- e.preventDefault();
- if ($(window.getSelection().anchorNode.parentNode).is('h2')) {
- d.execCommand('formatBlock', false, '
');
- } else {
- d.execCommand('formatBlock', false, '
');
- }
- bubble.update.call(this);
- },
- ul: function(e) {
- e.preventDefault();
- d.execCommand('insertUnorderedList', false);
- bubble.update.call(this);
- },
- ol: function(e) {
- e.preventDefault();
- d.execCommand('insertOrderedList', false);
- bubble.update.call(this);
- },
- undo: function(e) {
- e.preventDefault();
- d.execCommand('undo', false);
- }
- },
- enterKey: function(e) {
- if ($(this).attr('editor-mode') === 'inline') {
- e.preventDefault();
- return;
- }
- },
- paste: function(e) {
- var elem = $(this);
- setTimeout(function() {
- elem.find('*').each(function() {
- var current = $(this);
- $.each(this.attributes, function() {
- if (this.name !== 'class' || !current.hasClass('placeholder')) {
- current.removeAttr(this.name);
- }
- });
- });
- }, 100);
- }
- };
-
- $.fn.notebook = function(options) {
- options = $.extend({}, $.fn.notebook.defaults, options);
- actions.prepare(this, options);
- actions.bindEvents(this);
- return this;
+ italic: function(e) {
+ e.preventDefault();
+ d.execCommand('italic', false);
+ bubble.update.call(this);
+ },
+ underline: function(e) {
+ e.preventDefault();
+ d.execCommand('underline', false);
+ bubble.update.call(this);
+ },
+ anchor: function(e) {
+ e.preventDefault();
+ var s = utils.selection.save();
+ bubble.showLinkInput.call(this, s);
+ },
+ createLink: function(e, s) {
+ utils.selection.restore(s);
+ d.execCommand('createLink', false, e.url);
+ bubble.update.call(this);
+ },
+ removeLink: function(e, s) {
+ var el = $(utils.selection.getContainer(s)).closest('a');
+ el.contents().first().unwrap();
+ },
+ h1: function(e) {
+ e.preventDefault();
+ if ($(window.getSelection().anchorNode.parentNode).is('h1')) {
+ d.execCommand('formatBlock', false, '
');
+ } else {
+ d.execCommand('formatBlock', false, '
');
+ }
+ bubble.update.call(this);
+ },
+ h2: function(e) {
+ e.preventDefault();
+ if ($(window.getSelection().anchorNode.parentNode).is('h2')) {
+ d.execCommand('formatBlock', false, '
');
+ } else {
+ d.execCommand('formatBlock', false, '
');
+ }
+ bubble.update.call(this);
+ },
+ ul: function(e) {
+ e.preventDefault();
+ d.execCommand('insertUnorderedList', false);
+ bubble.update.call(this);
+ },
+ ol: function(e) {
+ e.preventDefault();
+ d.execCommand('insertOrderedList', false);
+ bubble.update.call(this);
+ },
+ undo: function(e) {
+ e.preventDefault();
+ d.execCommand('undo', false);
+ }
+ },
+ enterKey: function(e) {
+ if ($(this).attr('editor-mode') === 'inline') {
+ e.preventDefault();
+ return;
+ }
+ },
+ paste: function(e) {
+ var elem = $(this);
+ setTimeout(function() {
+ elem.find('*').each(function() {
+ var current = $(this);
+ $.each(this.attributes, function() {
+ if (this.name !== 'class' || !current.hasClass('placeholder')) {
+ current.removeAttr(this.name);
+ }
+ });
+ });
+ }, 100);
+ }
};
- $.fn.notebook.defaults = {
- autoFocus: false,
- placeholder: 'Your text here...',
- mode: 'multiline',
- modifiers: ['bold', 'italic', 'underline', 'h1', 'h2', 'ol', 'ul', 'anchor']
- };
+ $.fn.notebook = function(options) {
+ options = $.extend({}, $.fn.notebook.defaults, options);
+ actions.prepare(this, options);
+ actions.bindEvents(this);
+ return this;
+ };
+
+ $.fn.notebook.defaults = {
+ autoFocus: false,
+ placeholder: 'Your text here...',
+ mode: 'multiline',
+ modifiers: ['bold', 'italic', 'underline', 'h1', 'h2', 'ol', 'ul', 'anchor']
+ };
-})(jQuery, document, window);
+})(jQuery, document, window);
\ No newline at end of file