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