94 lines
2.8 KiB
JavaScript
94 lines
2.8 KiB
JavaScript
"use strict";
|
|
|
|
export const debugPrefix = "[racket-wiki:cmap 0.2.122]";
|
|
|
|
export function debug(message, details) {
|
|
if (details === undefined) {
|
|
console.info(debugPrefix, message);
|
|
return;
|
|
}
|
|
console.info(debugPrefix, message, details);
|
|
}
|
|
|
|
export function elementDescription(element) {
|
|
if (!(element instanceof Element)) return String(element);
|
|
return {
|
|
tag: element.tagName,
|
|
id: element.id || null,
|
|
classes: Array.from(element.classList),
|
|
itemId: element.dataset.rwCmapItemId || null
|
|
};
|
|
}
|
|
|
|
export function selectionStyle(element) {
|
|
if (!(element instanceof Element) || typeof window.getComputedStyle !== "function") return null;
|
|
const style = window.getComputedStyle(element);
|
|
return {
|
|
pointerEvents: style.pointerEvents,
|
|
outline: style.outline,
|
|
outlineOffset: style.outlineOffset,
|
|
boxShadow: style.boxShadow,
|
|
overflow: style.overflow,
|
|
zIndex: style.zIndex
|
|
};
|
|
}
|
|
|
|
export function escapeHtml(value) {
|
|
return String(value || "")
|
|
.replaceAll("&", "&")
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll('"', """)
|
|
.replaceAll("'", "'");
|
|
}
|
|
|
|
export function numberOr(value, fallback) {
|
|
return Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
export function conceptDescriptionReference(label, id) {
|
|
const slug = String(label || "")
|
|
.normalize("NFKD")
|
|
.toLocaleLowerCase()
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
.replace(/^-+|-+$/g, "")
|
|
.slice(0, 120)
|
|
.replace(/-+$/g, "");
|
|
return `cmap:${slug || `concept-${id}`}`;
|
|
}
|
|
|
|
export function newConceptId() {
|
|
if (window.crypto && typeof window.crypto.randomUUID === "function") {
|
|
try {
|
|
return window.crypto.randomUUID().toLowerCase();
|
|
} catch (_error) {
|
|
// randomUUID can be exposed but forbidden in an insecure/file context.
|
|
}
|
|
}
|
|
const bytes = new Uint8Array(16);
|
|
if (window.crypto && typeof window.crypto.getRandomValues === "function") {
|
|
window.crypto.getRandomValues(bytes);
|
|
} else {
|
|
for (let index = 0; index < bytes.length; index += 1) {
|
|
bytes[index] = Math.floor(Math.random() * 256);
|
|
}
|
|
}
|
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
|
|
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
}
|
|
|
|
export function normalizeConceptTags(tags) {
|
|
if (!Array.isArray(tags)) return [];
|
|
return tags.map((tag) => {
|
|
if (typeof tag === "string") return { type: "label", value: tag.trim() };
|
|
if (!tag || typeof tag !== "object") return null;
|
|
return {
|
|
type: String(tag.type || "label").trim() || "label",
|
|
value: String(tag.value || tag.name || "").trim()
|
|
};
|
|
}).filter((tag) => tag && tag.value);
|
|
}
|