DevPlace CI / test (push) Successful in 32m28s
Add `_alt_from_url` helper in Python rendering and `altFromUrl` in JS ContentRenderer to generate descriptive alt attributes from image filenames, replacing empty alt strings. Update image embed templates and Avatar component to use derived alt text. Migrate inline `style.width` assignments to CSS custom properties (`--poll-pct`, `--hud-xp`, `--quest-pct`, `--bar-pct`, `--quota-pct`) across PollManager, GameFarm, and profile/quest CSS for better maintainability. Introduce new admin, docs, projects, services, and base CSS utility classes. Add "Code Farm" docs page entry.
92 lines
3.1 KiB
JavaScript
92 lines
3.1 KiB
JavaScript
// retoor <retoor@molodetz.nl>
|
|
|
|
import { Component } from "./Component.js";
|
|
import { assetUrl } from "../assetVersion.js";
|
|
|
|
const LB_CSS_ID = "lightbox-css";
|
|
|
|
export class AppLightbox extends Component {
|
|
connectedCallback() {
|
|
if (this._built) {
|
|
return;
|
|
}
|
|
this._built = true;
|
|
this._ensureCss();
|
|
this.lastFocus = null;
|
|
this.bodyOverflow = "";
|
|
this.build();
|
|
this.bind();
|
|
}
|
|
|
|
_ensureCss() {
|
|
if (document.getElementById(LB_CSS_ID)) return;
|
|
const link = document.createElement("link");
|
|
link.id = LB_CSS_ID;
|
|
link.rel = "stylesheet";
|
|
link.href = assetUrl("/static/css/lightbox.css");
|
|
document.head.appendChild(link);
|
|
}
|
|
|
|
build() {
|
|
const overlay = document.createElement("div");
|
|
overlay.className = "lightbox-overlay";
|
|
overlay.setAttribute("role", "dialog");
|
|
overlay.setAttribute("aria-modal", "true");
|
|
overlay.setAttribute("aria-label", "Image viewer");
|
|
overlay.innerHTML =
|
|
'<button type="button" class="lightbox-close" aria-label="Close">×</button>' +
|
|
'<img class="lightbox-image" src="" alt="">';
|
|
this.appendChild(overlay);
|
|
|
|
this.overlay = overlay;
|
|
this.image = overlay.querySelector(".lightbox-image");
|
|
this.closeBtn = overlay.querySelector(".lightbox-close");
|
|
}
|
|
|
|
bind() {
|
|
this.overlay.addEventListener("click", (e) => {
|
|
if (e.target !== this.image) this.close();
|
|
});
|
|
this.closeBtn.addEventListener("click", () => this.close());
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape" && this.overlay.classList.contains("visible")) {
|
|
e.preventDefault();
|
|
this.close();
|
|
}
|
|
});
|
|
this.overlay.addEventListener("keydown", (e) => {
|
|
if (e.key === "Tab" && this.overlay.classList.contains("visible")) {
|
|
e.preventDefault();
|
|
this.closeBtn.focus();
|
|
}
|
|
});
|
|
document.addEventListener("click", (e) => {
|
|
const thumb = e.target.closest("img[data-lightbox]");
|
|
if (!thumb || thumb.closest("a")) return;
|
|
e.preventDefault();
|
|
this.open(thumb.dataset.full || thumb.currentSrc || thumb.src, { alt: thumb.alt });
|
|
});
|
|
}
|
|
|
|
open(src, options) {
|
|
const opts = options || {};
|
|
if (!src) return;
|
|
this.image.src = src;
|
|
this.image.alt = opts.alt || "Enlarged image";
|
|
this.lastFocus = document.activeElement;
|
|
this.bodyOverflow = document.body.style.overflow;
|
|
document.body.style.overflow = "hidden";
|
|
this.overlay.classList.add("visible");
|
|
setTimeout(() => this.closeBtn.focus(), 20);
|
|
}
|
|
|
|
close() {
|
|
this.overlay.classList.remove("visible");
|
|
this.image.src = "";
|
|
document.body.style.overflow = this.bodyOverflow;
|
|
if (this.lastFocus && this.lastFocus.focus) this.lastFocus.focus();
|
|
}
|
|
}
|
|
|
|
customElements.define("dp-lightbox", AppLightbox);
|