Initial commit
This commit is contained in:
commit
7327876db2
BIN
fonts/JetBrainsMono-Regular.woff2
Executable file
BIN
fonts/JetBrainsMono-Regular.woff2
Executable file
Binary file not shown.
BIN
fonts/JetBrainsMonoNL-Regular.ttf
Executable file
BIN
fonts/JetBrainsMonoNL-Regular.ttf
Executable file
Binary file not shown.
28
index.html
Executable file
28
index.html
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>URL Editor</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>URL Editor</h1>
|
||||||
|
Edit URL encoded strings
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<label for="raw">Raw</label>
|
||||||
|
<br>
|
||||||
|
<div contenteditable spellcheck="false" id="raw" class="textarea" oninput="rawUpdated()"></div>
|
||||||
|
|
||||||
|
<label for="formatted">Formatted</label>
|
||||||
|
<br>
|
||||||
|
<div contenteditable spellcheck="false" id="formatted" class="textarea" oninput="formattedUpdated()"></div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
144
main.js
Executable file
144
main.js
Executable file
@ -0,0 +1,144 @@
|
|||||||
|
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 "<span class='escaped'>" + str + "</span>";
|
||||||
|
} catch(e) {
|
||||||
|
return "<span class='invalid'>" + str + "</span>";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.replace(/%.?$/, "<span class='invalid'>$&</span>"); // 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, "<br>");
|
||||||
|
formatted += "<span class='escaped'>" + decoded + "</span>";
|
||||||
|
} catch (e) {
|
||||||
|
formatted += "<span class='invalid'>" + sequence + "</span>";
|
||||||
|
}
|
||||||
|
i += sequence.length - 1;
|
||||||
|
|
||||||
|
// Unfinished escape sequence
|
||||||
|
} else {
|
||||||
|
formatted += "<span class='invalid'>" + string.substr(i, 3) + "</span>";
|
||||||
|
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
|
||||||
|
}
|
58
style.css
Executable file
58
style.css
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "JetBrains Mono";
|
||||||
|
src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2"),
|
||||||
|
url("fonts/JetBrainsMonoNL-Regular.ttf") format("ttf");
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 0.5;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.textarea {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid black;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
font-variant-ligatures: none;
|
||||||
|
-webkit-font-variant-ligatures: none;
|
||||||
|
resize: vertical;
|
||||||
|
outline: 1px solid white;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-all;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.textarea:focus {
|
||||||
|
border-color: #1E88E5;
|
||||||
|
outline-color: #1E88E5;
|
||||||
|
}
|
||||||
|
.textarea > span {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.escaped {
|
||||||
|
background-color: #FFEE58;
|
||||||
|
}
|
||||||
|
.invalid {
|
||||||
|
background-color: #EF5350;
|
||||||
|
}
|
||||||
|
.highlighted {
|
||||||
|
box-shadow:
|
||||||
|
inset 2px 2px 0 rgba(0, 0, 0, 0.5),
|
||||||
|
inset -2px -2px 0 rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user