Huge updates
This commit is contained in:
@@ -3,7 +3,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
console.error('[gallery-init] BiggerPicture not found. Check script path.');
|
||||
return;
|
||||
}
|
||||
const VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'm4v', 'ogv'];
|
||||
|
||||
const isVideo = (href) => {
|
||||
if (!href) return false;
|
||||
href = href.toLowerCase();
|
||||
return VIDEO_EXTENSIONS.some(ext => href.endsWith('.' + ext));
|
||||
};
|
||||
|
||||
// 1b) Convert MP4 links into inline <video> players
|
||||
// Convert video links into inline <video> players BUT keep the <a>
|
||||
const videoLinks = document.querySelectorAll('a[href]');
|
||||
videoLinks.forEach(a => {
|
||||
const href = a.getAttribute("href");
|
||||
if (!isVideo(href)) return;
|
||||
|
||||
const video = document.createElement("video");
|
||||
video.controls = true;
|
||||
video.preload = "metadata";
|
||||
video.style.maxWidth = "100%";
|
||||
video.style.borderRadius = "6px";
|
||||
|
||||
const source = document.createElement("source");
|
||||
source.src = href;
|
||||
source.type = "video/" + href.split('.').pop(); // guesses correct mime
|
||||
video.appendChild(source);
|
||||
|
||||
// Replace the link with the inline video
|
||||
a.parentNode.replaceChild(video, a);
|
||||
});
|
||||
|
||||
// 1) Wrap Org-exported images so they’re clickable
|
||||
const imgs = document.querySelectorAll('.figure img, img.org-svg');
|
||||
imgs.forEach((img) => {
|
||||
@@ -39,7 +68,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 3) Build galleries per content container
|
||||
const containers = document.querySelectorAll('main, article, .content, body');
|
||||
containers.forEach((container) => {
|
||||
const links = Array.from(container.querySelectorAll('.figure a, a:has(img.org-svg)'));
|
||||
//const links = Array.from(container.querySelectorAll('.figure a, a:has(img.org-svg)'));
|
||||
const links = Array.from(container.querySelectorAll(
|
||||
'.figure a, a:has(img.org-svg), a[href$=".mp4"], a[href$=".webm"], a[href$=".mov"], a[href$=".m4v"], a[href$=".ogv"]'
|
||||
));
|
||||
if (!links.length) return;
|
||||
|
||||
// Start the lightbox on click
|
||||
@@ -69,7 +101,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
},
|
||||
|
||||
// Called once after open and on every slide change
|
||||
onOpen(containerEl) { setupRotation(containerEl); enhanceSVG(containerEl); },
|
||||
//onOpen(containerEl) { setupRotation(containerEl); enhanceSVG(containerEl); }
|
||||
onOpen(containerEl) {
|
||||
const linkEl = bp.currItem;
|
||||
const href = linkEl?.href || "";
|
||||
|
||||
if (href.endsWith(".mp4")) {
|
||||
// Turn off image rotation (not applicable for video)
|
||||
teardownRotation();
|
||||
|
||||
const htmlLayer = containerEl.querySelector(".bp-html");
|
||||
const imgLayer = containerEl.querySelector(".bp-img");
|
||||
|
||||
if (!htmlLayer) return;
|
||||
|
||||
// Hide the default image layer
|
||||
if (imgLayer) imgLayer.style.display = "none";
|
||||
|
||||
// Insert video player
|
||||
htmlLayer.innerHTML = `
|
||||
<video controls autoplay style="max-width:95vw; max-height:95vh">
|
||||
<source src="${href}" type="video/mp4">
|
||||
</video>
|
||||
`;
|
||||
|
||||
return; // Do not run image/SVG enhancements
|
||||
}
|
||||
|
||||
// Image behaviour
|
||||
setupRotation(containerEl);
|
||||
enhanceSVG(containerEl);
|
||||
} ,
|
||||
onUpdate(containerEl){ setupRotation(containerEl); enhanceSVG(containerEl); }
|
||||
});
|
||||
});
|
||||
|
||||
398
assets/scripts/sitemap-interactive.js
Normal file
398
assets/scripts/sitemap-interactive.js
Normal 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();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user