diff --git a/index.html b/index.html index 7fd136c..f0368a2 100644 --- a/index.html +++ b/index.html @@ -3,199 +3,18 @@ Tickle - - - - + - -
+
+ + +
+

     

- + diff --git a/modules/index.mjs b/modules/index.mjs index b461635..2d79388 100644 --- a/modules/index.mjs +++ b/modules/index.mjs @@ -1,5 +1,4 @@ //@ts-check export * from './tickle/index.mjs' export * from './utils/index.mjs' -export { default as blog } from './templateBlog.mjs' -export { default as musings } from './templateMusings.mjs' \ No newline at end of file +export * as templates from './templates/index.mjs' \ No newline at end of file diff --git a/modules/templateBlog.mjs b/modules/templateBlog.mjs deleted file mode 100644 index aa66a1c..0000000 --- a/modules/templateBlog.mjs +++ /dev/null @@ -1,38 +0,0 @@ -//@ts-check -import { fetchText } from "./utils/fetchText.mjs"; -import { getElementById } from "./utils/getElementById.mjs"; -import { onDocumentKeyUp } from "./utils/onDocumentKey.mjs"; -import { parseFileList } from "./tickle/parseFileList.mjs"; -import {createMenuEntriesFromFileList} from "./tickle/createMenuEntriesFromFileList.mjs" -import { sortFileListLines } from "./tickle/sortFileListLines.mjs" -import { mode } from "./tickle/mode.mjs"; -import {bootstrapRouter} from "./tickle/bootstrapRouter.mjs"; - -/** - * Loads the article list, parses it, creates the menu items -*/ -export const bootstrap = () => { - - const [Menu, Body, Source, Burger] = ["Menu", "Body", "Source", "Burger"].map( - getElementById - ); - - Burger.addEventListener("click", mode.menuOpen.toggle); - - mode.loading.on(); - fetchText("./files.txt").then((lines)=>{ - const links = parseFileList(lines) - const firstHref = links[0].href; - sortFileListLines(links) - Menu.appendChild(createMenuEntriesFromFileList(links)) - bootstrapRouter(firstHref, (content, raw)=>{ - Body.innerHTML = ""; - Source.innerHTML = raw; - Body.appendChild(content); - }) - }); - - onDocumentKeyUp("?", mode.sourceEnable.toggle); -}; - -export default bootstrap; diff --git a/modules/templateMusings.mjs b/modules/templateMusings.mjs deleted file mode 100644 index aa66a1c..0000000 --- a/modules/templateMusings.mjs +++ /dev/null @@ -1,38 +0,0 @@ -//@ts-check -import { fetchText } from "./utils/fetchText.mjs"; -import { getElementById } from "./utils/getElementById.mjs"; -import { onDocumentKeyUp } from "./utils/onDocumentKey.mjs"; -import { parseFileList } from "./tickle/parseFileList.mjs"; -import {createMenuEntriesFromFileList} from "./tickle/createMenuEntriesFromFileList.mjs" -import { sortFileListLines } from "./tickle/sortFileListLines.mjs" -import { mode } from "./tickle/mode.mjs"; -import {bootstrapRouter} from "./tickle/bootstrapRouter.mjs"; - -/** - * Loads the article list, parses it, creates the menu items -*/ -export const bootstrap = () => { - - const [Menu, Body, Source, Burger] = ["Menu", "Body", "Source", "Burger"].map( - getElementById - ); - - Burger.addEventListener("click", mode.menuOpen.toggle); - - mode.loading.on(); - fetchText("./files.txt").then((lines)=>{ - const links = parseFileList(lines) - const firstHref = links[0].href; - sortFileListLines(links) - Menu.appendChild(createMenuEntriesFromFileList(links)) - bootstrapRouter(firstHref, (content, raw)=>{ - Body.innerHTML = ""; - Source.innerHTML = raw; - Body.appendChild(content); - }) - }); - - onDocumentKeyUp("?", mode.sourceEnable.toggle); -}; - -export default bootstrap; diff --git a/modules/templates/blog.css b/modules/templates/blog.css new file mode 100644 index 0000000..18679a1 --- /dev/null +++ b/modules/templates/blog.css @@ -0,0 +1,177 @@ +@import url('https://fonts.bunny.net/css2?family=Lobster&family=Nunito+Sans:ital,wght@0,400;0,700;1,400&display=swap'); + +:root { + --accent: hotpink; + --background: #fdfdfd; + font-family: "Nunito Sans", sans-serif; +} +body, +html { + width: 100%; + height: 100%; + margin: 0; + padding: 0; +} +body { + padding: 3em; + max-width: 52em; + margin: 0 auto; +} +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; + appearance: none; + -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; +} +.menu { + padding: 1em; + transform: translateX(-100%); + display: flex; + flex-direction: column; + top: 0; + bottom: 0; + transition: all 0.3s ease-out; + gap: 1em; +} +.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; +} +.menu a:hover { + background: var(--accent); + color: var(--background); +} +.is-menu-open .menu { + transform: translateX(0); + transition-duration: 0.1s; +} +.is-menu-open .burger { + color: transparent; +} + +.is-menu-open .burger, +#Loading { + top: 0; + right: 0; + width: 100%; + height: 100%; + border-radius: 0; + background: rgba(17, 17, 17, 0.2); + cursor: default; +} +#Loading { + position: fixed; + text-align: center; + align-items: center; + justify-content: center; + font-size: 8rem; + color: var(--accent); + opacity: 0; + transition: opacity 0.3s ease-in, height 0s linear 0.3s; + height: 0; + 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; +} +@keyframes load { + 0% { + transform: scale(0.95); + } + 5% { + transform: scale(1.1); + } + 39% { + transform: scale(0.85); + } + 45% { + transform: scale(1); + } + 60% { + transform: scale(0.95); + } + 100% { + transform: scale(0.9); + } +} diff --git a/modules/templates/blog.mjs b/modules/templates/blog.mjs new file mode 100644 index 0000000..d0fff25 --- /dev/null +++ b/modules/templates/blog.mjs @@ -0,0 +1,41 @@ +//@ts-check +import { fetchText } from "../utils/fetchText.mjs"; +import { getElementByCSSSelector } from "../utils/getElementByCSSSelector.mjs"; +import { onDocumentKeyUp } from "../utils/onDocumentKey.mjs"; +import { parseFileList } from "../tickle/parseFileList.mjs"; +import { createMenuEntriesFromFileList } from "../tickle/createMenuEntriesFromFileList.mjs"; +import { sortFileListLines } from "../tickle/sortFileListLines.mjs"; +import { mode } from "../tickle/mode.mjs"; +import { bootstrapRouter } from "../tickle/bootstrapRouter.mjs"; + +export const bootstrap = async () => { + const [Menu, Body, Source, Burger] = [ + "nav", + "main", + "#Source", + ".burger", + ].map(getElementByCSSSelector); + + Burger.addEventListener("click", mode.menuOpen.toggle); + + mode.loading.on(); + const lines = await fetchText("../files.txt"); + const links = parseFileList(lines); + const firstHref = links[0].href; + sortFileListLines(links); + Menu.appendChild(createMenuEntriesFromFileList(links)); + + bootstrapRouter(firstHref, (content, raw) => { + Body.innerHTML = ""; + Source.innerHTML = raw; + Body.appendChild(content); + }); + + onDocumentKeyUp("?", mode.sourceEnabled.toggle); +}; + +export default bootstrap; + +if (!new URL(import.meta.url).searchParams.has("load")) { + bootstrap(); +} diff --git a/modules/templates/index.mjs b/modules/templates/index.mjs new file mode 100644 index 0000000..77c4894 --- /dev/null +++ b/modules/templates/index.mjs @@ -0,0 +1,2 @@ +//@ts-check +export { default as blog } from './blog.mjs' \ No newline at end of file diff --git a/modules/tickle/createMenuEntriesFromFileList.mjs b/modules/tickle/createMenuEntriesFromFileList.mjs index 63e3548..6836203 100644 --- a/modules/tickle/createMenuEntriesFromFileList.mjs +++ b/modules/tickle/createMenuEntriesFromFileList.mjs @@ -1,6 +1,6 @@ //@ts-check -import { makeTemplate } from "./utils/print.mjs"; -import { generateDomFromString } from "./utils/generateDomFromString.mjs"; +import { makeTemplate } from "../utils/print.mjs"; +import { generateDomFromString } from "../utils/generateDomFromString.mjs"; /** * @typedef {import("./parseFileList.mjs").ParsedLine} ParsedLine */ diff --git a/modules/tickle/mode.mjs b/modules/tickle/mode.mjs index 76f5718..aa192d6 100644 --- a/modules/tickle/mode.mjs +++ b/modules/tickle/mode.mjs @@ -1,8 +1,8 @@ //@ts-check -import { documentMode } from "./utils/documentMode.mjs"; +import { documentMode } from "../utils/documentMode.mjs"; export const mode = { loading: documentMode("loading"), - sourceEnable: documentMode("source-enabled"), + sourceEnabled: documentMode("source-enabled"), menuOpen: documentMode("menu-open"), }; diff --git a/modules/tickle/parseFileList.mjs b/modules/tickle/parseFileList.mjs index a691de5..b2c1228 100644 --- a/modules/tickle/parseFileList.mjs +++ b/modules/tickle/parseFileList.mjs @@ -1,6 +1,6 @@ //@ts-check -import { today } from "./utils/today.mjs"; +import { today } from "../utils/today.mjs"; /** * @typedef {Exclude, null>} ParsedLine diff --git a/modules/utils/createCustomElement.mjs b/modules/utils/createCustomElement.mjs new file mode 100644 index 0000000..764c2a9 --- /dev/null +++ b/modules/utils/createCustomElement.mjs @@ -0,0 +1,98 @@ +//@ts-check + +const createOptions = () => ({ + name: "my-custom-element", + css: ":host{}", + html: "", + ParentClass: HTMLElement, + observedAttributes: /** @type {string[]}*/ ([]), +}); + +/** + * WIP: minimal API for custom elements. Do not use! + * @typedef {ReturnType} Options + * @param {Partial} [options] + */ +export const createCustomElement = (options) => { + const { + name, + css, + html, + observedAttributes: attrs, + ParentClass, + } = { ...createOptions(), ...options }; + + class CustomClass extends ParentClass { + static template = document.createElement("template"); + static stylesheet = new CSSStyleSheet(); + + constructor(){ + super() + } + + /** + * Registers the custom element. If it was already registered, this is a no-op. + * @param {string} tag + */ + static define(tag = name) { + if (!customElements.get(tag)) { + customElements.define(tag, this); + } + } + + static get observedAttributes() { + return attrs; + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) { + return; + } + this[property] = newValue; + } + + /** @type {AbortController|null} */ + _abortController = null; + + /** + * If no