Skip to content

Instantly share code, notes, and snippets.

@darobin
Last active May 7, 2016 11:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darobin/8a128f05106d0e02717b to your computer and use it in GitHub Desktop.
Save darobin/8a128f05106d0e02717b to your computer and use it in GitHub Desktop.
The Twitter Box
<div id="box" contentEditable="minimal"></div>
<script>
var box = document.getElementById("box")
, compoRange = null
, compoText = null
;
function insertText (txt) {
var sel = window.getSelection();
if (!sel) return;
var range = sel.getRangeAt(0);
if (!range) return;
sel.deleteFromDocument();
range.insertNode(document.createTextNode(txt));
range.collapse();
}
box.addEventListener("beforeinput", function (ev) {
insertText(ev.data);
});
box.addEventListener("delete", function (ev) {
ev.data.deleteContents();
});
box.addEventListener("newline", function (ev) {
insertText("\n");
});
box.addEventListener("compositionstart", function (ev) {
var sel = window.getSelection();
if (!sel) return;
var range = sel.getRangeAt(0);
if (!range) return;
sel.deleteFromDocument();
compoText = document.createTextNode(ev.data);
range.insertNode(compoText);
range.collapse();
compoRange = new Range();
compoRange.setStart(compoText, 0);
compoRange.setEnd(compoText, compoText.length);
compoRange.style.textDecoration = "underline";
});
box.addEventListener("compositionupdate", function (ev) {
compoText.data = ev.data;
compoRange.setEnd(compoText, compoText.length);
});
box.addEventListener("compositionend", function (ev) {
compoRange.detach();
compoRange.deleteContents();
insertText(ev.data);
});
</script>
<!--
Can we make it so that an empty one like this has by default a min-height of
line-height and is clickable everywhere for focus? Thanks.
-->
<div id="box" contentEditable="minimal"></div>
<script>
var box = document.getElementById("box")
, excessRange = null
, compoRange = null
, compoText = null
;
// Check that the text does not exceed 140 character, redden it otherwise
function checkExcess () {
// Range.detach() is used to remove a Range to which styling has been applied.
// Maybe it can be done automatically when the object goes out of scope, or through
// some other method.
if (excessRange) excessRange.detach();
// brutally normalise box to a single Text child node
box.textContent = box.textContent;
var txtLen = box.textContent.length;
if (txtLen <= 140) return;
var tooMuch = txtLen - 140;
excessRange = new Range();
excessRange.setStart(box.firstChild, txtLen - tooMuch);
excessRange.setEnd(box.firstChild, txtLen);
// We add a CSSStyleDeclaration on Range
excessRange.style.color = "#f00";
excessRange.style.background = "#fe6a65";
}
// Insert text at cursor
function insertText (txt) {
// It is quite possible that Selection.replace(node|text...) would be very nice here
var sel = window.getSelection();
if (!sel) return;
var range = sel.getRangeAt(0);
if (!range) return;
// Delete whatever is selected, even if there are multiple ranges.
// We assume the UA prevents the selection from extending outside of the
// edit box while it has focus (this should be made a normative requirement).
sel.deleteFromDocument();
range.insertNode(document.createTextNode(txt));
// We need to explictly collapse because insertion extends the Range
range.collapse();
// ISSUE: when you have a multi-range selection due to linear bidi selection being in
// effect, what happens when you call deleteFromDocument()? I am *presuming* that after
// that operation only one range remains, and it is a collapsed range positioned wherever
// the cursor should logically go based on the platform's behaviour. This is
// underspecified in Selection
checkExcess();
}
// Assumptions for beforeinput:
// - the input event never gets triggered
// - pasting and dropping will trigger this, so we can ignore them
// ISSUE: but if something that isn't plain text is pasted/dropped we need to filter it so we
// can't ignore. At the same time, if we listen to it we get *both*. Should we just always
// filter here? Or modify the data in place in paste/drop without inserting?
box.addEventListener("beforeinput", function (ev) {
insertText(ev.data);
});
// Assumptions for delete:
// - it is up to the platform to know specific conventions for delete backwards/forwards,
// delete word/line/paragraph/etc., knowing about the selection, and to map this correctly
// - the data on the event is a Range
box.addEventListener("delete", function (ev) {
ev.data.deleteContents();
checkExcess();
});
// Assumptions for newline:
// - it is up to the platform to know specific conventions for the insertion of a newline, a
// new item, OK the control, or whatever and to map this correctly using modifiers
// - (I'm not sure the above is entirely possible though for this case. Still, it beats getting
// all the keyboard events…)
// We don't care about the details, we just insert a newline
box.addEventListener("newline", function (ev) {
insertText("\n");
});
// Assumptions for composition events:
// - you are guaranteed a compositionend event once you've seen compositionstart, and the order
// is deterministic.
box.addEventListener("compositionstart", function (ev) {
var sel = window.getSelection();
if (!sel) return;
var range = sel.getRangeAt(0);
if (!range) return;
sel.deleteFromDocument();
compoText = document.createTextNode(ev.data);
range.insertNode(compoText);
range.collapse();
// the ongoing composition is rendered underlined
compoRange = new Range();
compoRange.setStart(compoText, 0);
compoRange.setEnd(compoText, compoText.length);
compoRange.style.textDecoration = "underline";
// ignore excess UI while composing
if (excessRange) excessRange.detach();
});
box.addEventListener("compositionupdate", function (ev) {
compoText.data = ev.data;
compoRange.setEnd(compoText, compoText.length);
});
box.addEventListener("compositionend", function (ev) {
compoRange.detach(); // might not be needed given we're deleting contents anyway
compoRange.deleteContents();
insertText(ev.data);
});
</script>
@samourai
Copy link

samourai commented May 7, 2016

i want to use your code for a project using a contenteditable like twiiter, but it does not work. can give me some solution .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment