no semicolons

This commit is contained in:
Joshua Seigler 2025-06-28 22:15:54 -04:00
parent 320b777b99
commit bf93638810
7 changed files with 528 additions and 134 deletions

View file

@ -1,65 +1,65 @@
const darkModeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
/** @param {Event} evt */ /** @param {Event} evt */
function removeEffect({ target }) { function removeEffect({ target }) {
const effectsLayer = document.querySelector("#effects"); const effectsLayer = document.querySelector("#effects")
if (effectsLayer == null) { return }; if (effectsLayer == null) {
return
}
const effects = Array.from(effectsLayer.children).filter( const effects = Array.from(effectsLayer.children).filter(
(e) => e['__effectParent'] === target, (e) => e["__effectParent"] === target
); )
effects.forEach((e) => { effects.forEach((e) => {
e.getAnimations().forEach((anim) => { e.getAnimations().forEach((anim) => {
if (+(anim.currentTime ?? 0) < 100) { if (+(anim.currentTime ?? 0) < 100) {
anim.pause(); anim.pause()
effectsLayer.removeChild(e); effectsLayer.removeChild(e)
return; return
} }
anim.pause(); anim.pause()
anim.updatePlaybackRate(-0.25); anim.updatePlaybackRate(-0.25)
anim.play(); anim.play()
anim.addEventListener("finish", () => { anim.addEventListener("finish", () => {
if (effectsLayer.contains(e)) { if (effectsLayer.contains(e)) {
effectsLayer.removeChild(e); effectsLayer.removeChild(e)
} }
}); })
}); })
}); })
} }
function isElement(target: EventTarget | null): target is Element { function isElement(target: EventTarget | null): target is Element {
return target !== null && typeof target["matches"] === 'function' return target !== null && typeof target["matches"] === "function"
} }
function addEffect({ target }: UIEvent) { function addEffect({ target }: UIEvent) {
const effectsLayer = document.querySelector("#effects"); const effectsLayer = document.querySelector("#effects")
if ( if (
!isElement(target) || !isElement(target) ||
!target.matches("a[href],.nav-toggle-button,button,input[type='radio']") !target.matches("a[href],.nav-toggle-button,button,input[type='radio']")
) { ) {
return; return
} }
const color = window.getComputedStyle(target).getPropertyValue('--glowColor'); const color = window.getComputedStyle(target).getPropertyValue("--glowColor")
const rects = Array.from(target.getClientRects()); const rects = Array.from(target.getClientRects())
Array.from(target.children).forEach((child) => { Array.from(target.children).forEach((child) => {
rects.push(...Array.from(child.getClientRects())); rects.push(...Array.from(child.getClientRects()))
}); })
rects.forEach((rect) => { rects.forEach((rect) => {
const { top, left, width, height } = rect; const { top, left, width, height } = rect
const newEffect = document.createElement("div"); const newEffect = document.createElement("div")
newEffect['__effectParent'] = target; newEffect["__effectParent"] = target
newEffect.classList.add("effect-instance"); newEffect.classList.add("effect-instance")
const padding = "10rem"; const padding = "10rem"
newEffect.style.top = `calc(${top + window.scrollY}px - ${padding})`; newEffect.style.top = `calc(${top + window.scrollY}px - ${padding})`
newEffect.style.left = `calc(${left + window.scrollX}px - ${padding})`; newEffect.style.left = `calc(${left + window.scrollX}px - ${padding})`
newEffect.style.width = `calc(${width}px + 2 * ${padding})`; newEffect.style.width = `calc(${width}px + 2 * ${padding})`
newEffect.style.height = `calc(${height}px + 2 * ${padding})`; newEffect.style.height = `calc(${height}px + 2 * ${padding})`
newEffect.style.setProperty('--glowColor', color); newEffect.style.setProperty("--glowColor", color)
effectsLayer?.appendChild(newEffect); effectsLayer?.appendChild(newEffect)
}); })
} }
document.addEventListener("mouseenter", addEffect, true); document.addEventListener("mouseenter", addEffect, true)
document.addEventListener("focus", addEffect, true); document.addEventListener("focus", addEffect, true)
document.addEventListener("mouseleave", removeEffect, true); document.addEventListener("mouseleave", removeEffect, true)
document.addEventListener("blur", removeEffect, true); document.addEventListener("blur", removeEffect, true)

View file

@ -1,28 +1,28 @@
import fs from "fs"; import fs from "fs"
import path from "path"; import path from "path"
import md from "markdown-it"; import md from "markdown-it"
import mdAnchor from "markdown-it-anchor"; import mdAnchor from "markdown-it-anchor"
import { spoiler as mdSpoiler } from "@mdit/plugin-spoiler"; import { spoiler as mdSpoiler } from "@mdit/plugin-spoiler"
import { footnote as mdFootnote } from "@mdit/plugin-footnote"; import { footnote as mdFootnote } from "@mdit/plugin-footnote"
import mdLinkAttributes from "markdown-it-link-attributes"; import mdLinkAttributes from "markdown-it-link-attributes"
import mdPrism from "markdown-it-prism"; import mdPrism from "markdown-it-prism"
import dayjs from "dayjs"; import dayjs from "dayjs"
import utc from "dayjs/plugin/utc.js"; import utc from "dayjs/plugin/utc.js"
import clean from "eleventy-plugin-clean"; import site from "./site/_data/site.js"
import toc from "eleventy-plugin-toc"; import clean from "eleventy-plugin-clean"
import site from "./site/_data/site.js"; import toc from "eleventy-plugin-toc"
import { feedPlugin as EleventyFeedPlugin } from "@11ty/eleventy-plugin-rss"; import { feedPlugin as EleventyFeedPlugin } from "@11ty/eleventy-plugin-rss"
import EleventyVitePlugin from "@11ty/eleventy-plugin-vite"; import EleventyVitePlugin from "@11ty/eleventy-plugin-vite"
import { execSync } from "child_process"; import { ViteMinifyPlugin } from "vite-plugin-minify"
import fetch from "@11ty/eleventy-fetch"; import { execSync } from "child_process"
import { XMLValidator, XMLParser } from "fast-xml-parser"; import fetch from "@11ty/eleventy-fetch"
import { ViteMinifyPlugin } from "vite-plugin-minify"; import { XMLValidator, XMLParser } from "fast-xml-parser"
dayjs.extend(utc); dayjs.extend(utc)
export default async (config) => { export default async (config) => {
const slugify = config.getFilter("slugify"); const slugify = config.getFilter("slugify")
const url = config.getFilter("url"); const url = config.getFilter("url")
const mdLib = md({ const mdLib = md({
html: true, html: true,
breaks: true, breaks: true,
@ -42,94 +42,92 @@ export default async (config) => {
}) })
.use(mdLinkAttributes, { .use(mdLinkAttributes, {
matcher(href) { matcher(href) {
return href.match(/^https?:\/\//); return href.match(/^https?:\/\//)
}, },
attrs: { attrs: {
target: "_blank", target: "_blank",
rel: "noopener", rel: "noopener",
}, },
}) })
.use(mdPrism); .use(mdPrism)
mdLib.renderer.rules.render_footnote_anchor = ( mdLib.renderer.rules.render_footnote_anchor = (
tokens, tokens,
idx, idx,
options, options,
env, env,
slf, slf
) => { ) => {
let id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); let id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf)
if (tokens[idx].meta.subId > 0) id += `:${tokens[idx].meta.subId}`; if (tokens[idx].meta.subId > 0) id += `:${tokens[idx].meta.subId}`
/* ↩ with escape code to prevent display as Apple Emoji on iOS */ /* ↩ with escape code to prevent display as Apple Emoji on iOS */
return ` <a href="#fnref${id}" class="footnote-backref">\u21a9\uFE0E</a>`; return ` <a href="#fnref${id}" class="footnote-backref">\u21a9\uFE0E</a>`
}; }
config.setLibrary("md", mdLib); config.setLibrary("md", mdLib)
config.addPassthroughCopy({ config.addPassthroughCopy({
assets: "/", assets: "/",
}); })
// collection from music folder // collection from music folder
config.addPassthroughCopy("site/music", { config.addPassthroughCopy("site/music", {
rename: (filename) => { rename: (filename) => {
const ext = path.extname(filename); const ext = path.extname(filename)
const base = path.basename(filename, ext); const base = path.basename(filename, ext)
return `${slugify(base)}${ext}`; return `${slugify(base)}${ext}`
}, },
}); })
config.addCollection("music", () => { config.addCollection("music", () => {
const musicFiles = fs.readdirSync("./site/music/").map((filename) => { const musicFiles = fs.readdirSync("./site/music/").map((filename) => {
const ext = path.extname(filename); const ext = path.extname(filename)
const base = path.basename(filename, ext); const base = path.basename(filename, ext)
const absUrl = `/music/${slugify(base)}${ext}`; const absUrl = `/music/${slugify(base)}${ext}`
return { return {
data: { data: {
title: base, title: base,
tags: ["music"], tags: ["music"],
}, },
url: url(absUrl), url: url(absUrl),
}; }
}); })
return musicFiles; return musicFiles
}); })
config.addCollection("categories", (collectionApi) => { config.addCollection("categories", (collectionApi) => {
const posts = collectionApi const posts = collectionApi
.getFilteredByTag("posts") .getFilteredByTag("posts")
.filter( .filter((p) => !p.data.draft || process.env.ELEVENTY_RUN_MODE !== "build")
(p) => !p.data.draft || process.env.ELEVENTY_RUN_MODE !== "build",
);
const categories = posts.reduce((tags, post) => { const categories = posts.reduce((tags, post) => {
post.data.tags post.data.tags
.filter((tag) => tag !== "posts") .filter((tag) => tag !== "posts")
.forEach((tag) => { .forEach((tag) => {
const prev = tags[tag] ?? { id: Object.keys(tags).length, count: 0 }; const prev = tags[tag] ?? { id: Object.keys(tags).length, count: 0 }
tags[tag] = { ...prev, count: prev.count + 1 }; tags[tag] = { ...prev, count: prev.count + 1 }
}); })
return tags; return tags
}, {}); }, {})
return Object.fromEntries( return Object.fromEntries(
Object.entries(categories).sort((a, b) => { Object.entries(categories).sort((a, b) => {
return b[1].count - a[1].count; return b[1].count - a[1].count
}), })
); )
}); })
config.addCollection("webroll", fetchShaarliWebroll); config.addCollection("webroll", fetchShaarliWebroll)
config.addFilter("toISOString", (dateString) => { config.addFilter("toISOString", (dateString) => {
return new Date(dateString).toISOString(); return new Date(dateString).toISOString()
}); })
config.addFilter("formatDate", (date, format) => { config.addFilter("formatDate", (date, format) => {
return dayjs(date).utc().format(format); return dayjs(date).utc().format(format)
}); })
config.addFilter("markdown", (markdownString) => { config.addFilter("markdown", (markdownString) => {
return mdLib.renderInline(String(markdownString ?? "").trim()); return mdLib.renderInline(String(markdownString ?? "").trim())
}); })
clean.updateFileRecord("dist"); clean.updateFileRecord("dist")
const buildTime = new Date().toISOString().replace(/[:.-]/g, ""); const buildTime = new Date().toISOString().replace(/[:.-]/g, "")
config.addGlobalData("buildTime", buildTime); config.addGlobalData("buildTime", buildTime)
config.addPlugin(EleventyFeedPlugin, { config.addPlugin(EleventyFeedPlugin, {
type: "atom", // "atom", ""rss", or "json" type: "atom", // "atom", ""rss", or "json"
@ -149,61 +147,65 @@ export default async (config) => {
}, },
}, },
stylesheet: "/simple-atom.xslt", stylesheet: "/simple-atom.xslt",
}); })
config.addPlugin(EleventyVitePlugin, { config.addPlugin(EleventyVitePlugin, {
viteOptions: { viteOptions: {
server: { server: {
port: 8080 port: 8080,
}, },
build: { build: {
mode: 'production', mode: "production",
sourcemap: true, sourcemap: true,
}, },
plugins: [ plugins: [ViteMinifyPlugin({})],
ViteMinifyPlugin({}) },
] })
}
});
config.addPlugin(toc); config.addPlugin(toc)
config.on("eleventy.after", () => { config.on("eleventy.after", () => {
execSync(`npx pagefind --site dist --glob \"**/*.html\"`, { execSync(`npx pagefind --site dist --glob \"**/*.html\"`, {
encoding: "utf-8", encoding: "utf-8",
}); })
}); })
return { return {
dir: { dir: {
input: "site", input: "site",
output: "dist", output: "dist",
}, },
}; }
}; }
async function fetchShaarliWebroll() { async function fetchShaarliWebroll() {
const url = "https://links.apps.seigler.net/feed/atom?&searchtags=%24webroll"; const url = "https://links.apps.seigler.net/feed/atom?&searchtags=%24webroll"
const urlTextContent = await fetch(url, { duration: "30s", type: "text" }); const urlTextContent = await fetch(url, { duration: "30s", type: "text" })
const validation = XMLValidator.validate(urlTextContent); const validation = XMLValidator.validate(urlTextContent)
let feedContent; let feedContent
if (validation === true) { if (validation === true) {
feedContent = new XMLParser({ feedContent = new XMLParser({
ignoreAttributes: false, ignoreAttributes: false,
}).parse(urlTextContent).feed.entry; }).parse(urlTextContent).feed.entry
} else { } else {
throw new Error(`Invalid XML from webroll feed. Reason: ${validation.err.msg}`); throw new Error(
`Invalid XML from webroll feed. Reason: ${validation.err.msg}`
)
} }
const entries = feedContent.map(entry => { const entries = feedContent.map((entry) => {
return { return {
url: entry.link['@_href'], url: entry.link["@_href"],
data: { data: {
title: entry.title, title: entry.title,
date: new Date(entry.published), date: new Date(entry.published),
description: entry.content['#text'].split('\n<br>&#8212; <a href="https://links.apps.seigler.net/')[0], description: entry.content["#text"].split(
tags: entry.category.map(category => category['@_label']).filter(category => category !== '$webroll'), '\n<br>&#8212; <a href="https://links.apps.seigler.net/'
} )[0],
tags: entry.category
.map((category) => category["@_label"])
.filter((category) => category !== "$webroll"),
},
} }
}); })
return entries; return entries
} }

385
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,7 @@
"keywords": [], "keywords": [],
"author": "Joshua Seigler", "author": "Joshua Seigler",
"license": "ISC", "license": "ISC",
"dependencies": { "devDependencies": {
"@11ty/eleventy": "^3.1.0", "@11ty/eleventy": "^3.1.0",
"@11ty/eleventy-fetch": "^5.1.0", "@11ty/eleventy-fetch": "^5.1.0",
"@11ty/eleventy-plugin-rss": "^2.0.4", "@11ty/eleventy-plugin-rss": "^2.0.4",
@ -31,5 +31,15 @@
"markdown-it-prism": "^3.0.0", "markdown-it-prism": "^3.0.0",
"pagefind": "^1.3.0", "pagefind": "^1.3.0",
"vite-plugin-minify": "^2.1.0" "vite-plugin-minify": "^2.1.0"
},
"prettier": {
"overrides": [
{
"files": "*.{js,ts}",
"options": {
"semi": false
}
}
]
} }
} }

View file

@ -1,6 +1,6 @@
const isDev = process.env.ELEVENTY_ENV === "development"; const isDev = process.env.ELEVENTY_ENV === "development";
const baseUrl = isDev ? "localhost:8080" : "https://joshua.seigler.net/"; const baseUrl = isDev ? "http://localhost:8080" : "https://joshua.seigler.net";
export default { export default {
title: "joshua.seigler.net", title: "joshua.seigler.net",

View file

@ -5,7 +5,7 @@
<a <a
class="tag" class="tag"
style="--tagIndex:{{ collections.categories[tag].id }}" style="--tagIndex:{{ collections.categories[tag].id }}"
href="/tags/{{ tag | slugify }}" href="/tags/{{ tag | slugify }}/"
>{{ tag }}</a> >{{ tag }}</a>
{%- endif %} {%- endif %}
{% endfor -%} {% endfor -%}

View file

@ -13,7 +13,6 @@ useTitle: true
{%- if item.data.date -%} {%- if item.data.date -%}
<aside>{{item.data.date | formatDate("MMMM DD, YYYY") }}</aside> <aside>{{item.data.date | formatDate("MMMM DD, YYYY") }}</aside>
{%- endif -%} {%- endif -%}
<aside>Tags: {{ item.data.tags | join(", ") }}</aside>
{{item.data.description | safe}} {{item.data.description | safe}}
</li> </li>
{%- endfor -%} {%- endfor -%}