mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Extracted excessive DOM rendering logic from `RevertModal.js` into `revertModalRenderers.js`. This improves code readability and maintainability by separating the view construction from the modal logic. Added unit tests for the new renderers using a lightweight DOM mock. Replaced local d-tag extraction with shared utility `getDTagValueFromTags`. Co-authored-by: PR0M3TH3AN <[email protected]>
896 lines
28 KiB
JavaScript
896 lines
28 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;
|
|
}
|
|
|
|
/* Shared Helpers (internal to this module, or exported if needed elsewhere) */
|
|
|
|
function createDl() {
|
|
const dl = document.createElement("dl");
|
|
dl.className = "grid gap-3 sm:grid-cols-2 text-xs text-subtle";
|
|
return dl;
|
|
}
|
|
|
|
function createDtDd(label, contentNode, colSpan = 1) {
|
|
const div = document.createElement("div");
|
|
if (colSpan > 1) 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;
|
|
}
|
|
|
|
export function createSection(title) {
|
|
const s = document.createElement("section");
|
|
s.className = "space-y-2";
|
|
const h = document.createElement("h4");
|
|
h.className = "text-sm font-semibold text-primary";
|
|
h.textContent = title;
|
|
s.appendChild(h);
|
|
return s;
|
|
}
|
|
|
|
function createTimestampNode(seconds, raw, utils) {
|
|
const { formatAbsoluteTimestamp, formatTimeAgo, createPlaceholder } = utils;
|
|
if (Number.isFinite(seconds)) {
|
|
const abs = formatAbsoluteTimestamp ? formatAbsoluteTimestamp(seconds) : `${seconds}`;
|
|
const rel = formatTimeAgo ? formatTimeAgo(seconds) : "";
|
|
const frag = document.createDocumentFragment();
|
|
if (abs) {
|
|
const s = document.createElement("span");
|
|
s.className = "text-primary";
|
|
s.textContent = abs;
|
|
frag.appendChild(s);
|
|
frag.appendChild(document.createTextNode(" "));
|
|
}
|
|
if (rel) {
|
|
const s = document.createElement("span");
|
|
s.className = "text-muted";
|
|
s.textContent = `(${rel})`;
|
|
frag.appendChild(s);
|
|
}
|
|
return frag;
|
|
}
|
|
if (raw) {
|
|
const s = document.createElement("span");
|
|
s.className = "text-primary";
|
|
s.textContent = raw;
|
|
return s;
|
|
}
|
|
return createPlaceholder();
|
|
}
|
|
|
|
function createDurationNode(seconds, raw, utils) {
|
|
const { formatDurationSeconds, createPlaceholder } = utils;
|
|
if (Number.isFinite(seconds)) {
|
|
const display = formatDurationSeconds ? formatDurationSeconds(seconds) : `${seconds}`;
|
|
const suffix = seconds === 1 ? "second" : "seconds";
|
|
const frag = document.createDocumentFragment();
|
|
const s1 = document.createElement("span");
|
|
s1.className = "text-primary";
|
|
s1.textContent = display;
|
|
frag.appendChild(s1);
|
|
frag.appendChild(document.createTextNode(" "));
|
|
const s2 = document.createElement("span");
|
|
s2.className = "text-subtle";
|
|
s2.textContent = `(${seconds} ${suffix})`;
|
|
frag.appendChild(s2);
|
|
return frag;
|
|
}
|
|
if (raw) {
|
|
const s = document.createElement("span");
|
|
s.className = "text-primary";
|
|
s.textContent = raw;
|
|
return s;
|
|
}
|
|
return createPlaceholder();
|
|
}
|
|
|
|
function createFlagStatus(value, { yes, no, unspecified }, utils) {
|
|
const { createPlaceholder } = utils;
|
|
if (value === true) {
|
|
const s = document.createElement("span");
|
|
s.className = "text-primary";
|
|
s.textContent = yes;
|
|
return s;
|
|
}
|
|
if (value === false) {
|
|
const s = document.createElement("span");
|
|
s.className = "text-primary";
|
|
s.textContent = no;
|
|
return s;
|
|
}
|
|
return createPlaceholder(unspecified);
|
|
}
|
|
|
|
/* New Exported Renderers */
|
|
|
|
export function createHeader(version, nip71Metadata, utils) {
|
|
const {
|
|
formatAbsoluteTimestamp,
|
|
formatTimeAgo,
|
|
truncateMiddle,
|
|
createPlaceholder,
|
|
createLinkMarkup,
|
|
fallbackThumbnailSrc,
|
|
} = utils;
|
|
|
|
const wrapper = document.createElement("div");
|
|
wrapper.className = "space-y-6";
|
|
|
|
const createdAt = typeof version.created_at === "number" ? version.created_at : 0;
|
|
const absolute = formatAbsoluteTimestamp ? formatAbsoluteTimestamp(createdAt) : "";
|
|
const relative = formatTimeAgo ? formatTimeAgo(createdAt) : "";
|
|
const timestampText = [absolute, relative ? `(${relative})` : ""].filter(Boolean).join(" ");
|
|
|
|
const thumbnail = typeof version.thumbnail === "string" ? version.thumbnail.trim() : "";
|
|
const thumbnailSrc = thumbnail || fallbackThumbnailSrc;
|
|
const thumbnailAlt = thumbnail ? "Revision thumbnail" : "Fallback thumbnail";
|
|
|
|
const url = typeof version.url === "string" ? version.url.trim() : "";
|
|
const urlNode = url ? createLinkMarkup(url) : createPlaceholder("None");
|
|
|
|
const magnet = typeof version.magnet === "string" ? version.magnet.trim() : "";
|
|
const rawMagnet = typeof version.rawMagnet === "string" ? version.rawMagnet.trim() : "";
|
|
const displayMagnet = magnet || rawMagnet;
|
|
const isPrivate = version.isPrivate === true;
|
|
|
|
const magnetNode = displayMagnet
|
|
? (() => {
|
|
const div = document.createElement("div");
|
|
div.className = "break-all";
|
|
const label = truncateMiddle ? truncateMiddle(displayMagnet, 72) : displayMagnet;
|
|
div.textContent = label;
|
|
if (isPrivate) {
|
|
const span = document.createElement("span");
|
|
span.className = "block mt-1 text-xs text-status-private-on";
|
|
span.textContent = "Magnet stays visible only to you — private notes keep the raw string local.";
|
|
div.appendChild(span);
|
|
}
|
|
return div;
|
|
})()
|
|
: createPlaceholder("None");
|
|
|
|
// Chips
|
|
const chipsDiv = document.createElement("div");
|
|
chipsDiv.className = "flex flex-wrap gap-2";
|
|
|
|
const nsfwFlag = version.isNsfw === true;
|
|
const forKidsFlag = version.isForKids === true;
|
|
const conflictingAudienceFlags = nsfwFlag && forKidsFlag;
|
|
|
|
if (version.deleted) {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "critical";
|
|
s.textContent = "Marked deleted";
|
|
chipsDiv.appendChild(s);
|
|
}
|
|
if (isPrivate) {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "private";
|
|
s.textContent = "Private";
|
|
chipsDiv.appendChild(s);
|
|
}
|
|
if (conflictingAudienceFlags) {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "warning";
|
|
s.textContent = "NSFW + For kids conflict";
|
|
chipsDiv.appendChild(s);
|
|
} else {
|
|
if (nsfwFlag) {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "critical";
|
|
s.textContent = "Marked NSFW";
|
|
chipsDiv.appendChild(s);
|
|
}
|
|
if (forKidsFlag) {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "success";
|
|
s.textContent = "For kids";
|
|
chipsDiv.appendChild(s);
|
|
}
|
|
}
|
|
if (version.version !== undefined) {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "neutral";
|
|
s.textContent = `Schema v${version.version}`;
|
|
chipsDiv.appendChild(s);
|
|
}
|
|
|
|
// DOM construction
|
|
const headerRow = document.createElement("div");
|
|
headerRow.className = "flex flex-col gap-4 lg:flex-row lg:items-start";
|
|
|
|
const imgDiv = document.createElement("div");
|
|
imgDiv.className = "overflow-hidden rounded-md border border-overlay bg-overlay-strong w-full max-w-sm";
|
|
const img = document.createElement("img");
|
|
img.src = thumbnailSrc;
|
|
img.alt = thumbnailAlt;
|
|
img.className = "w-full h-auto object-cover";
|
|
img.loading = "lazy";
|
|
imgDiv.appendChild(img);
|
|
headerRow.appendChild(imgDiv);
|
|
|
|
const headerContent = document.createElement("div");
|
|
headerContent.className = "flex-1 space-y-3";
|
|
|
|
const titleTimeDiv = document.createElement("div");
|
|
titleTimeDiv.className = "space-y-1";
|
|
const titleH3 = document.createElement("h3");
|
|
titleH3.className = "text-lg font-semibold text-primary";
|
|
titleH3.textContent = version.title || "Untitled";
|
|
titleTimeDiv.appendChild(titleH3);
|
|
const timeP = document.createElement("p");
|
|
timeP.className = "text-xs text-muted";
|
|
timeP.textContent = timestampText;
|
|
titleTimeDiv.appendChild(timeP);
|
|
headerContent.appendChild(titleTimeDiv);
|
|
|
|
if (chipsDiv.hasChildNodes()) {
|
|
headerContent.appendChild(chipsDiv);
|
|
}
|
|
|
|
const headerDl = createDl();
|
|
headerDl.appendChild(createDtDd("Hosted URL", urlNode));
|
|
headerDl.appendChild(createDtDd("Magnet", magnetNode));
|
|
headerContent.appendChild(headerDl);
|
|
|
|
headerRow.appendChild(headerContent);
|
|
wrapper.appendChild(headerRow);
|
|
|
|
return wrapper;
|
|
}
|
|
|
|
export function createDescription(description, utils) {
|
|
const descSection = createSection("Description");
|
|
|
|
const descriptionNode = description
|
|
? (() => {
|
|
const p = document.createElement("p");
|
|
p.className = "whitespace-pre-wrap text-primary";
|
|
p.textContent = description;
|
|
return p;
|
|
})()
|
|
: (() => {
|
|
const p = document.createElement("p");
|
|
p.className = "text-subtle";
|
|
p.textContent = "No description provided.";
|
|
return p;
|
|
})();
|
|
|
|
descSection.appendChild(descriptionNode);
|
|
return descSection;
|
|
}
|
|
|
|
export function createAudienceFlags(version, utils) {
|
|
const audSection = createSection("Audience flags");
|
|
const audDl = createDl();
|
|
|
|
const nsfwStatusNode = createFlagStatus(version.isNsfw, {
|
|
yes: "Yes — marked NSFW",
|
|
no: "No — not flagged as NSFW",
|
|
unspecified: "Not specified",
|
|
}, utils);
|
|
|
|
const kidsStatusNode = createFlagStatus(version.isForKids, {
|
|
yes: "Yes — marked for kids",
|
|
no: "No — not marked for kids",
|
|
unspecified: "Not specified",
|
|
}, utils);
|
|
|
|
audDl.appendChild(createDtDd("NSFW flag", nsfwStatusNode));
|
|
audDl.appendChild(createDtDd("For kids flag", kidsStatusNode));
|
|
audSection.appendChild(audDl);
|
|
|
|
if (version.isNsfw === true && version.isForKids === true) {
|
|
const p = document.createElement("p");
|
|
p.className = "text-xs text-status-warning-on";
|
|
p.textContent = "Conflicting flags detected — this revision is marked both NSFW and for kids. Review before reverting.";
|
|
audSection.appendChild(p);
|
|
}
|
|
|
|
return audSection;
|
|
}
|
|
|
|
export function createEventMetadata(nip71Metadata, utils) {
|
|
const { createPlaceholder } = utils;
|
|
|
|
const eventSection = createSection("NIP-71 event metadata");
|
|
const eventDl = createDl();
|
|
|
|
const kindValue = nip71Metadata.kind;
|
|
const kindNode = (() => {
|
|
if (kindValue !== "" && kindValue !== null && kindValue !== undefined) {
|
|
const label = typeof kindValue === "number" ? kindValue : `${kindValue}`;
|
|
const numeric = Number(label);
|
|
let suffix = "";
|
|
if (Number.isFinite(numeric)) {
|
|
if (numeric === 22) suffix = "short";
|
|
else if (numeric === 21) suffix = "video";
|
|
}
|
|
|
|
const frag = document.createDocumentFragment();
|
|
const code = document.createElement("code");
|
|
code.className = "rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
|
|
code.textContent = `kind ${label}`;
|
|
frag.appendChild(code);
|
|
|
|
if (suffix) {
|
|
frag.appendChild(document.createTextNode(" "));
|
|
const s = document.createElement("span");
|
|
s.className = "text-muted";
|
|
s.textContent = `(${suffix})`;
|
|
frag.appendChild(s);
|
|
}
|
|
return frag;
|
|
}
|
|
return createPlaceholder();
|
|
})();
|
|
eventDl.appendChild(createDtDd("NIP-71 kind", kindNode));
|
|
|
|
const summaryNode = nip71Metadata.summary
|
|
? (() => {
|
|
const p = document.createElement("p");
|
|
p.className = "whitespace-pre-wrap text-primary";
|
|
p.textContent = nip71Metadata.summary;
|
|
return p;
|
|
})()
|
|
: createPlaceholder("Not provided");
|
|
eventDl.appendChild(createDtDd("Summary", summaryNode, 2));
|
|
|
|
const contentWarningNode = nip71Metadata.contentWarning
|
|
? (() => {
|
|
const s = document.createElement("span");
|
|
s.className = "pill";
|
|
s.dataset.size = "compact";
|
|
s.dataset.variant = "warning";
|
|
s.textContent = nip71Metadata.contentWarning;
|
|
return s;
|
|
})()
|
|
: createPlaceholder("Not provided");
|
|
eventDl.appendChild(createDtDd("Content warning", contentWarningNode, 2));
|
|
|
|
const publishedAtNode = createTimestampNode(
|
|
nip71Metadata.publishedAtSeconds,
|
|
nip71Metadata.publishedAtRaw,
|
|
utils
|
|
);
|
|
eventDl.appendChild(createDtDd("Published timestamp", publishedAtNode));
|
|
|
|
const durationNode = createDurationNode(
|
|
nip71Metadata.durationSeconds,
|
|
nip71Metadata.durationRaw,
|
|
utils
|
|
);
|
|
eventDl.appendChild(createDtDd("Duration", durationNode));
|
|
|
|
const altNode = nip71Metadata.alt
|
|
? (() => {
|
|
const p = document.createElement("p");
|
|
p.className = "whitespace-pre-wrap text-primary";
|
|
p.textContent = nip71Metadata.alt;
|
|
return p;
|
|
})()
|
|
: createPlaceholder("Not provided");
|
|
eventDl.appendChild(createDtDd("Alt text", altNode, 2));
|
|
|
|
eventSection.appendChild(eventDl);
|
|
return eventSection;
|
|
}
|
|
|
|
export function createNotePointers(version, dTagValue, utils) {
|
|
const { createPlaceholder, truncateMiddle } = utils;
|
|
|
|
const ptrSection = createSection("Note pointers");
|
|
const ptrDl = createDl();
|
|
|
|
ptrDl.appendChild(createDtDd("Mode", document.createTextNode(version.mode || "live")));
|
|
|
|
const dTagNode = dTagValue
|
|
? (() => {
|
|
const c = document.createElement("code");
|
|
c.className = "rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
|
|
c.textContent = dTagValue;
|
|
return c;
|
|
})()
|
|
: createPlaceholder("Not provided");
|
|
ptrDl.appendChild(createDtDd("d tag", dTagNode));
|
|
|
|
const rootId = typeof version.videoRootId === "string" ? version.videoRootId : "";
|
|
const rootDisplay = rootId
|
|
? truncateMiddle
|
|
? truncateMiddle(rootId, 64)
|
|
: rootId
|
|
: "";
|
|
|
|
const rootIdNode = rootDisplay
|
|
? (() => {
|
|
const c = document.createElement("code");
|
|
c.className = "break-all rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
|
|
c.title = rootId;
|
|
c.textContent = rootDisplay;
|
|
return c;
|
|
})()
|
|
: createPlaceholder("Not provided");
|
|
ptrDl.appendChild(createDtDd("videoRootId", rootIdNode));
|
|
|
|
const eventDisplay = version.id
|
|
? truncateMiddle
|
|
? truncateMiddle(version.id, 64)
|
|
: version.id
|
|
: "";
|
|
|
|
const eventIdNode = eventDisplay
|
|
? (() => {
|
|
const c = document.createElement("code");
|
|
c.className = "break-all rounded bg-overlay-panel-soft px-1.5 py-0.5 text-primary";
|
|
c.title = version.id || "";
|
|
c.textContent = eventDisplay;
|
|
return c;
|
|
})()
|
|
: (() => {
|
|
const s = document.createElement("span");
|
|
s.className = "text-subtle";
|
|
s.textContent = "Unknown";
|
|
return s;
|
|
})();
|
|
ptrDl.appendChild(createDtDd("Event ID", eventIdNode));
|
|
|
|
ptrSection.appendChild(ptrDl);
|
|
return ptrSection;
|
|
}
|