Last active
February 4, 2022 13:19
-
-
Save MarkTiedemann/9742139e290777ed40b16ce0046f285f to your computer and use it in GitHub Desktop.
Minimal Retro App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="de"> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Retrospektive</title> | |
| <style> | |
| body { | |
| margin: 0 auto; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| width: 80ch; | |
| font-family: "Segoe UI"; | |
| } | |
| section { | |
| width: calc(100% - 1em - 2px); | |
| border: 1px solid lightgrey; | |
| padding: .5em; | |
| } | |
| p { | |
| margin-block-start: 0; | |
| margin-block-end: 0; | |
| padding: .5em; | |
| } | |
| section:last-of-type p { | |
| font-weight: bold; | |
| } | |
| section:last-of-type .indented { | |
| margin-left: 3em; | |
| font-weight: normal; | |
| } | |
| section:last-of-type .cancelled { | |
| text-decoration: line-through; | |
| color: lightgrey; | |
| } | |
| section:last-of-type .marked { | |
| background-color: yellow; | |
| } | |
| h1 { | |
| margin-block-start: 3rem; | |
| margin-block-end: 0; | |
| } | |
| h2 { | |
| margin-block-start: 3rem; | |
| margin-block-end: 1.5rem; | |
| } | |
| footer { | |
| position: relative; | |
| margin: 3rem 0; | |
| } | |
| summary { | |
| font-weight: bold; | |
| } | |
| code { | |
| font-family: inherit; | |
| font-size: .8em; | |
| background-color: lightgrey; | |
| border-radius: .3ch; | |
| padding: 0 .5ch; | |
| } | |
| button { | |
| position: absolute; | |
| right: 0; | |
| top: 0; | |
| } | |
| </style> | |
| <h1>Retrospektive - <span></span></h1> | |
| <h2>Was ist gut gelaufen?</h2> | |
| <section> | |
| <p contenteditable><Was ist gut gelaufen?></p> | |
| <p contenteditable></p> | |
| <p contenteditable></p> | |
| <p contenteditable></p> | |
| </section> | |
| <h2>Was können wir verbessern?</h2> | |
| <section> | |
| <p contenteditable><Was können wir verbessern?></p> | |
| <p contenteditable class="indented"><Wie können wir es verbessern?></p> | |
| <p contenteditable></p> | |
| <p contenteditable></p> | |
| <p contenteditable></p> | |
| </section> | |
| <footer> | |
| <details> | |
| <summary>Hilfe</summary> | |
| <article> | |
| <h4>Bedienung der Arbeitsflächen</h4> | |
| <ul> | |
| <li>Nutze <code>↑</code>, <code>↓</code>, um zwischen den Zeilen zu wechseln.</li> | |
| <li>Nutze <code>↵ Enter</code>, um eine neue Zeile hinzuzufügen.</li> | |
| <li>Drücke in der ersten Zeile <code>↑</code> oder in der letzten Zeile <code>↓</code>, um eine neue Zeile hinzuzufügen.</li> | |
| <li>Nutze <code>Entf</code> oder <code>← Backspace</code>, um leere Zeilen zu löschen.</li> | |
| <li>Nutze <code>Strg</code> plus <code>x</code>, um eine Zeile - egal, ob leer oder mit Inhalt - zu löschen.</li> | |
| <li>Nutze <code>⇧ Shift</code> plus <code>↑</code> oder <code>↓</code>, um eine Zeile nach oben oder unten zu verschieben.</li> | |
| </ul> | |
| </article> | |
| <article> | |
| <h4>Zusätzliche Funktionen für die Arbeitsfläche <q>Was können wir verbessern?</q></h4> | |
| <ul> | |
| <li>Drücke <code>Tab</code>, um eine Zeile einzurücken.</li> | |
| <li>Drücke <code>Tab</code> plus <code>⇧ Shift</code>, um die Einrückung einer Zeile aufzuheben.</li> | |
| <li>Schreibe <code>~</code> am Anfang einer Zeile, um diese durchzustreichen.</li> | |
| <li>Schreibe <code>*</code> am Anfang einer Zeile, um diese zu markieren.</li> | |
| </ul> | |
| </article> | |
| </details> | |
| <button>Als Markdown in die Zwischenablage kopieren</button> | |
| </footer> | |
| <script> | |
| // TODO(Mark): Persist to localStorage | |
| // | |
| // OR | |
| // | |
| // TODO(Mark): Connect to WebSocket server | |
| // TODO(Mark): Implement line-wise locking | |
| let span = document.querySelector("span"); | |
| let today = new Date().toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" }); | |
| span.textContent = today; | |
| document.title = "Retrospektive - " + today; | |
| for (let p of document.querySelectorAll("p")) { | |
| initParagraph(p); | |
| } | |
| function initParagraph(p) { | |
| p.onkeydown = ev => { | |
| let prev = p.previousElementSibling; | |
| let next = p.nextElementSibling; | |
| switch (ev.key) { | |
| case "Enter": | |
| ev.preventDefault(); | |
| next = document.createElement("p"); | |
| next.contentEditable = true; | |
| initParagraph(next); | |
| p.insertAdjacentElement("afterend", next); | |
| next.focus(); | |
| break; | |
| case "ArrowDown": | |
| if (ev.shiftKey) { | |
| if (next === null) { | |
| // TODO(Mark): Automatically add empty line | |
| } else { | |
| ev.preventDefault(); | |
| let clone = p.cloneNode(true); | |
| p.remove(); | |
| initParagraph(clone); | |
| next.insertAdjacentElement("afterend", clone); | |
| clone.focus(); | |
| } | |
| } else { | |
| if (next === null) { | |
| // If there is no next line, append a new one | |
| next = document.createElement("p"); | |
| next.contentEditable = true; | |
| initParagraph(next); | |
| p.insertAdjacentElement("afterend", next); | |
| next.focus(); | |
| } else { | |
| next.focus(); | |
| } | |
| } | |
| break; | |
| case "ArrowUp": | |
| if (ev.shiftKey) { | |
| if (prev === null) { | |
| // TODO(Mark): Automatically add empty line | |
| } else { | |
| let clone = p.cloneNode(true); | |
| p.remove(); | |
| initParagraph(clone); | |
| prev.insertAdjacentElement("beforebegin", clone); | |
| clone.focus(); | |
| } | |
| } else { | |
| if (prev === null) { | |
| // If there is no previous line, prepend a new one | |
| prev = document.createElement("p"); | |
| prev.contentEditable = true; | |
| initParagraph(prev); | |
| p.insertAdjacentElement("beforebegin", prev); | |
| prev.focus(); | |
| } else { | |
| prev.focus(); | |
| } | |
| } | |
| break; | |
| case "Tab": | |
| ev.preventDefault(); | |
| p.classList.toggle("indented", !ev.shiftKey); | |
| break; | |
| case "Delete": | |
| case "Backspace": | |
| if (p.textContent === "") { | |
| deleteLine(prev, next); | |
| } | |
| break; | |
| case "x": | |
| if (ev.ctrlKey) { | |
| deleteLine(prev, next); | |
| } | |
| break; | |
| } | |
| }; | |
| p.oninput = ev => { | |
| let text = p.textContent; | |
| p.classList.toggle("marked", text.startsWith("*")); | |
| p.classList.toggle("cancelled", text.startsWith("~")); | |
| }; | |
| function deleteLine(prev, next) { | |
| if (prev === null && next === null) { | |
| // Don't delete last existing line | |
| } else if (prev === null) { | |
| // Delete first line, focus second | |
| p.remove(); | |
| next.focus(); | |
| } else if (next === null) { | |
| // Delete last line, focus second last | |
| p.remove(); | |
| prev.focus(); | |
| } else { | |
| // Delete line somewhere in the middle | |
| p.remove(); | |
| next.focus(); | |
| } | |
| } | |
| } | |
| let details = document.querySelector("details"); | |
| details.ontoggle = ev => { | |
| localStorage.setItem("details", details.open ? "open" : "closed"); | |
| }; | |
| details.open = localStorage.getItem("details") !== "closed"; | |
| let button = document.querySelector("button"); | |
| button.onclick = async () => { | |
| let lines = []; | |
| let h1 = document.querySelector("h1"); | |
| lines.push("# " + h1.textContent); | |
| lines.push(""); | |
| let h2 = document.querySelector("h2:first-of-type"); | |
| lines.push("## " + h2.textContent); | |
| lines.push(""); | |
| for (let p of document.querySelectorAll("section:first-of-type p")) { | |
| let text = p.textContent.trim(); | |
| if (text !== "") { | |
| lines.push("* " + text); | |
| } | |
| } | |
| lines.push(""); | |
| h2 = document.querySelector("h2:last-of-type"); | |
| lines.push("## " + h2.textContent); | |
| lines.push(""); | |
| for (let p of document.querySelectorAll("section:last-of-type p")) { | |
| let text = p.textContent.trim(); | |
| if (text !== "") { | |
| text = "* " + text; | |
| if (p.classList.contains("indented")) { | |
| text = " " + text; | |
| } | |
| lines.push(text); | |
| } | |
| } | |
| lines.push(""); | |
| let text = lines.join("\n").trim(); | |
| await navigator.clipboard.writeText(text); | |
| }; | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment