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`.
This commit is contained in:
Martin Zimmermann 2014-03-27 10:10:28 +01:00
parent 638ddc6359
commit 09451ff707
7 changed files with 93 additions and 57 deletions

View File

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

View File

@ -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"];
}

View File

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

24
isso/js/app/lib/editor.js Normal file
View File

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

View File

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

View File

@ -4,7 +4,7 @@
</div>
<div class="form-wrapper">
<div class="textarea-wrapper">
<textarea name="text" rows="2" placeholder="{{ i18n-postbox-text }}"></textarea>
<div class="textarea placeholder" contenteditable="true">{{ i18n-postbox-text }}</div>
</div>
<section class="auth-section">
<p class="input-wrapper">

View File

@ -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><br><\/div>/gi, '<br>')
.replace(/<div>/gi,'<br>')
.replace(/<br>/gi, '\n');
return _.textContent.trim();
};
return {
cookie: cookie,
ago: ago
ago: ago,
text: text
};
});