From 09451ff707353e3203eb27fd15a9e0a7224e744f Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Thu, 27 Mar 2014 10:10:28 +0100 Subject: [PATCH] replace textarea with a content-editable div Mainly because of the sluggish auto-resize "feature" which comes for free when using a content-editable div. If you use a custom CSS, make sure you replace textarea (element with .textarea (class) and set `white-space: pre`. --- isso/css/isso.scss | 22 +++++++++--- isso/js/app/isso.js | 66 ++++++++++++++++++++--------------- isso/js/app/lib.js | 2 +- isso/js/app/lib/editor.js | 24 +++++++++++++ isso/js/app/lib/fancy.js | 20 ----------- isso/js/app/text/postbox.html | 2 +- isso/js/app/utils.js | 14 ++++++-- 7 files changed, 93 insertions(+), 57 deletions(-) create mode 100644 isso/js/app/lib/editor.js delete mode 100644 isso/js/app/lib/fancy.js diff --git a/isso/css/isso.scss b/isso/css/isso.scss index 94dd227..d610d71 100644 --- a/isso/css/isso.scss +++ b/isso/css/isso.scss @@ -27,6 +27,16 @@ a { font-weight: bold; font-family: "Helvetica", Arial, sans-serif; } + + .textarea { + white-space: pre; + outline: 0px solid transparent; + } + + .textarea.placeholder { + color: #AAA; + } + } .parent-highlight { @@ -82,6 +92,10 @@ a { } } + > .textarea-wrapper .textarea { + margin-top: 0.2em; + } + > div.text { p { @@ -98,9 +112,10 @@ a { } - > div.textarea-wrapper textarea { + > div.textarea-wrapper .textarea { @include fill-parent; @include isso-shadow; + min-height: 48px; font: inherit; } @@ -155,12 +170,9 @@ a { > .form-wrapper { @include span-columns(10 of 11); - textarea { + .textarea { @include fill-parent; @include isso-shadow; - @include placeholder { - color: #AAA; - } min-height: 48px; font: inherit; diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index ea4895c..580cceb 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -15,7 +15,7 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m $(".avatar > svg", el).replace(lib.identicons.blank(4, 48)); // on text area focus, generate identicon from IP address - $(".textarea-wrapper > textarea", el).on("focus", function() { + $(".textarea-wrapper > .textarea", el).on("focus", function() { if ($(".avatar svg", el).getAttribute("className") === "blank") { $(".avatar svg", el).replace( lib.identicons.generate(lib.pbkdf2(api.remote_addr(), api.salt, 1000, 6), 4, 48)); @@ -45,8 +45,10 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m el.onsuccess = function() {}; el.validate = function() { - if ($("textarea", this).value.length < 3) { - $("textarea", this).focus(); + if (utils.text($(".textarea", this).innerHTML).length < 3 || + $(".textarea", this).classList.contains("placeholder")) + { + $(".textarea", this).focus(); return false; } return true; @@ -62,14 +64,13 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m api.create($("#isso-thread").getAttribute("data-isso-id"), { author: $("[name=author]", el).value || null, email: $("[name=email]", el).value || null, - text: $("textarea", el).value, + text: utils.text($(".textarea", el).innerHTML), parent: parent || null }).then(function(comment) { $("[name=author]", el).value = ""; $("[name=email]", el).value = ""; - $("textarea", el).value = ""; - $("textarea", el).rows = 2; - $("textarea", el).blur(); + $(".textarea", el).innerHTML = ""; + $(".textarea", el).blur(); insert(comment, true); if (parent !== null) { @@ -79,8 +80,7 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m }); }); - // copy'n'paste sluggy automagically dynamic textarea resize - lib.fancy.autoresize($("textarea", el), 48); + lib.editorify($(".textarea", el)); return el; }; @@ -136,7 +136,7 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m function(toggler) { form = footer.insertAfter(new Postbox(comment.id)); form.onsuccess = function() { toggler.next(); }; - $("textarea", form).focus(); + $(".textarea", form).focus(); $("a.reply", footer).textContent = msgs["comment-close"]; }, function() { @@ -186,35 +186,45 @@ define(["app/text/html", "app/dom", "app/utils", "app/config", "app/api", "app/m edit.textContent = msgs["comment-save"]; edit.insertAfter($.new("a.cancel", msgs["comment-cancel"])).on("click", function() { - text.textContent = ""; - text.className = "text"; - text.append(comment.text); + toggler.canceled = true; toggler.next(); }); + toggler.canceled = false; api.view(comment.id, 1).then(function(rv) { - var textarea = $.new("textarea", rv.text); - lib.fancy.autoresize(textarea, 48); - text.className = "textarea-wrapper"; + var textarea = lib.editorify($.new("div.textarea")); + + textarea.textContent = rv.text; + textarea.focus(); + + text.classList.remove("text"); + text.classList.add("textarea-wrapper"); + text.textContent = ""; text.append(textarea); - textarea.focus(); }); }, function(toggler) { - var textarea = $("textarea", text); - if (textarea && textarea.value.length < 3) { - textarea.focus(); - toggler.wait(); - return; - } else if (textarea) { - api.modify(comment.id, {"text": textarea.value}).then(function(rv) { - text.innerHTML = rv.text; - text.className = "text"; - comment.text = rv.text; - }); + var textarea = $(".textarea", text); + + if (! toggler.canceled && textarea !== null) { + if (utils.text(textarea.innerHTML).length < 3) { + textarea.focus(); + toggler.wait(); + return; + } else { + api.modify(comment.id, {"text": utils.text(textarea.innerHTML)}).then(function(rv) { + text.innerHTML = rv.text; + comment.text = rv.text; + }); + } + } else { + text.innerHTML = comment.text; } + text.classList.remove("textarea-wrapper"); + text.classList.add("text"); + $("a.cancel", footer).remove(); $("a.edit", footer).textContent = msgs["comment-edit"]; } diff --git a/isso/js/app/lib.js b/isso/js/app/lib.js index e452048..4214131 100644 --- a/isso/js/app/lib.js +++ b/isso/js/app/lib.js @@ -1,6 +1,6 @@ define(function (require) { return { - fancy: require("app/lib/fancy"), + editorify: require("app/lib/editor"), identicons: require("app/lib/identicons"), pbkdf2: require("app/lib/pbkdf2"), sha1: require("app/lib/sha1") diff --git a/isso/js/app/lib/editor.js b/isso/js/app/lib/editor.js new file mode 100644 index 0000000..ef49e9d --- /dev/null +++ b/isso/js/app/lib/editor.js @@ -0,0 +1,24 @@ +define(["app/dom", "app/markup"], function($, Mark) { + "use strict"; + + return function(el) { + el.setAttribute("contentEditable", true); + + el.on("focus", function() { + if (el.classList.contains("placeholder")) { + el.innerHTML = ""; + el.classList.remove("placeholder"); + } + }); + + el.on("blur", function() { + if (el.textContent.length === 0) { + el.textContent = Mark.up("{{ i18n-postbox-text }}"); + el.classList.add("placeholder"); + } + }); + + return el; + }; + +}); \ No newline at end of file diff --git a/isso/js/app/lib/fancy.js b/isso/js/app/lib/fancy.js deleted file mode 100644 index a3f9a48..0000000 --- a/isso/js/app/lib/fancy.js +++ /dev/null @@ -1,20 +0,0 @@ -define(function() { - - "use strict"; - - var autoresize = function(textarea, minheight) { - var offset= !window.opera ? (textarea.offsetHeight - textarea.clientHeight) : (textarea.offsetHeight + parseInt(window.getComputedStyle(textarea, null).getPropertyValue('border-top-width'))); - ["keyup", "focus"].forEach(function(event) { - textarea.on(event, function() { - if ((textarea.scrollHeight + offset ) > minheight) { - textarea.style.height = "auto"; - textarea.style.height = (textarea.scrollHeight + offset ) + 'px'; - } - }); - }); - }; - - return { - autoresize: autoresize - }; -}); diff --git a/isso/js/app/text/postbox.html b/isso/js/app/text/postbox.html index a72f71f..229a879 100644 --- a/isso/js/app/text/postbox.html +++ b/isso/js/app/text/postbox.html @@ -4,7 +4,7 @@
- +
{{ i18n-postbox-text }}

diff --git a/isso/js/app/utils.js b/isso/js/app/utils.js index 1427d0e..4cf56cb 100644 --- a/isso/js/app/utils.js +++ b/isso/js/app/utils.js @@ -1,4 +1,5 @@ define(["app/markup"], function(Mark) { + "use strict"; // return `cookie` string if set var cookie = function(cookie) { @@ -7,7 +8,7 @@ define(["app/markup"], function(Mark) { var ago = function(localTime, date) { - var secs = ((localTime.getTime() - date.getTime()) / 1000) + var secs = ((localTime.getTime() - date.getTime()) / 1000); if (isNaN(secs) || secs < 0 ) { secs = 0; @@ -39,8 +40,17 @@ define(["app/markup"], function(Mark) { i18n("date-year", Math.ceil(days / 365.25)); }; + var text = function(html) { + var _ = document.createElement("div"); + _.innerHTML = html.replace(/


<\/div>/gi, '
') + .replace(/
/gi,'
') + .replace(/
/gi, '\n'); + return _.textContent.trim(); + }; + return { cookie: cookie, - ago: ago + ago: ago, + text: text }; });