diff --git a/Readme.md b/Readme.md index 11660c2..40f646b 100644 --- a/Readme.md +++ b/Readme.md @@ -1,3 +1,15 @@ # hornwitser.no The code behind hornwitser.no + +## Development + +For regular development run: +- `pnpm watch-ts` to transpile the TypeScript code to build/node and watch for changes. +- `pnpm watch-site` to host the site on http://localhost:8080 and reload the server on changes. +- `pnpm test` to run the tests and report coverage. + +Other scripts available are: +- `pnpm prepare` to transpile the TypeScript code to build/node +- `pnpm build` to build the static resources to build/web. +- `pnpm serve` to host the website on http://localhost:8080 diff --git a/package.json b/package.json index fe91023..427faba 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "main": "build/node/index.js", "scripts": { "prepare": "tsc", + "watch-ts": "tsc --watch", + "watch-site": "node --enable-source-maps build/node/cli.js watch", "build": "node --enable-source-maps build/node/cli.js build", + "serve": "node --enable-source-maps build/node/cli.js serve", "test": "node --test --enable-source-maps --experimental-test-coverage" }, "dependencies": { diff --git a/src/components/BasePage.tsx b/src/components/BasePage.tsx index 13674d9..8e2ec7f 100644 --- a/src/components/BasePage.tsx +++ b/src/components/BasePage.tsx @@ -18,8 +18,9 @@ source.addEventListener("reload", () => location.reload());`; {props.title} - - , "/subdir"); + assert.equal((el as Element).attributes.get("src"), "../assets/script.js"); + }); + + test("img src", () => { + const el = resolveRefs(, "/subdir"); + assert.equal((el as Element).attributes.get("src"), "../assets/image.png"); + }); + test("nested element", () => { const el = resolveRefs(
Content with Link
, "/"); assert.equal((el.childNodes[1] as Element).attributes.get("href"), "page.html"); diff --git a/src/utils/resolve-refs.ts b/src/utils/resolve-refs.ts index 7bdba49..2c19dd0 100644 --- a/src/utils/resolve-refs.ts +++ b/src/utils/resolve-refs.ts @@ -8,6 +8,13 @@ function shallowCopyElement(element: Element) { return copy; } +const resolvedAttributes = new Map([ + ["a", "href"], + ["img", "src"], + ["link", "href"], + ["script", "src"], +]); + /** Resolves absolute href attributes in a and link elements the Node tree into relative references from the given directory. @@ -21,20 +28,18 @@ export function resolveRefs(node: Node, dir: string) { } let resolvedNode = node; const name = node.name; - if ( - (name === "link" || name === "a") - && node.attributes.has("href") - ) { - const original = node.attributes.get("href")! + let attribute = resolvedAttributes.get(name); + if (attribute && node.attributes.has(attribute)) { + const original = node.attributes.get(attribute)! if (/^[a-z][a-z+.-]*:/i.test(original)) { // Ignore refs that start with a URI scheme. /* node:coverage ignore next 3 */ } else if (!original.startsWith("/")) { - console.log(`Warning: found relative href to ${original}`); + console.log(`Warning: found relative ${attribute} to ${original}`); } else { const ref = posix.relative(dir, original); resolvedNode = shallowCopyElement(node); - resolvedNode.attributes.set("href", ref); + resolvedNode.attributes.set(attribute, ref); } } const resolvedChildren: Node[] = []; diff --git a/web/assets/fonts/ComicNeue-Bold.woff2 b/web/assets/fonts/ComicNeue-Bold.woff2 new file mode 100644 index 0000000..250a33f Binary files /dev/null and b/web/assets/fonts/ComicNeue-Bold.woff2 differ diff --git a/web/assets/fonts/ComicNeue-BoldItalic.woff2 b/web/assets/fonts/ComicNeue-BoldItalic.woff2 new file mode 100644 index 0000000..2fecd41 Binary files /dev/null and b/web/assets/fonts/ComicNeue-BoldItalic.woff2 differ diff --git a/web/assets/fonts/ComicNeue-Italic.woff2 b/web/assets/fonts/ComicNeue-Italic.woff2 new file mode 100644 index 0000000..f2bcb15 Binary files /dev/null and b/web/assets/fonts/ComicNeue-Italic.woff2 differ diff --git a/web/assets/fonts/ComicNeue-Light.woff2 b/web/assets/fonts/ComicNeue-Light.woff2 new file mode 100644 index 0000000..4f1f4f4 Binary files /dev/null and b/web/assets/fonts/ComicNeue-Light.woff2 differ diff --git a/web/assets/fonts/ComicNeue-LightItalic.woff2 b/web/assets/fonts/ComicNeue-LightItalic.woff2 new file mode 100644 index 0000000..3d9db1c Binary files /dev/null and b/web/assets/fonts/ComicNeue-LightItalic.woff2 differ diff --git a/web/assets/fonts/ComicNeue-Regular.woff2 b/web/assets/fonts/ComicNeue-Regular.woff2 new file mode 100644 index 0000000..0580549 Binary files /dev/null and b/web/assets/fonts/ComicNeue-Regular.woff2 differ diff --git a/web/viewport.js b/web/assets/scripts/viewport.js similarity index 100% rename from web/viewport.js rename to web/assets/scripts/viewport.js diff --git a/web/style.css b/web/assets/styles/base.css similarity index 71% rename from web/style.css rename to web/assets/styles/base.css index 47b3f38..fc9ea79 100644 --- a/web/style.css +++ b/web/assets/styles/base.css @@ -14,6 +14,10 @@ html { text-size-adjust: none; } +:is(ul, ol)[role="list"] { + list-style: none; +} + h1, h2, h3, h4, button, input, label { line-height: 1.1; } @@ -39,29 +43,43 @@ textarea:not([rows]) { /* Overall styling */ html { color-scheme: light dark; - font-family: sans-serif; + font-family: "Comic Neue", sans-serif; overflow-wrap: break-word; scrollbar-gutter: stable; } -hgroup h1 { - margin-bottom: 0.1em; +hgroup :is(h1, h2, h3, h4) { + margin-block-start: 0; + margin-block-end: 0.1em; } hgroup p { font-style: italic; - margin-bottom: 1em; } h1, h2, h3, h4 { + font-family: "Comic Neue", sans-serif; margin-block-start: 1.25em; margin-block-end: 0.5em; } -:is(p, ol, ul) + :is(p, ol, ul) { - padding-block-start: 1em; +:is(hgroup, p, blockquote, ol, ul, li) + :is(hgroup, blockquote, p, ol, ul, li) { + margin-block-start: var(--block-space, 1em); +} +.tight { + --block-space: 0.35em; } -ol, ul { +blockquote { + padding-inline: 1.25em; +} +blockquote::before { + content: "“"; +} +blockquote::after { + content: "”"; +} + +:is(ol, ul):not([role]) { padding-inline-start: 1.25em; } @@ -80,8 +98,8 @@ ol, ul { /* Base Page Layout */ body { max-width: 50rem; - padding: 0; - margin-block: 0; + padding-inline: 0.5em; + padding-block-end: 2em; margin-inline: auto; } @@ -94,7 +112,7 @@ body { background-color: grey; } -/* index */ +/* about */ .author { display: grid; grid-template-columns: auto 1fr; @@ -102,13 +120,24 @@ body { gap: 1em; margin-block: 1em; } -.author h1 { - margin-block-start: 0; -} .author p { margin-block-end: 0; } +/* links */ +.no-break { + display: inline-block; + max-width: 100%; +} +.link { + background-color: color-mix(in oklab, Canvas 90%, white); + padding: 0.5em; + border-radius: 0.5em; +} +.link>* { + --block-space: 0.5em; +} + /* sandbox */ .sandbox-inset-3d { contain: paint; diff --git a/web/assets/styles/font.css b/web/assets/styles/font.css new file mode 100644 index 0000000..56ba778 --- /dev/null +++ b/web/assets/styles/font.css @@ -0,0 +1,71 @@ +@font-face { + font-family: 'Comic Neue'; + src: + local('Comic Neue Light'), + local('ComicNeue-Light'), + url('/assets/fonts/ComicNeue-Light.woff2') format('woff2') + ; + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Comic Neue'; + src: + local('Comic Neue Italic'), + local('ComicNeue-Italic'), + url('/assets/fonts/ComicNeue-Italic.woff2') format('woff2') + ; + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Comic Neue'; + src: + local('Comic Neue Bold'), + local('ComicNeue-Bold'), + url('/assets/fonts/ComicNeue-Bold.woff2') format('woff2') + ; + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Comic Neue'; + src: + local('Comic Neue Bold Italic'), + local('ComicNeue-BoldItalic'), + url('/assets/fonts/ComicNeue-BoldItalic.woff2') format('woff2') + ; + font-weight: bold; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Comic Neue'; + src: + local('Comic Neue Light Italic'), + local('ComicNeue-LightItalic'), + url('/assets/fonts/ComicNeue-LightItalic.woff2') format('woff2') + ; + font-weight: 300; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Comic Neue'; + src: + local('Comic Neue Regular'), + local('ComicNeue-Regular'), + url('/assets/fonts/ComicNeue-Regular.woff2') format('woff2') + ; + font-weight: normal; + font-style: normal; + font-display: swap; +}