Skip to content

Instantly share code, notes, and snippets.

@MarkTiedemann
Last active February 4, 2022 13:19
Show Gist options
  • Select an option

  • Save MarkTiedemann/9742139e290777ed40b16ce0046f285f to your computer and use it in GitHub Desktop.

Select an option

Save MarkTiedemann/9742139e290777ed40b16ce0046f285f to your computer and use it in GitHub Desktop.
Minimal Retro App
<!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>&lt;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>&lt;Was können wir verbessern?></p>
<p contenteditable class="indented">&lt;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