Compare commits

...

23 Commits

Author SHA1 Message Date
F.W. Dekker 7e289927de
Un-justify text 1 month ago
F.W. Dekker 1f0a7df0db
Do not justify outside of .container 1 month ago
F.W. Dekker 39cfcb51a5
Justify text by default 1 month ago
F.W. Dekker e51c026eca
Add issue tracker as VCS to footer 4 months ago
F.W. Dekker 161a36b308
Add template elements based on meta tags 5 months ago
F.W. Dekker b756a2cc44
Improve styling of noscript to be less intrusive 5 months ago
F.W. Dekker db7ab52818
Add callback to reuse fetched nav items 6 months ago
F.W. Dekker 70bbc76aa2
Prevent nav elements from obscuring each other 6 months ago
F.W. Dekker cbcb30aba0
Remove deprecated `showOnPage` 6 months ago
F.W. Dekker 9a3227b43f
Ensure whole nav row is clickable 6 months ago
F.W. Dekker 4f9d7b8311
Add deprecation notice 6 months ago
F.W. Dekker b5be385ea5
Simplify code for visibility toggle 6 months ago
F.W. Dekker 46718785af
Ensure .hidden properties are important 6 months ago
F.W. Dekker a82c43e86f
Add general-purpose .hidden CSS class 6 months ago
F.W. Dekker 136904e783
Start deprecating inline styles 6 months ago
F.W. Dekker 45e06510da
Add non-breaking spaces where appropriate 6 months ago
F.W. Dekker 2e124c0815
Add small border to bottom of navbar 6 months ago
F.W. Dekker 6ed587c54a
Do not mark subdomains as external links 6 months ago
F.W. Dekker 7b5590f9ea
Add non-JS hamburger menu 6 months ago
F.W. Dekker 764348ad6d
Deprecate nav.css 6 months ago
F.W. Dekker 86d6783ccc
Rename main exported files 6 months ago
F.W. Dekker 5476627d41
Add nav.css for nav-only users 6 months ago
F.W. Dekker 2e73257db6
Ensure high priority for white links in nav 6 months ago
  1. 4
      Gruntfile.js
  2. 2
      LICENSE
  3. 3731
      package-lock.json
  4. 21
      package.json
  5. 43
      src/main/css/common.css
  6. 7
      src/main/css/main.css
  7. 7
      src/main/css/snippets/colors.css
  8. 57
      src/main/css/snippets/common.css
  9. 47
      src/main/css/snippets/nav.css
  10. 23
      src/main/css/snippets/overrides.css
  11. 91
      src/main/js/main.js
  12. 51
      src/test/index.html

4
Gruntfile.js

@ -9,7 +9,7 @@ module.exports = grunt => {
cssmin: {
target: {
files: {
"dist/bundle.css": "src/main/css/main.css",
"dist/template.css": "src/main/css/main.css",
},
},
},
@ -35,7 +35,7 @@ module.exports = grunt => {
output: {
library: "fwdekker-template",
libraryTarget: "umd",
filename: "bundle.js",
filename: "template.js",
path: path.resolve(__dirname, "dist"),
}
},

2
LICENSE

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Felix W. Dekker
Copyright (c) 2020 F.W. Dekker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

3731
package-lock.json

File diff suppressed because it is too large

21
package.json

@ -1,8 +1,8 @@
{
"name": "@fwdekker/template",
"version": "1.0.3",
"version": "2.5.4",
"description": "The base template for pages on fwdekker.com.",
"author": "Felix W. Dekker",
"author": "F.W. Dekker",
"license": "MIT",
"homepage": "https://git.fwdekker.com/FWDekker/fwdekker-template",
"repository": {
@ -12,10 +12,10 @@
"bugs": {
"url": "https://git.fwdekker.com/FWDekker/fwdekker-template/issues"
},
"browser": "bundle.js",
"browser": "template.js",
"files": [
"dist/bundle.js",
"dist/bundle.css"
"dist/template.js",
"dist/template.css"
],
"scripts": {
"clean": "grunt clean",
@ -28,15 +28,14 @@
"normalize.css": "^8.0.1"
},
"devDependencies": {
"grunt": "^1.3.0",
"grunt-cli": "^1.4.2",
"grunt": "^1.4.1",
"grunt-cli": "^1.4.3",
"grunt-contrib-clean": "^2.0.0",
"grunt-contrib-cssmin": "^4.0.0",
"grunt-contrib-sass": "^2.0.0",
"grunt-contrib-watch": "^1.1.0",
"grunt-focus": "^1.0.0",
"grunt-webpack": "^4.0.2",
"webpack": "^5.33.2",
"webpack-cli": "^4.6.0"
"grunt-webpack": "^4.0.3",
"webpack": "^5.52.1",
"webpack-cli": "^4.8.0"
}
}

43
src/main/css/common.css

@ -1,43 +0,0 @@
/* Variables */
:root {
--fwdekker-theme-color: #0033cc;
--fwdekker-theme-color-dark: #0029a3;
--fwdekker-theme-color-very-dark: #001f7a;
--fwdekker-theme-color-light: #003df5;
--fwdekker-theme-color-very-light: #1f57ff;
}
/* Base elements */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
color: black;
}
#contents {
margin-top: 5rem;
margin-bottom: 5rem;
}
footer {
margin-bottom: 3rem;
}
/* Override Milligram */
header .container {
text-align: center;
}
/* Make arrow next to dropdown visible */
select {
-webkit-appearance: menulist;
-moz-appearance: menulist;
appearance: auto;
}

7
src/main/css/main.css

@ -1,5 +1,6 @@
@import "../../../node_modules/normalize.css/normalize.css";
@import "../../../node_modules/milligram/dist/milligram.css";
@import "common.css";
@import "nav.css";
@import "overrides.css";
@import "snippets/colors.css";
@import "snippets/common.css";
@import "snippets/nav.css";
@import "snippets/overrides.css";

7
src/main/css/snippets/colors.css

@ -0,0 +1,7 @@
:root {
--fwdekker-theme-color: #0033cc;
--fwdekker-theme-color-dark: #0029a3;
--fwdekker-theme-color-very-dark: #001f7a;
--fwdekker-theme-color-light: #003df5;
--fwdekker-theme-color-very-light: #1f57ff;
}

57
src/main/css/snippets/common.css

@ -0,0 +1,57 @@
/* Base elements */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
body {
color: black;
}
main {
/* Flex-based footer positioning, taken from https://stackoverflow.com/a/12253099 */
display: flex;
flex-direction: column;
min-height: 100%;
}
#contents {
flex: 1;
margin-top: 5rem;
margin-bottom: 5rem;
}
/* Noscript */
noscript img {
position: absolute;
}
noscript p {
font-weight: bold;
text-align: center;
}
/* Generic classes */
.hidden {
display: none !important;
}
/* Header */
header .container {
text-align: center;
}
/* Footer */
footer {
margin-bottom: 3rem;
}
#footerVersion {
float: right;
}

47
src/main/css/nav.css → src/main/css/snippets/nav.css

@ -1,28 +1,42 @@
/* Base elements */
nav {
z-index: 10;
margin: 0;
width: 100%;
background-color: var(--fwdekker-theme-color);
border-bottom: 1px solid #cccccc;
font-size: 120%;
--padding: calc(2em / 3);
}
nav * {
z-index: 10;
vertical-align: middle;
}
nav a {
nav a, nav a:link, nav a:visited, nav a:hover, nav a:active {
width: 100%; /* Ensures whole li is clickable */
}
nav a, nav a:link, nav a:visited, nav a:hover, nav a:active, nav #nav-hamburger-label {
display: inline-block;
margin: 0;
padding: calc(var(--padding)) calc(var(--padding));
width: 100%;
height: 100%;
color: white;
}
nav #nav-hamburger-label {
float: right;
font-size: unset;
cursor: pointer;
}
/* Logo */
nav .logo {
width: calc(1em + var(--padding));
height: calc(1em + var(--padding));
@ -30,12 +44,14 @@ nav .logo {
vertical-align: middle;
filter: brightness(0) invert(1);
}
nav div.logo {
display: inline-block;
margin-right: calc(1em / 3);
}
/* First level nesting */
nav ul {
margin: 0;
padding: 0;
@ -53,7 +69,9 @@ nav ul li {
}
nav ul li:hover,
nav ul li:focus-within {
nav ul li:focus-within,
nav #nav-hamburger-label:hover,
nav #nav-hamburger-label:focus-within {
cursor: pointer;
background-color: var(--fwdekker-theme-color-very-dark);
}
@ -63,7 +81,10 @@ nav li.currentPage {
}
/* Second level nesting */
nav ul li ul {
z-index: 11;
display: none;
position: absolute;
left: 0;
@ -85,3 +106,21 @@ nav ul li ul li {
width: 100%;
white-space: nowrap;
}
/* Hide hamburger-related elements */
nav input[type="checkbox"] {
display: none;
}
@media (min-width: 600px) {
nav #nav-hamburger-label {
display: none;
}
}
@media (max-width: 600px) {
nav input[type="checkbox"]:not(:checked) ~ ul li:not(:first-child) {
display: none;
}
}

23
src/main/css/overrides.css → src/main/css/snippets/overrides.css

@ -1,5 +1,14 @@
/* Override Milligram color scheme */
/* Replaces #9b4dca with #0033cc */
/* Make arrow next to dropdown visible */
select {
-webkit-appearance: menulist;
-moz-appearance: menulist;
appearance: auto;
}
/* Override Milligram color scheme, based on v1.4.1. */
/* Replaces #9b4dca with `--fwdekker-theme-color`. */
/* Simply look at latest unminimized Milligram release and Ctrl+F where #9b4dca occurs. */
.button,
button,
input[type='button'],
@ -66,19 +75,19 @@ pre {
border-left: 0.3rem solid var(--fwdekker-theme-color);
}
input[type='color']:focus,
input[type='date']:focus,
input[type='datetime']:focus,
input[type='datetime-local']:focus,
input[type='email']:focus,
input[type='month']:focus,
input[type='number']:focus,
input[type='password']:focus,
input[type='search']:focus,
input[type='tel']:focus,
input[type='text']:focus,
input[type='url']:focus,
input[type='color']:focus,
input[type='date']:focus,
input[type='month']:focus,
input[type='week']:focus,
input[type='datetime']:focus,
input[type='datetime-local']:focus,
input:not([type]):focus,
textarea:focus,
select:focus {

91
src/main/js/main.js

@ -47,9 +47,10 @@ const doAfterLoad = function(fun) {
* Fetches entries asynchronously from the website's API.
*
* @param [highlightPath] {String} the path to highlight together with its parents
* @param [cb] {Function} the callback to execute on the fetched entries, to prevent the need to re-fetch elsewhere
* @returns {HTMLElement} a base navigation element that will eventually be filled with contents
*/
const nav = function(highlightPath = "") {
const nav = function(highlightPath = "", cb = undefined) {
const base = stringToHtml(
`<ul><li><a href="https://fwdekker.com/">` +
`<div class="logo"><img class="logo" src="https://fwdekker.com/favicon.png" alt="FWDekker" /></div>` +
@ -61,14 +62,22 @@ const nav = function(highlightPath = "") {
fetch("https://fwdekker.com/api/nav/")
.then(it => it.json())
.then(json => {
json.entries.forEach(entry => base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath), "li")))
if (cb !== undefined) cb(json);
json.entries.forEach(entry => base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath), "li")));
})
.catch(e => {
console.error("Failed to fetch navigation elements", e);
return [];
});
const nav = stringToHtml(`<nav></nav>`, "nav");
const nav = stringToHtml(
`<nav>` +
`<input id="nav-hamburger-checkbox" type="checkbox" hidden />` +
`<label id="nav-hamburger-label" for="nav-hamburger-checkbox">&#9776;</label>` +
`</nav>`,
"nav"
);
nav.appendChild(base);
return nav;
};
@ -84,11 +93,12 @@ const nav = function(highlightPath = "") {
*/
const unpackEntry = function(entry, path = "/", highlightPath = "") {
const shouldHighlight = highlightPath.startsWith(`${path + entry.name}/`);
const isExternalLink = !entry.link.startsWith("https://fwdekker.com/") && entry.link !== "#";
const isExternalLink = !(/^https:\/\/.*fwdekker.com/.test(entry.link)) && entry.link !== "#";
const formattedName = (isExternalLink ? "&#9099; " : "") + entry.name;
if (entry.entries.length === 0)
return `<li class="${shouldHighlight ? "currentPage" : ""}"><a href="${entry.link}">${formattedName}</a></li>`;
return "" +
`<li ${shouldHighlight ? "class=\"currentPage\"" : ""}><a href="${entry.link}">${formattedName}</a></li>`;
const depth = path.split("/").length - 2; // -1 because count parts, then another -1 because of leading `/`
const arrow = depth === 0 ? "&#9662;" : "&#9656;";
@ -149,9 +159,9 @@ const footer = function(
version = undefined,
privacyPolicyURL = undefined
}) {
if (author === undefined) author = "F.W. Dekker";
if (author === undefined) author = "F.W.&nbsp;Dekker";
if (authorURL === undefined) authorURL = "https://fwdekker.com/";
if (license === undefined) license = "MIT License";
if (license === undefined) license = "MIT&nbsp;License";
if (licenseURL === undefined && vcsURL !== undefined) licenseURL = `${vcsURL}src/branch/master/LICENSE`;
if (vcs === undefined && vcsURL !== undefined) vcs = "git";
if (privacyPolicyURL === undefined) privacyPolicyURL = "https://fwdekker.com/privacy/";
@ -160,9 +170,9 @@ const footer = function(
`<footer><section class="container">` +
footerLink("Made by ", author, authorURL, ". ") +
footerLink("Licensed under the ", license, licenseURL, ". ") +
footerLink("Source code available on ", vcs, vcsURL, ". ") +
footerLink("Source code and issue tracker on ", vcs, vcsURL, ". ") +
footerLink("Consider reading the ", privacyPolicyURL && "privacy policy", privacyPolicyURL, ". ") +
`<div style="float: right;">${version || ""}</div>` +
`<div id="footerVersion">${version || ""}</div>` +
`</section></footer>`,
"footer");
};
@ -184,18 +194,59 @@ const footerLink = function(prefix, text, url, suffix) {
/**
* Unhides the main element on the page and applies default display styling.
* Runs the functions `nav`, `header`, and `footer` after the page has loaded using properties defined in the meta tags.
*
* The remaining functions are invoked only if the corresponding `target` meta property is set to select an existing
* element. The HTML element returned by the function is then added as a child to the element specified by the query
* selector in the `target` property. The parameters to the function invocation can be set using other meta properties.
* The format of meta properties is `fwd:<function>:<property>`, where `property` is the same as the name of the
* parameter of that function, except that instead of camelcase words are separated by dashes. For example, `vcsURL`
* becomes `vcs-url`.
*
* Meta properties can be set by including `<meta name="fwd:<function>:<property>" content="<value>" />` in the HTML
* page on which this module is included. The `<value>` is then passed without modification as a parameter to the
* function. Leaving out the `<value>` by writing `<meta name="fwd:<function>:property" />` instead results in passing
* `null` as the value. Not including the meta tag at all corresponds to passing `undefined` to the function. See the
* documentation of the respective functions for more details on the parameters that they accept.
*
* Note that the function is invoked only if `fwd:<function>:target` is a query selector for an existing element. This
* means that it is possible to mix how the functions are invoked; for example, one can use meta properties to pass
* parameters to `nav`, but also invoke `header` in a separate function manually.
*/
const showPage = function() {
// Flex-based footer positioning, taken from https://stackoverflow.com/a/12253099
const main = $("main");
main.style.display = "flex";
main.style.flexDirection = "column";
main.style.minHeight = "100%";
$("#contents").style.flex = "1";
}
doAfterLoad(() => {
const getMetaProperty = (name) => {
const element = $(`meta[name="${name}"]`);
return element === null ? undefined : element.getAttribute("content");
};
const navTarget = $(getMetaProperty("fwd:nav:target"));
if (navTarget !== null) {
navTarget.appendChild(nav(getMetaProperty("fwd:nav:highlight-path")));
}
const headerTarget = $(getMetaProperty("fwd:header:target"));
if (headerTarget !== null) {
headerTarget.appendChild(header({
title: getMetaProperty("fwd:header:title"),
description: getMetaProperty("fwd:header:description"),
}));
}
const footerTarget = $(getMetaProperty("fwd:footer:target"));
if (footerTarget !== null) {
footerTarget.appendChild(footer({
author: getMetaProperty("fwd:footer:author"),
authorURL: getMetaProperty("fwd:footer:author-url"),
license: getMetaProperty("fwd:footer:license"),
licenseURL: getMetaProperty("fwd:footer:license-url"),
vcs: getMetaProperty("fwd:footer:vcs"),
vcsURL: getMetaProperty("fwd:footer:vcs-url"),
version: getMetaProperty("fwd:footer:version"),
privacyPolicyURL: getMetaProperty("fwd:footer:privacy-policy-url"),
}));
}
});
// Export to namespace
fwdekker = {stringToHtml, $, doAfterLoad, nav, header, footer, showPage};
fwdekker = {stringToHtml, $, doAfterLoad, nav, header, footer};

51
src/test/index.html

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="F.W. Dekker" />
<meta name="application-name" content="Test" />
<meta name="description" content="A test page" />
<meta name="theme-color" content="#0033cc" />
<meta name="fwd:nav:target" content="#nav" />
<meta name="fwd:nav:highlight-path" content="/Tools/Dice/" />
<meta name="fwd:header:target" content="#header" />
<meta name="fwd:header:title" content="Test" />
<meta name="fwd:header:description" content="A test page" />
<meta name="fwd:footer:target" content="#footer" />
<meta name="fwd:footer:vcs-url" content="https://git.fwdekker.com/FWDekker/fwdekker-template/" />
<meta name="fwd:footer:version" content="vTEST" />
<title>Tools | FWDekker</title>
<link rel="stylesheet" href="https://static.fwdekker.com/fonts/roboto/roboto.css" />
<!--suppress HtmlUnknownTarget -->
<link rel="stylesheet" href="../../dist/template.css" />
</head>
<body>
<noscript>
<p>
This website does not function if JavaScript is disabled.
Please check the <a href="https://www.enable-javascript.com/">
instructions on how to enable JavaScript in your web browser</a>.
</p>
</noscript>
<main>
<div id="nav"></div>
<div id="contents">
<div id="header"></div>
<section class="container">
<p>These are the page contents.</p>
<p>These are some more contents.</p>
</section>
</div>
<div id="footer"></div>
</main>
<!--suppress HtmlUnknownTarget -->
<script src="../../dist/template.js"></script>
</body>
</html>
Loading…
Cancel
Save