removing author and cleaning js / css

This commit is contained in:
gitea-actions
2026-07-08 22:28:43 +01:00
parent d0610a5907
commit 0db2151f92
81 changed files with 18580 additions and 18565 deletions

View File

@@ -0,0 +1,398 @@
/**
* sitemap-interactive.js
* Drop into /assets/scripts/ — no .org changes required.
*
* Handles two page structures generated by org-publish:
* 1. FLAT LIST — a top-level <ul class="org-ul"> (e.g. Sitemap)
* 2. OUTLINE — <div class="outline-2/3"> with <h2>/<h3> headings and
* nested <ul class="org-ul"> (e.g. "2025 List", "Blogs List")
*
* Activated on any page whose <h1 class="title"> matches SITEMAP_PATTERNS.
*/
(function () {
"use strict";
const SITEMAP_PATTERNS = ["sitemap", "list"];
function init() {
const titleEl = document.querySelector("h1.title");
if (!titleEl) return;
const title = titleEl.textContent.trim().toLowerCase();
if (!SITEMAP_PATTERNS.some((p) => title.includes(p))) return;
const contentDiv = document.getElementById("content");
if (!contentDiv) return;
const hasOutline = !!contentDiv.querySelector(".outline-2, .outline-3");
const topUl = !hasOutline && contentDiv.querySelector("ul.org-ul");
if (!hasOutline && !topUl) return;
// ── Parsers ───────────────────────────────────────────────────────────
function parseLi(li) {
const link = li.querySelector(":scope > a");
const childUl = li.querySelector(":scope > ul");
const textNode = Array.from(li.childNodes).find(
(n) => n.nodeType === Node.TEXT_NODE && n.textContent.trim()
);
const tags = Array.from(li.querySelectorAll(".post-tag")).map((t) =>
t.textContent.trim()
);
const dateEl = li.querySelector(".post-date");
const date = dateEl ? dateEl.textContent.trim() : null;
return {
label: link
? link.textContent.trim()
: textNode
? textNode.textContent.trim()
: li.childNodes[0]?.textContent?.trim() || "",
href: link ? link.getAttribute("href") : null,
tags,
date,
children: childUl ? parseUl(childUl) : [],
};
}
function parseUl(ul) {
return Array.from(ul.children)
.filter((li) => li.tagName === "LI")
.map(parseLi);
}
function parseFlatList() {
return { tree: parseUl(topUl), replaceTarget: topUl, wrapOutline: false };
}
function parseOutline() {
const tree = [];
const outline2s = contentDiv.querySelectorAll(":scope .outline-2");
if (outline2s.length === 0) {
// Only outline-3 directly (flat month grouping without year wrapper)
contentDiv.querySelectorAll(":scope .outline-3").forEach((section) => {
const heading = section.querySelector("h3");
const ul = section.querySelector("ul.org-ul");
tree.push({
label: heading ? heading.textContent.trim() : "Section",
href: null, tags: [], date: null,
children: ul ? parseUl(ul) : [],
});
});
} else {
outline2s.forEach((o2) => {
const h2 = o2.querySelector(":scope > div > h2, :scope > h2");
const groupNode = {
label: h2 ? h2.textContent.trim() : "Group",
href: null, tags: [], date: null,
children: [],
};
const outline3s = o2.querySelectorAll(".outline-3");
if (outline3s.length > 0) {
outline3s.forEach((o3) => {
const h3 = o3.querySelector(":scope > div > h3, :scope > h3");
const ul = o3.querySelector("ul.org-ul");
groupNode.children.push({
label: h3 ? h3.textContent.trim() : "Month",
href: null, tags: [], date: null,
children: ul ? parseUl(ul) : [],
});
});
} else {
const ul = o2.querySelector("ul.org-ul");
if (ul) groupNode.children = parseUl(ul);
}
tree.push(groupNode);
});
}
return { tree, replaceTarget: null, wrapOutline: true };
}
const parsed = hasOutline ? parseOutline() : parseFlatList();
const { tree } = parsed;
if (!tree.length) return;
// ── Styles ────────────────────────────────────────────────────────────
if (!document.getElementById("sm-styles")) {
const style = document.createElement("style");
style.id = "sm-styles";
style.textContent = `
.sitemap-interactive { font-family: inherit; margin: 1.5rem 0; }
.sm-toolbar {
display: flex; align-items: center; gap: .6rem;
margin-bottom: 1rem; flex-wrap: wrap;
}
.sm-search {
flex: 1; min-width: 160px; padding: .35rem .7rem;
border: 1px solid var(--border, #444); border-radius: 4px;
background: var(--bg, transparent); color: inherit; font-size: .9rem;
}
.sm-search:focus { outline: 2px solid var(--accent, #7aa2f7); outline-offset: 1px; }
.sm-expand-all, .sm-collapse-all {
padding: .3rem .65rem; border: 1px solid var(--border, #444);
border-radius: 4px; background: transparent; color: inherit;
font-size: .8rem; cursor: pointer; opacity: .75; transition: opacity .15s;
}
.sm-expand-all:hover, .sm-collapse-all:hover { opacity: 1; }
.sm-tree ul { list-style: none; margin: 0; padding: 0 0 0 1.4rem; }
.sm-tree > ul { padding-left: 0; }
.sm-tree li { margin: 0; }
.sm-node {
display: flex; align-items: center; gap: .4rem;
padding: .2rem .3rem; border-radius: 4px;
line-height: 1.5; transition: background .1s; flex-wrap: wrap;
}
.sm-node:hover { background: var(--hover-bg, rgba(122,162,247,.08)); }
.sm-node.sm-hidden { display: none; }
.sm-toggle {
width: 1.2rem; height: 1.2rem; display: inline-flex;
align-items: center; justify-content: center;
cursor: pointer; border: none; background: none; color: inherit;
font-size: .7rem; opacity: .6;
transition: transform .18s, opacity .15s;
flex-shrink: 0; padding: 0; border-radius: 3px;
}
.sm-toggle:hover { opacity: 1; background: var(--hover-bg, rgba(122,162,247,.15)); }
.sm-toggle.open { transform: rotate(90deg); }
.sm-toggle-placeholder { width: 1.2rem; flex-shrink: 0; }
.sm-icon { font-size: .8rem; opacity: .5; flex-shrink: 0; }
.sm-label a { color: var(--link, inherit); text-decoration: none; font-size: .9rem; }
.sm-label a:hover { text-decoration: underline; }
.sm-label span { font-size: .9rem; font-weight: 600; opacity: .85; }
.sm-badge {
font-size: .65rem; padding: .05rem .35rem; border-radius: 8px;
background: var(--badge-bg, rgba(122,162,247,.15));
color: var(--badge-fg, #7aa2f7); opacity: .8;
}
.sm-date { font-size: .75rem; opacity: .45; margin-left: .2rem; }
.sm-tags { display: inline-flex; gap: .25rem; margin-left: .2rem; }
.sm-tag {
font-size: .65rem; padding: .05rem .3rem; border-radius: 8px;
background: var(--tag-bg, rgba(160,200,120,.15));
color: var(--tag-fg, #9ece6a); opacity: .85;
}
.sm-children { overflow: hidden; }
.sm-children.collapsed { display: none; }
.sm-label mark {
background: var(--mark-bg, rgba(255,200,50,.3));
color: inherit; border-radius: 2px; padding: 0 1px;
}
.sm-count { font-size: .78rem; opacity: .45; margin-top: .8rem; }
`;
document.head.appendChild(style);
}
// ── Widget scaffold ───────────────────────────────────────────────────
const wrapper = document.createElement("div");
wrapper.className = "sitemap-interactive";
wrapper.innerHTML = `
<div class="sm-toolbar">
<input class="sm-search" type="search" placeholder="Filter pages…" aria-label="Filter" />
<button class="sm-expand-all">⊞ Expand all</button>
<button class="sm-collapse-all">⊟ Collapse all</button>
</div>
<div class="sm-tree" role="tree"></div>
<p class="sm-count"></p>
`;
const treeEl = wrapper.querySelector(".sm-tree");
const searchEl = wrapper.querySelector(".sm-search");
const countEl = wrapper.querySelector(".sm-count");
// ── Tree builder ──────────────────────────────────────────────────────
function countLeaves(nodes) {
let n = 0;
for (const node of nodes) {
if (!node.children || !node.children.length) n++;
else n += countLeaves(node.children);
}
return n;
}
function buildTree(nodes) {
const ul = document.createElement("ul");
for (const node of nodes) {
const li = document.createElement("li");
const row = document.createElement("div");
row.className = "sm-node";
const hasChildren = node.children && node.children.length > 0;
let childrenEl;
if (hasChildren) {
const toggle = document.createElement("button");
toggle.className = "sm-toggle open";
toggle.innerHTML = "▶";
toggle.setAttribute("aria-expanded", "true");
childrenEl = document.createElement("div");
childrenEl.className = "sm-children";
childrenEl.appendChild(buildTree(node.children));
toggle.addEventListener("click", () => {
childrenEl.classList.toggle("collapsed");
toggle.classList.toggle("open");
toggle.setAttribute("aria-expanded",
String(!childrenEl.classList.contains("collapsed")));
});
row.appendChild(toggle);
} else {
const ph = document.createElement("span");
ph.className = "sm-toggle-placeholder";
row.appendChild(ph);
}
const icon = document.createElement("span");
icon.className = "sm-icon";
icon.textContent = hasChildren ? "📂" : "📄";
row.appendChild(icon);
const labelEl = document.createElement("span");
labelEl.className = "sm-label";
if (node.href) {
const a = document.createElement("a");
a.href = node.href;
a.textContent = node.label;
labelEl.appendChild(a);
} else {
const s = document.createElement("span");
s.textContent = node.label;
labelEl.appendChild(s);
}
row.appendChild(labelEl);
if (hasChildren) {
const badge = document.createElement("span");
badge.className = "sm-badge";
badge.textContent = countLeaves(node.children);
row.appendChild(badge);
}
if (node.date) {
const dateSpan = document.createElement("span");
dateSpan.className = "sm-date";
dateSpan.textContent = node.date;
row.appendChild(dateSpan);
}
if (node.tags && node.tags.length) {
const tagsEl = document.createElement("span");
tagsEl.className = "sm-tags";
node.tags.forEach((t) => {
const tag = document.createElement("span");
tag.className = "sm-tag";
tag.textContent = t;
tagsEl.appendChild(tag);
});
row.appendChild(tagsEl);
}
li.appendChild(row);
if (hasChildren) li.appendChild(childrenEl);
ul.appendChild(li);
}
return ul;
}
treeEl.appendChild(buildTree(tree));
// ── Search / filter ───────────────────────────────────────────────────
function escapeRe(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function filterTree(q) {
const query = q.trim().toLowerCase();
const re = query ? new RegExp(escapeRe(query), "gi") : null;
let visible = 0;
function walk(ul) {
let anyVisible = false;
for (const li of ul.children) {
const row = li.querySelector(":scope > .sm-node");
const childrenDiv = li.querySelector(":scope > .sm-children");
const labelEl = row.querySelector(".sm-label");
const target = labelEl.querySelector("a") || labelEl.querySelector("span");
const text = target.dataset.orig || target.textContent;
target.dataset.orig = text;
let selfMatch = false;
if (!query) {
target.innerHTML = "";
target.textContent = text;
selfMatch = true;
} else if (text.toLowerCase().includes(query)) {
target.innerHTML = text.replace(re, (m) => `<mark>${m}</mark>`);
selfMatch = true;
} else {
target.innerHTML = "";
target.textContent = text;
}
let childVisible = false;
if (childrenDiv) {
childVisible = walk(childrenDiv.querySelector("ul"));
if (query) {
childrenDiv.classList.toggle("collapsed", !childVisible && !selfMatch);
const toggle = row.querySelector(".sm-toggle");
if (toggle) toggle.classList.toggle("open", childVisible || selfMatch);
}
}
const show = !query || selfMatch || childVisible;
row.classList.toggle("sm-hidden", !show);
if (show) {
anyVisible = true;
if (!childrenDiv) visible++;
}
}
return anyVisible;
}
walk(treeEl.querySelector("ul"));
countEl.textContent = query
? `${visible} page${visible !== 1 ? "s" : ""} matching "${query}"`
: "";
}
searchEl.addEventListener("input", (e) => filterTree(e.target.value));
wrapper.querySelector(".sm-expand-all").addEventListener("click", () => {
treeEl.querySelectorAll(".sm-children").forEach((el) => el.classList.remove("collapsed"));
treeEl.querySelectorAll(".sm-toggle").forEach((el) => {
el.classList.add("open");
el.setAttribute("aria-expanded", "true");
});
});
wrapper.querySelector(".sm-collapse-all").addEventListener("click", () => {
treeEl.querySelectorAll(".sm-children").forEach((el) => el.classList.add("collapsed"));
treeEl.querySelectorAll(".sm-toggle").forEach((el) => {
el.classList.remove("open");
el.setAttribute("aria-expanded", "false");
});
});
// ── Inject into page ──────────────────────────────────────────────────
if (parsed.wrapOutline) {
// Hide original outline divs (direct children of content only)
contentDiv.querySelectorAll(":scope > .outline-2, :scope > .outline-3")
.forEach((el) => (el.style.display = "none"));
// Insert after title + optional intro <p>
const insertAfter = contentDiv.querySelector(":scope > p") || titleEl;
insertAfter.insertAdjacentElement("afterend", wrapper);
} else {
parsed.replaceTarget.replaceWith(wrapper);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();