Files
bitvid/js/ui/components/revertModalRenderers.js
T
google-labs-jules[bot]andPR0M3TH3AN f676fb2ff5 [security] replace innerHTML in RevertModal.js
Migrated `js/ui/components/RevertModal.js` to safe DOM APIs (`createElement`, `textContent`, `DOMParser`).
Extracted rendering logic to `js/ui/components/revertModalRenderers.js`.
Updated baseline in `scripts/check-innerhtml.mjs` (0 remaining usages).
Updated tests to mock DOMParser.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-14 18:58:02 +00:00

440 lines
14 KiB
JavaScript

export function createImetaVariants(variants, utils) {
const { createListEmpty, createPlaceholder, createLinkMarkup } = utils;
if (!Array.isArray(variants) || variants.length === 0) {
return createListEmpty("No media variants provided.");
}
const ol = document.createElement("ol");
ol.className = "space-y-3";
variants.forEach((variant, index) => {
if (!variant) return;
const li = document.createElement("li");
li.className = "rounded-md border border-overlay bg-overlay-muted p-3 space-y-3";
// Header
const headerDiv = document.createElement("div");
headerDiv.className = "flex flex-wrap items-center justify-between gap-2 text-xs text-muted";
const variantLabel = document.createElement("span");
variantLabel.className = "font-semibold text-primary";
variantLabel.textContent = `Variant ${index + 1}`;
headerDiv.appendChild(variantLabel);
if (variant.autoGenerated) {
const badgesDiv = document.createElement("div");
badgesDiv.className = "flex flex-wrap gap-2";
const badge = document.createElement("span");
badge.className = "badge";
badge.dataset.variant = "warning";
badge.textContent = "Auto-generated";
badgesDiv.appendChild(badge);
headerDiv.appendChild(badgesDiv);
}
li.appendChild(headerDiv);
// DL Grid
const dl = document.createElement("dl");
dl.className = "grid gap-3 sm:grid-cols-2 text-xs text-subtle";
const createDtDd = (label, contentNode, colSpan = false) => {
const div = document.createElement("div");
if (colSpan) div.className = "sm:col-span-2";
const dt = document.createElement("dt");
dt.className = "font-semibold text-primary";
dt.textContent = label;
div.appendChild(dt);
const dd = document.createElement("dd");
dd.className = "mt-1";
dd.appendChild(contentNode);
div.appendChild(dd);
return div;
};
// Mime
const mimeNode = variant.m
? (() => {
const c = document.createElement("code");
c.className = "rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
c.textContent = variant.m;
return c;
})()
: createPlaceholder();
dl.appendChild(createDtDd("MIME type", mimeNode));
// Dim
const dimNode = variant.dim
? (() => {
const c = document.createElement("code");
c.className = "rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
c.textContent = variant.dim;
return c;
})()
: createPlaceholder();
dl.appendChild(createDtDd("Dimensions", dimNode));
// URL
const urlNode = variant.url
? createLinkMarkup(variant.url)
: createPlaceholder("No URL");
dl.appendChild(createDtDd("URL", urlNode, true));
// Hash
const hashNode = variant.x
? (() => {
const c = document.createElement("code");
c.className = "break-all rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
c.textContent = variant.x;
return c;
})()
: createPlaceholder("Not provided");
dl.appendChild(createDtDd("Content hash", hashNode, true));
li.appendChild(dl);
// Nested lists
const nestedGrid = document.createElement("div");
nestedGrid.className = "grid gap-3 sm:grid-cols-3 text-xs text-subtle";
const createNestedList = (label, values, emptyLabel) => {
const container = document.createElement("div");
const p = document.createElement("p");
p.className = "text-2xs uppercase tracking-wide text-subtle";
p.textContent = label;
container.appendChild(p);
if (!Array.isArray(values) || values.length === 0) {
container.appendChild(createListEmpty(emptyLabel));
return container;
}
const validItems = values
.map(v => createLinkMarkup(v))
.filter(node => node.textContent !== "");
if (validItems.length === 0) {
container.appendChild(createListEmpty(emptyLabel));
return container;
}
const ul = document.createElement("ul");
ul.className = "mt-1 space-y-1 text-xs text-primary";
validItems.forEach(itemNode => {
const itemLi = document.createElement("li");
itemLi.appendChild(itemNode);
ul.appendChild(itemLi);
});
container.appendChild(ul);
return container;
};
nestedGrid.appendChild(createNestedList("Images", variant.image, "No image URLs."));
nestedGrid.appendChild(createNestedList("Fallbacks", variant.fallback, "No fallback URLs."));
nestedGrid.appendChild(createNestedList("Services", variant.service, "No service hints."));
li.appendChild(nestedGrid);
ol.appendChild(li);
});
return ol;
}
export function createTextTracks(tracks, utils) {
const { createListEmpty, createPlaceholder, createLinkMarkup } = utils;
if (!Array.isArray(tracks) || tracks.length === 0) {
return createListEmpty("No caption tracks.");
}
const ol = document.createElement("ol");
ol.className = "space-y-3";
tracks.forEach((track, index) => {
const li = document.createElement("li");
li.className = "rounded-md border border-overlay bg-overlay-muted p-3 space-y-3";
const headerDiv = document.createElement("div");
headerDiv.className = "flex items-center justify-between text-xs text-muted";
const label = document.createElement("span");
label.className = "font-semibold text-primary";
label.textContent = `Track ${index + 1}`;
headerDiv.appendChild(label);
if (track.language) {
const langSpan = document.createElement("span");
langSpan.className = "pill";
langSpan.dataset.size = "compact";
langSpan.dataset.variant = "neutral";
langSpan.textContent = track.language;
headerDiv.appendChild(langSpan);
}
li.appendChild(headerDiv);
const dl = document.createElement("dl");
dl.className = "grid gap-3 sm:grid-cols-2 text-xs text-subtle";
const createDtDd = (label, contentNode, colSpan = false) => {
const div = document.createElement("div");
if (colSpan) div.className = "sm:col-span-2";
const dt = document.createElement("dt");
dt.className = "font-semibold text-primary";
dt.textContent = label;
div.appendChild(dt);
const dd = document.createElement("dd");
dd.className = "mt-1";
dd.appendChild(contentNode);
div.appendChild(dd);
return div;
};
const urlNode = track.url
? createLinkMarkup(track.url)
: createPlaceholder("No URL");
dl.appendChild(createDtDd("URL", urlNode, true));
const typeNode = track.type
? (() => {
const c = document.createElement("code");
c.className = "rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
c.textContent = track.type;
return c;
})()
: createPlaceholder();
dl.appendChild(createDtDd("Type", typeNode));
const langNode = track.language
? (() => {
const s = document.createElement("span");
s.className = "pill";
s.dataset.size = "compact";
s.dataset.variant = "neutral";
s.textContent = track.language;
return s;
})()
: createPlaceholder();
dl.appendChild(createDtDd("Language", langNode));
li.appendChild(dl);
ol.appendChild(li);
});
return ol;
}
export function createSegments(segments, utils) {
const { createListEmpty, createPlaceholder, createLinkMarkup, formatDurationSeconds } = utils;
if (!Array.isArray(segments) || segments.length === 0) {
return createListEmpty("No chapter segments.");
}
const ol = document.createElement("ol");
ol.className = "space-y-3";
const createTimeNode = (seconds, raw) => {
if (Number.isFinite(seconds)) {
const display = formatDurationSeconds(seconds);
const suffix = seconds === 1 ? "second" : "seconds";
const frag = document.createDocumentFragment();
const span1 = document.createElement("span");
span1.className = "text-primary";
span1.textContent = display;
frag.appendChild(span1);
const space = document.createTextNode(" ");
frag.appendChild(space);
const span2 = document.createElement("span");
span2.className = "text-subtle";
span2.textContent = `(${seconds} ${suffix})`;
frag.appendChild(span2);
return frag;
}
if (raw) {
const span = document.createElement("span");
span.className = "text-primary";
span.textContent = raw;
return span;
}
return createPlaceholder();
};
segments.forEach((segment, index) => {
const li = document.createElement("li");
li.className = "rounded-md border border-overlay bg-overlay-muted p-3 space-y-3";
const headerDiv = document.createElement("div");
headerDiv.className = "text-xs font-semibold text-primary";
headerDiv.textContent = `Chapter ${index + 1}`;
li.appendChild(headerDiv);
const dl = document.createElement("dl");
dl.className = "grid gap-3 sm:grid-cols-2 text-xs text-subtle";
const createDtDd = (label, contentNode, colSpan = false) => {
const div = document.createElement("div");
if (colSpan) div.className = "sm:col-span-2";
const dt = document.createElement("dt");
dt.className = "font-semibold text-primary";
dt.textContent = label;
div.appendChild(dt);
const dd = document.createElement("dd");
dd.className = "mt-1";
dd.appendChild(contentNode);
div.appendChild(dd);
return div;
};
dl.appendChild(createDtDd("Start", createTimeNode(segment.startSeconds, segment.startRaw)));
dl.appendChild(createDtDd("End", createTimeNode(segment.endSeconds, segment.endRaw)));
const titleNode = segment.title
? (() => {
const p = document.createElement("p");
p.className = "whitespace-pre-wrap text-primary";
p.textContent = segment.title;
return p;
})()
: createPlaceholder("No title");
dl.appendChild(createDtDd("Title", titleNode, true));
const thumbNode = segment.thumbnail
? createLinkMarkup(segment.thumbnail)
: createPlaceholder("No thumbnail");
dl.appendChild(createDtDd("Thumbnail", thumbNode, true));
li.appendChild(dl);
ol.appendChild(li);
});
return ol;
}
export function createHashtags(hashtags, utils) {
const { createListEmpty } = utils;
if (!Array.isArray(hashtags) || hashtags.length === 0) {
return createListEmpty("No hashtags provided.");
}
const div = document.createElement("div");
div.className = "flex flex-wrap gap-2";
hashtags.forEach(tag => {
const label = tag.startsWith("#") ? tag : `#${tag}`;
const span = document.createElement("span");
span.className = "pill";
span.dataset.size = "compact";
span.dataset.variant = "neutral";
span.textContent = label;
div.appendChild(span);
});
return div;
}
export function createParticipants(participants, utils) {
const { createListEmpty, createPlaceholder, createLinkMarkup, truncateMiddle } = utils;
if (!Array.isArray(participants) || participants.length === 0) {
return createListEmpty("No participants listed.");
}
const ol = document.createElement("ol");
ol.className = "space-y-3";
participants.forEach((participant, index) => {
const li = document.createElement("li");
li.className = "rounded-md border border-overlay bg-overlay-muted p-3 space-y-2";
const headerDiv = document.createElement("div");
headerDiv.className = "text-xs font-semibold text-primary";
headerDiv.textContent = `Participant ${index + 1}`;
li.appendChild(headerDiv);
const dl = document.createElement("dl");
dl.className = "grid gap-3 sm:grid-cols-2 text-xs text-subtle";
const createDtDd = (label, contentNode, colSpan = false) => {
const div = document.createElement("div");
if (colSpan) div.className = "sm:col-span-2";
const dt = document.createElement("dt");
dt.className = "font-semibold text-primary";
dt.textContent = label;
div.appendChild(dt);
const dd = document.createElement("dd");
dd.className = "mt-1";
dd.appendChild(contentNode);
div.appendChild(dd);
return div;
};
const displayPubkey = participant.pubkey
? truncateMiddle
? truncateMiddle(participant.pubkey, 48)
: participant.pubkey
: "";
const pubkeyNode = participant.pubkey
? (() => {
const c = document.createElement("code");
c.className = "break-all rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
c.title = participant.pubkey;
c.textContent = displayPubkey;
return c;
})()
: createPlaceholder("No pubkey");
dl.appendChild(createDtDd("Pubkey", pubkeyNode, true));
const relayNode = participant.relay
? createLinkMarkup(participant.relay, { breakAll: false })
: createPlaceholder("No relay specified");
dl.appendChild(createDtDd("Relay", relayNode, true));
li.appendChild(dl);
ol.appendChild(li);
});
return ol;
}
export function createReferences(references, utils) {
const { createListEmpty, createLinkMarkup } = utils;
if (!Array.isArray(references) || references.length === 0) {
return createListEmpty("No external references.");
}
const items = references
.map((entry) => createLinkMarkup(entry))
.filter(node => node.textContent !== "");
if (items.length === 0) {
return createListEmpty("No external references.");
}
const ul = document.createElement("ul");
ul.className = "space-y-1 text-xs text-primary";
items.forEach(node => {
const li = document.createElement("li");
li.appendChild(node);
ul.appendChild(li);
});
return ul;
}