commented code, added some info, added icon

This commit is contained in:
2022-07-03 00:21:20 +02:00
parent b4fd4f344c
commit 90449faa8b
8 changed files with 375 additions and 146 deletions

View File

@ -22,15 +22,89 @@
margin: 0;
padding: 0;
}
.trigger {
display: none;
body {
padding: 3em;
max-width: 52em;
margin: 0 auto;
}
.left-nav > * {
pre {
margin: 0;
padding: 3em;
max-width: inherit;
max-height: 100vh;
overflow: scroll;
background: rgb(192, 192, 192);
position: absolute;
top: 0;
left: 50%;
transition: all 1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
transform: translate(-50%, -100%);
}
.is-source-enabled pre {
transform: translate(-50%, 0);
}
button {
display: inline-block;
border: none;
margin: 0;
text-decoration: none;
font-family: inherit;
font-size: 1rem;
display: flex;
justify-content: center;
align-content: center;
text-align: center;
vertical-align: middle;
background: var(--accent);
color: var(--background);
cursor: pointer;
padding: 0.5em 1em;
border-radius: 0 0 0.2em 0;
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
-webkit-appearance: none;
-moz-appearance: none;
}
h1,
h2,
h3,
h4 {
color: var(--accent);
font-family: "Lobster", serif;
}
a {
color: var(--accent);
position: relative;
text-decoration: none;
padding: 0.1em;
}
a::after {
content: "";
position: absolute;
background-color: var(--accent);
position: absolute;
left: 0;
bottom: 3px;
width: 100%;
height: 1px;
z-index: -1;
transition: all 0.1s ease-in;
}
a:hover {
color: var(--background);
transition: all 0.3s ease-in-out;
}
a:hover::after {
bottom: 0;
height: 100%;
transition: all 0.3s ease-in-out;
}
.menu,
.burger {
position: fixed;
top: 0;
left: 0;
}
.left-nav > .menu {
.menu {
padding: 1em;
transform: translateX(-100%);
display: flex;
@ -40,39 +114,26 @@
transition: all 0.3s ease-out;
gap: 1em;
}
.left-nav > .menu a {
.menu a {
text-decoration: none;
background: var(--background);
color: var(--accent);
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
padding: 0.2em 0.4em;
}
.left-nav > .menu a:hover {
.menu a:hover {
background: var(--accent);
color: var(--background);
}
.left-nav > .trigger:checked ~ .menu {
.menu-is-open .menu {
transform: translateX(0);
transition-duration: 0.1s;
}
.burger {
width: 2em;
height: 2em;
display: flex;
justify-content: center;
align-content: center;
text-align: center;
vertical-align: middle;
line-height: 2em;
border-radius: 0.2em;
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
transition: background 0.5s ease;
}
.left-nav > .trigger:checked ~ .burger {
.menu-is-open .burger {
color: transparent;
}
.left-nav > .trigger:checked ~ .burger,
.menu-is-open .burger,
#Loading {
top: 0;
right: 0;
@ -80,9 +141,10 @@
height: 100%;
border-radius: 0;
background: rgba(17, 17, 17, 0.2);
cursor: default;
}
#Loading {
position: absolute;
position: fixed;
text-align: center;
align-items: center;
justify-content: center;
@ -94,16 +156,15 @@
display: flex;
overflow: hidden;
}
#Loading > * {
animation: load 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}
.is-loading #Loading {
opacity: 1;
height: 100%;
transition: opacity 1s ease-in, height 0 linear;
}
#Loading::after {
content: "❤";
animation: beat 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}
@keyframes beat {
@keyframes load {
0% {
transform: scale(0.95);
}
@ -123,48 +184,37 @@
transform: scale(0.9);
}
}
#Body {
padding: 3em;
max-width: 52em;
margin: 0 auto;
}
#Body h1,
#Body h2,
#Body h3,
#Body h4 {
color: var(--accent);
font-family: "Lobster", serif;
}
#Body a {
color: var(--background);
background-color: var(--accent);
text-decoration: none;
padding: 0.1em 0.4em;
}
</style>
</head>
<body>
<nav class="left-nav">
<input id="main-nav" type="checkbox" class="trigger" />
<label for="main-nav" class="burger">&#8801;</label>
<button id="Burger" class="burger">&#8801;</button>
<div id="Menu" class="menu"></div>
</nav>
<header id="Menu"></header>
<main id="Body"></main>
<div id="Loading"></div>
<pre id="Source"></pre>
<div id="Loading">
<p></p>
</div>
<script type="module">
//@ts-check
const is_debug_mode = /^localhost|127.0.0.1/.test(
window.location.hostname
);
/*****************************************************************
*
* "THE FRAMEWORK"
* a small set of utilities that help
*
****************************************************************/
/**
* markdown parser. Remove if you don't use markdown
*/
// @ts-ignore
import { micromark } from "https://esm.sh/micromark@3?bundle";
const is_debug_mode = /^localhost|127.0.0.1/.test(
window.location.hostname
);
/**
* A small utility to query elements and get back an array
*/
@ -179,33 +229,120 @@
* @param {string} url
*/
const isExternal = (url) =>
/^(https?|mailto|tel|ftp|ipfs|dat):/.test(url);
url && /^(https?|mailto|tel|ftp|ipfs|dat):/.test(url);
/**
* Makes sure urls to local pages get passed through the routing system
* @param {HTMLElement} container the element containing links to find
*/
const rewriteLocalUrls = (container) => {
$(container, "a").forEach((a) => {
const href = a.getAttribute("href");
if (href && !isExternal(href)) {
console.log({ href });
const rewrittenHref = "#/" + href.replace(/^\.?\//, "");
a.setAttribute("href", rewrittenHref);
console.log(a);
console.log(rewrittenHref);
a.setAttribute("href", "#/" + href.replace(/^\.?\//, ""));
}
});
return container;
};
/**
* Returns the hash part of the url, but only if it starts with a `/`
* This allows regular hashes to continue to work
* It reads also query parameters
*/
const getCurrentHashUrl = () => {
const [path, searchStr] = (
window.location.hash[1] === "/" ? window.location.hash.slice(2) : ""
).split("?");
const params = new URLSearchParams(searchStr);
return { path, params };
};
/**
* useful to check for transitions while developing styles, if the loading screen disappears too fast
*
* uses micromark. You can plug a different parser if you prefer
*/
const wait = (val) => new Promise((ok) => setTimeout(ok, 1, val));
const wait = is_debug_mode
? (val) => new Promise((ok) => setTimeout(ok, 1000, val))
: (val) => val;
/**
* @param {string} htmlString
*/
const generateDOM = (htmlString) =>
/** @type {HTMLElement} */ (
new DOMParser().parseFromString(
`<div>${htmlString}</div>`,
"text/html"
).firstChild
);
/**
* Loads and parses a markdown document. Makes use of micromark
* @param {string} path the path to load
*/
const loadMarkdown = (path) =>
fetch(is_debug_mode ? `./${path}?rand=${Math.random()}` : `./${path}`)
.then((response) => response.text())
.then(wait)
.then((raw) => {
const content = rewriteLocalUrls(generateDOM(micromark(raw)));
const firstTitleElement = content.querySelector("h1");
const title = firstTitleElement
? firstTitleElement.textContent
: path.replace(/\.\w{2, 4}$/, "");
return { title, raw, content };
});
/**
* parses a filelist string. That's a string that looks like
* ```
* name dd-mm-yyyy link name
* name dd-mm-yyyy
* name linkname
* name
* ```
* @param {string} lines
*/
const parseFileList = (lines) =>
lines
.trim()
.split(`\n`)
.map((line, index) => {
const today = new Date().toISOString().split("T")[0];
const result = line.match(
/(?<name>.+)\.(?<ext>\w{2,3})(?:\s+(?<date>[\d-]+)?(?<title>.+))?/
);
if (!result) {
console.error(`could not parse line ${index}: [${line}]`);
return null;
}
const {
groups: { name, ext, date = today, title = name },
} = result;
const href = `/${name}.${ext}`;
const date_unix = new Date(date).getTime();
return { name, href, date, title, date_unix, index };
})
.filter(Boolean)
.sort(({ date_unix: a }, { date_unix: b }) => a - b);
/*****************************************************************
*
* Creating references to the important stuff
*
****************************************************************/
/**
* The elements we will need
*/
const [Menu, Body, Loading] = ["Menu", "Body", "Loading"].map((id) =>
document.getElementById(id)
);
const [Menu, Body, Loading, Source, Burger] = [
"Menu",
"Body",
"Loading",
"Source",
"Burger",
].map((id) => document.getElementById(id));
/**
* cache the original title to append it to the page title
@ -217,85 +354,86 @@
const hideLoadingOverlay = () =>
document.body.classList.remove("is-loading");
const getCurrentHashUrl = () => {
const [path, searchStr] = (
window.location.hash[1] === "/" ? window.location.hash.slice(2) : ""
).split("?");
const params = new URLSearchParams(searchStr);
return { path, params };
};
/*****************************************************************
*
* Router
*
* Things related to main navigation and to loading pages
*
****************************************************************/
const onHashChange = (evt) => {
/**
* Listens to hashchange event, and attempts to read the url.
* If the url is set,
*/
const onHashChange = async (evt) => {
const { path, params } = getCurrentHashUrl();
if (!path) {
return false;
}
showLoadingOverlay();
return fetch(
is_debug_mode ? `./${path}?rand=${Math.random()}` : `./${path}`
)
.then((response) => response.text())
.then(wait)
.then((text) => {
const [, title] = text.match(/^#\s+(\w+.+)/) ||
text.match(/(.*?)\n===+/m) || [, path];
document.title = `${title} | ${mainTitle}`;
if (params.has("source")) {
Body.innerHTML = `<pre>${text}</pre>`;
} else {
Body.innerHTML = micromark(text);
rewriteLocalUrls(Body);
}
hideLoadingOverlay();
});
const { title, raw, content } = await loadMarkdown(path);
document.title = `${title} | ${mainTitle}`;
Body.innerHTML = "";
Source.innerHTML = raw;
Body.appendChild(content);
hideLoadingOverlay();
};
window.addEventListener("hashchange", onHashChange);
/**
* Called when the file list is obtained (presumably through loading)
* parses the file list, then fills the side navigation
* If there's no page loaded, it also loads the first page in the list
* (the list gets sorted by date, but the first line is the one that gets used)
* @param {string} lines
*/
const fillMenuFromFileList = (lines) => {
const links = parseFileList(lines);
Menu.innerHTML = links
.map(({ href, title }) => `<a data-link href="#${href}">${title}</a>`)
.join(`\n`);
if (!getCurrentHashUrl().path) {
const href = links.find(({ index }) => index === 0).href;
window.location.hash = `#${href}`;
} else {
onHashChange();
}
};
/*****************************************************************
*
* Bootstrapping
*
* this is where things actually happen
*
****************************************************************/
const loadFileList = () => {
showLoadingOverlay();
fetch("./files.txt")
.then((response) => response.text())
.then(fillMenuFromFileList);
};
/**
* Loads the article list, parses it, creates the menu items
*/
const start = () => {
let hasAutoloadedFirstPage = false;
showLoadingOverlay();
fetch("./files.txt")
.then((response) => response.text())
.then((lines) => {
Menu.innerHTML = lines
.split(`\n`)
.map((line, index) => {
const today = new Date().toISOString().split("T")[0];
const result = line.match(
/(?<name>.+)\.(?<ext>\w{2,3})(?:\s+(?<date>[\d-]+)?(?<title>.+))?/
);
if (!result) {
console.log(`could not parse line ${index}: [${line}]`);
return null;
}
const {
groups: { name, ext, date = today, title = name },
} = result;
const href = `/${name}.${ext}`;
if (!getCurrentHashUrl().path) {
hasAutoloadedFirstPage = true;
window.location.hash = `#${href}`;
}
const date_unix = new Date(date).getTime();
return { name, href, date, title, date_unix };
})
.filter(Boolean)
.sort(({ date_unix: a }, { date_unix: b }) => a - b)
.map(
({ href, title }) => `<a data-link href="#${href}">${title}</a>`
)
.join(`\n`);
if (!hasAutoloadedFirstPage) {
onHashChange();
}
});
};
(function bootstrap() {
Burger.addEventListener("click", () =>
document.body.classList.toggle("menu-is-open")
);
start();
window.addEventListener("hashchange", onHashChange);
loadFileList();
document.addEventListener(
"keyup",
({ key }) =>
key === "?" && document.body.classList.toggle("is-source-enabled")
);
})();
</script>
</body>
</html>