commit 7327876db294ffe2febee1f9ef3ba76e92bfe895 Author: Reimar Date: Mon Dec 18 23:04:09 2023 +0100 Initial commit diff --git a/fonts/JetBrainsMono-Regular.woff2 b/fonts/JetBrainsMono-Regular.woff2 new file mode 100755 index 0000000..80c62dd Binary files /dev/null and b/fonts/JetBrainsMono-Regular.woff2 differ diff --git a/fonts/JetBrainsMonoNL-Regular.ttf b/fonts/JetBrainsMonoNL-Regular.ttf new file mode 100755 index 0000000..643e682 Binary files /dev/null and b/fonts/JetBrainsMonoNL-Regular.ttf differ diff --git a/index.html b/index.html new file mode 100755 index 0000000..62aaa76 --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + URL Editor + + + + + +
+

URL Editor

+ Edit URL encoded strings +
+ +
+ + +
+
+ + +
+
+ +
+ + \ No newline at end of file diff --git a/main.js b/main.js new file mode 100755 index 0000000..77e125a --- /dev/null +++ b/main.js @@ -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 "" + 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 +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100755 index 0000000..f5a20c8 --- /dev/null +++ b/style.css @@ -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); +} \ No newline at end of file