var string = "";
var escapeSequenceRegex = /(%.{2})+/gi;
onload = function() {
document.getElementById("raw").addEventListener("keydown", onKeydown)
document.getElementById("formatted").addEventListener("keydown", onKeydown)
function onKeydown(event) {
if (event.which === 13) {
string += "%0A";
update(event.target);
event.preventDefault();
}
}
}
function rawUpdated() {
string = document.getElementById("raw").innerText;
update(document.getElementById("raw"));
}
function formattedUpdated() {
string = encodeURIComponent(document.getElementById("formatted").innerText);
update(document.getElementById("formatted"));
}
function update(input) {
// Save caret position
var caretPos = getCaretPos(input);
// Create HTML for raw input
document.getElementById("raw").innerHTML =
string
.replace(escapeSequenceRegex, function(str) {
try {
decodeURIComponent(str);
return "" + str + "";
} catch(e) {
return "" + str + "";
}
})
.replace(/%.?$/, "$&"); // Unfinished escape sequence
// Create HTML for formatted input
var formatted = "";
for (var i = 0; i < string.length; i++) {
// URL escape sequence reached
if (string[i] === "%") {
var sequence = string.substr(i).match(escapeSequenceRegex);
// Contains valid characters
if (sequence) {
sequence = sequence[0];
try {
var decoded = decodeURIComponent(sequence)
.replace(/\n/g, "
");
formatted += "" + decoded + "";
} catch (e) {
formatted += "" + sequence + "";
}
i += sequence.length - 1;
// Unfinished escape sequence
} else {
formatted += "" + string.substr(i, 3) + "";
i += 2;
}
} else {
formatted += string[i];
}
}
document.getElementById("formatted").innerHTML = formatted;
// Restore caret position
setCaretPos(input, caretPos);
document.querySelectorAll("#raw > span, #formatted > span").forEach(function(escaped) {
escaped.onmouseenter = highlightSegment;
escaped.onmouseleave = unhighlightAll;
});
}
function highlightSegment(event) {
// Get node index (https://stackoverflow.com/questions/5913927/get-child-node-index#comment39914731_5913984)
var node = event.target;
for (var i = 0; (node = node.previousElementSibling); i++);
document.getElementById("formatted").children[i].className += " highlighted";
document.getElementById("raw").children[i].className += " highlighted";
}
function unhighlightAll() {
[].slice.call(document.getElementsByClassName("highlighted")).forEach(function(elem) {
elem.className = elem.className.replace("highlighted", "");
});
}
// why is manipulating the caret in a contenteditable div so painful
// https://stackoverflow.com/a/12500791
function getCaretPos(element) {
var range = window.getSelection().getRangeAt(0);
var selected = range.toString().length;
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
return preCaretRange.toString().length - selected;
}
// https://stackoverflow.com/a/36953852
function setCaretPos(element, pos) {
// Loop through all child nodes
for (var i = 0; i < element.childNodes.length; i++){
var node = element.childNodes[i];
if (node.nodeType === Node.TEXT_NODE) {
if (node.length >= pos){
// finally add our range
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1; // we are done
} else {
pos -= node.length;
}
} else {
pos = setCaretPos(node, pos);
if (pos === -1) {
return -1; // no need to finish the for loop
}
}
}
return pos; // needed because of recursion stuff
}