mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 12:52:26 +00:00
update
This commit is contained in:
1 parent
000127f88c
commit
071d78dd6c
194 files changed
+28793
-3944
No files matched your search
@@ -891,11 +891,29 @@ export class EditModal {
|
||||
|
||||
const finalTitle = titleWasEdited ? newTitle : original.title || "";
|
||||
const finalUrl = urlWasEdited ? newUrl : original.url || "";
|
||||
const shouldUseOriginalWs = wsInput ? wsInput.readOnly !== false : true;
|
||||
const shouldUseOriginalXs = xsInput ? xsInput.readOnly !== false : true;
|
||||
let finalWs = shouldUseOriginalWs ? original.ws || "" : newWs;
|
||||
let finalXs = shouldUseOriginalXs ? original.xs || "" : newXs;
|
||||
let finalMagnet = magnetWasEdited ? newMagnet : original.magnet || "";
|
||||
const originalMagnetValue =
|
||||
typeof original.magnet === "string" ? original.magnet.trim() : "";
|
||||
const originalWsValue =
|
||||
typeof original.ws === "string" ? original.ws.trim() : "";
|
||||
const originalXsValue =
|
||||
typeof original.xs === "string" ? original.xs.trim() : "";
|
||||
|
||||
const wsWasManuallyEdited = wsInput ? wsInput.readOnly === false : false;
|
||||
const xsWasManuallyEdited = xsInput ? xsInput.readOnly === false : false;
|
||||
|
||||
let finalMagnet = magnetWasEdited ? newMagnet : originalMagnetValue;
|
||||
let finalWs = wsWasManuallyEdited ? newWs : originalWsValue;
|
||||
let finalXs = xsWasManuallyEdited ? newXs : originalXsValue;
|
||||
|
||||
if (magnetWasEdited) {
|
||||
const magnetHintCandidates = extractMagnetHints(finalMagnet);
|
||||
if (!wsWasManuallyEdited) {
|
||||
finalWs = magnetHintCandidates.ws || "";
|
||||
}
|
||||
if (!xsWasManuallyEdited) {
|
||||
finalXs = magnetHintCandidates.xs || "";
|
||||
}
|
||||
}
|
||||
const finalThumbnail = isEditing(thumbnailInput)
|
||||
? newThumbnail
|
||||
: original.thumbnail || "";
|
||||
@@ -990,6 +1008,10 @@ export class EditModal {
|
||||
finalXs = "";
|
||||
}
|
||||
|
||||
const magnetChanged = magnetWasEdited && finalMagnet !== originalMagnetValue;
|
||||
const wsEditedFlag = wsWasManuallyEdited || magnetChanged;
|
||||
const xsEditedFlag = xsWasManuallyEdited || magnetChanged;
|
||||
|
||||
const updatedData = {
|
||||
version: original.version || 2,
|
||||
title: finalTitle,
|
||||
@@ -1000,8 +1022,8 @@ export class EditModal {
|
||||
mode: this.resolveModeForSubmit(),
|
||||
ws: finalWs,
|
||||
xs: finalXs,
|
||||
wsEdited: !shouldUseOriginalWs,
|
||||
xsEdited: !shouldUseOriginalXs,
|
||||
wsEdited: wsEditedFlag,
|
||||
xsEdited: xsEditedFlag,
|
||||
urlEdited: urlWasEdited,
|
||||
magnetEdited: magnetWasEdited,
|
||||
enableComments: finalEnableComments,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
import { formatShortNpub } from "../../utils/formatters.js";
|
||||
import { sanitizeProfileMediaUrl } from "../../utils/profileMedia.js";
|
||||
|
||||
const DEFAULT_PROFILE_AVATAR = "assets/svg/default-profile.svg";
|
||||
|
||||
@@ -213,15 +214,26 @@ export class SimilarContentCard {
|
||||
continue;
|
||||
}
|
||||
const trimmed = entry.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizeProfileMediaUrl(trimmed);
|
||||
if (sanitized) {
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
const fallback =
|
||||
typeof baseline.picture === "string" && baseline.picture.trim()
|
||||
? sanitizeProfileMediaUrl(baseline.picture) || baseline.picture.trim()
|
||||
: "";
|
||||
|
||||
return fallback;
|
||||
})();
|
||||
|
||||
return { name, npub, shortNpub, pubkey, picture };
|
||||
const resolvedPicture = picture || DEFAULT_PROFILE_AVATAR;
|
||||
|
||||
return { name, npub, shortNpub, pubkey, picture: resolvedPicture };
|
||||
}
|
||||
|
||||
applyIdentityToElements() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
import { updateVideoCardSourceVisibility } from "../../utils/cardSourceVisibility.js";
|
||||
import { sanitizeProfileMediaUrl } from "../../utils/profileMedia.js";
|
||||
import { userLogger } from "../../utils/logger.js";
|
||||
import {
|
||||
applyModerationContextDatasets,
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
import { buildModerationBadgeText } from "../moderationCopy.js";
|
||||
|
||||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
||||
const DEFAULT_PROFILE_AVATAR = "assets/svg/default-profile.svg";
|
||||
const HEX64_REGEX = /^[0-9a-f]{64}$/i;
|
||||
|
||||
export class VideoCard {
|
||||
constructor({
|
||||
@@ -27,6 +30,7 @@ export class VideoCard {
|
||||
assets = {},
|
||||
state = {},
|
||||
variant = "default",
|
||||
identity = null,
|
||||
ensureGlobalMoreMenuHandlers,
|
||||
onRequestCloseAllMenus,
|
||||
nsfwContext = null,
|
||||
@@ -91,6 +95,7 @@ export class VideoCard {
|
||||
: null;
|
||||
|
||||
this.designSystem = normalizeDesignSystemContext(designSystem);
|
||||
this.identity = this.normalizeIdentity(identity);
|
||||
|
||||
const normalizedVariant =
|
||||
typeof variant === "string" && variant.trim().toLowerCase() === "compact"
|
||||
@@ -461,6 +466,8 @@ export class VideoCard {
|
||||
header.appendChild(controls);
|
||||
}
|
||||
|
||||
this.applyIdentityToAuthorElements();
|
||||
|
||||
content.appendChild(title);
|
||||
content.appendChild(header);
|
||||
|
||||
@@ -501,6 +508,135 @@ export class VideoCard {
|
||||
this.refreshModerationUi();
|
||||
}
|
||||
|
||||
normalizeIdentity(nextIdentity = {}, fallback = null) {
|
||||
const baseline = fallback && typeof fallback === "object" ? fallback : {};
|
||||
const candidate =
|
||||
nextIdentity && typeof nextIdentity === "object" ? nextIdentity : {};
|
||||
const video = this.video && typeof this.video === "object" ? this.video : {};
|
||||
|
||||
const pickFirstString = (
|
||||
entries = [],
|
||||
{ allowHex = true, sanitizer = (value) => value } = {},
|
||||
) => {
|
||||
for (const entry of entries) {
|
||||
if (typeof entry !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
if (!allowHex && HEX64_REGEX.test(trimmed)) {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizer(trimmed);
|
||||
if (!sanitized) {
|
||||
continue;
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const pubkey = pickFirstString([
|
||||
candidate.pubkey,
|
||||
baseline.pubkey,
|
||||
video.pubkey,
|
||||
video.author?.pubkey,
|
||||
video.creator?.pubkey,
|
||||
video.profile?.pubkey,
|
||||
]);
|
||||
|
||||
const npub = pickFirstString([
|
||||
candidate.npub,
|
||||
baseline.npub,
|
||||
video.npub,
|
||||
video.authorNpub,
|
||||
video.profile?.npub,
|
||||
]);
|
||||
|
||||
const shortNpub = pickFirstString([
|
||||
candidate.shortNpub,
|
||||
baseline.shortNpub,
|
||||
video.shortNpub,
|
||||
video.creatorNpub,
|
||||
]);
|
||||
|
||||
const name = pickFirstString(
|
||||
[
|
||||
candidate.name,
|
||||
candidate.display_name,
|
||||
candidate.displayName,
|
||||
candidate.username,
|
||||
baseline.name,
|
||||
baseline.display_name,
|
||||
baseline.displayName,
|
||||
baseline.username,
|
||||
video.creatorName,
|
||||
video.authorName,
|
||||
video.creator?.name,
|
||||
video.author?.name,
|
||||
video.profile?.display_name,
|
||||
video.profile?.name,
|
||||
video.profile?.username,
|
||||
],
|
||||
{ allowHex: false },
|
||||
);
|
||||
|
||||
const picture = pickFirstString(
|
||||
[
|
||||
candidate.picture,
|
||||
candidate.image,
|
||||
candidate.photo,
|
||||
baseline.picture,
|
||||
baseline.image,
|
||||
baseline.photo,
|
||||
video.creatorPicture,
|
||||
video.authorPicture,
|
||||
video.creator?.picture,
|
||||
video.author?.picture,
|
||||
video.profile?.picture,
|
||||
video.profile?.image,
|
||||
video.profile?.photo,
|
||||
],
|
||||
{ sanitizer: (value) => sanitizeProfileMediaUrl(value) },
|
||||
);
|
||||
|
||||
const resolvedName = name || shortNpub || npub || pubkey || "";
|
||||
const resolvedPicture = picture || DEFAULT_PROFILE_AVATAR;
|
||||
|
||||
return {
|
||||
name: resolvedName,
|
||||
npub,
|
||||
shortNpub,
|
||||
pubkey,
|
||||
picture: resolvedPicture,
|
||||
};
|
||||
}
|
||||
|
||||
updateIdentity(nextIdentity = {}) {
|
||||
this.identity = this.normalizeIdentity(nextIdentity, this.identity);
|
||||
this.applyIdentityToAuthorElements();
|
||||
}
|
||||
|
||||
applyIdentityToAuthorElements() {
|
||||
const nameLabel = this.identity?.name || "Unknown";
|
||||
if (this.authorNameEl) {
|
||||
this.authorNameEl.textContent = nameLabel || "Unknown";
|
||||
}
|
||||
|
||||
if (this.authorPicEl) {
|
||||
const picture = this.identity?.picture || DEFAULT_PROFILE_AVATAR;
|
||||
if (this.authorPicEl.getAttribute("src") !== picture) {
|
||||
this.authorPicEl.src = picture;
|
||||
}
|
||||
|
||||
const altFallback =
|
||||
nameLabel || this.identity?.shortNpub || this.identity?.npub || "Channel";
|
||||
this.authorPicEl.alt = `${altFallback}'s avatar`;
|
||||
}
|
||||
}
|
||||
|
||||
setCardBackdropImage(src) {
|
||||
if (!this.root || !this.root.style) {
|
||||
return;
|
||||
|
||||
+304
-29
@@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
renderTagPillStrip,
|
||||
applyTagPreferenceState,
|
||||
trimTagPillStripToFit,
|
||||
} from "./tagPillList.js";
|
||||
import { SimilarContentCard } from "./SimilarContentCard.js";
|
||||
import {
|
||||
@@ -129,6 +130,10 @@ export class VideoModal {
|
||||
this.videoViewCountEl = null;
|
||||
this.videoTagsRoot = null;
|
||||
this.videoTagsData = Object.freeze([]);
|
||||
this.videoTagsSourceData = this.videoTagsData;
|
||||
this.videoTagsResizeObserver = null;
|
||||
this.videoTagsLastObservedWidth = 0;
|
||||
this.handleVideoTagsResize = this.handleVideoTagsResize.bind(this);
|
||||
this.creatorAvatar = null;
|
||||
this.creatorName = null;
|
||||
this.creatorNpub = null;
|
||||
@@ -390,6 +395,7 @@ export class VideoModal {
|
||||
} else {
|
||||
this.loaded = false;
|
||||
this.cleanupVideoTags();
|
||||
this.teardownVideoTagsResizeObserver();
|
||||
this.videoTagsRoot = null;
|
||||
this.playerModal = null;
|
||||
}
|
||||
@@ -509,6 +515,7 @@ export class VideoModal {
|
||||
if (this.videoTagsRoot && this.videoTagsRoot !== nextVideoTagsRoot) {
|
||||
this.cleanupVideoTags(this.videoTagsRoot);
|
||||
}
|
||||
this.teardownVideoTagsResizeObserver();
|
||||
this.videoTagsRoot = nextVideoTagsRoot;
|
||||
if (this.videoTagsRoot) {
|
||||
this.cleanupVideoTags(this.videoTagsRoot);
|
||||
@@ -540,10 +547,11 @@ export class VideoModal {
|
||||
statsContainer: statsContainerCandidate,
|
||||
});
|
||||
|
||||
const previousTags = Array.isArray(this.videoTagsData)
|
||||
? [...this.videoTagsData]
|
||||
const previousTags = Array.isArray(this.videoTagsSourceData)
|
||||
? [...this.videoTagsSourceData]
|
||||
: [];
|
||||
this.videoTagsData = null;
|
||||
this.videoTagsSourceData = Object.freeze([]);
|
||||
|
||||
this.moderationController.initialize({ playerModal });
|
||||
|
||||
@@ -1807,7 +1815,12 @@ export class VideoModal {
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || trimmed === "null" || trimmed === "undefined") {
|
||||
if (
|
||||
!trimmed ||
|
||||
trimmed === "null" ||
|
||||
trimmed === "undefined" ||
|
||||
trimmed === "__root__"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
@@ -1930,6 +1943,7 @@ export class VideoModal {
|
||||
|
||||
destroy() {
|
||||
this.close();
|
||||
this.teardownVideoTagsResizeObserver();
|
||||
this.commentsController.destroy();
|
||||
this.reactionsController.destroy();
|
||||
this.similarContentController.destroy();
|
||||
@@ -2585,6 +2599,19 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure title element reference is available
|
||||
if (!this.videoTitle) {
|
||||
this.videoTitle = this.playerModal.querySelector("#videoTitle") || null;
|
||||
}
|
||||
// Set title immediately when opening if video has a title
|
||||
if (this.videoTitle && video && typeof video.title === "string" && video.title.trim()) {
|
||||
this.videoTitle.textContent = video.title.trim();
|
||||
this.videoTitle.hidden = false;
|
||||
} else if (this.videoTitle) {
|
||||
this.videoTitle.textContent = "Untitled";
|
||||
this.videoTitle.hidden = false;
|
||||
}
|
||||
|
||||
this.resetReactions();
|
||||
|
||||
this.playerModal.classList.remove("hidden");
|
||||
@@ -2795,21 +2822,29 @@ export class VideoModal {
|
||||
return true;
|
||||
}
|
||||
|
||||
renderVideoTags(tags = []) {
|
||||
renderVideoTags(tags = [], options = {}) {
|
||||
const { force = false } = options || {};
|
||||
const normalized = this.normalizeVideoTags(tags);
|
||||
const hasTags = normalized.length > 0;
|
||||
const root = this.videoTagsRoot;
|
||||
|
||||
if (root) {
|
||||
if (this.areVideoTagsEqual(normalized, this.videoTagsData)) {
|
||||
this.toggleVideoTagsVisibility(hasTags);
|
||||
this.refreshTagPreferenceStates();
|
||||
if (!force && this.areVideoTagsEqual(normalized, this.videoTagsData)) {
|
||||
const hasVisibleButtons =
|
||||
root.querySelector("button[data-tag]") !== null;
|
||||
this.toggleVideoTagsVisibility(hasVisibleButtons);
|
||||
if (hasVisibleButtons) {
|
||||
this.ensureVideoTagsResizeObserver();
|
||||
this.refreshTagPreferenceStates();
|
||||
} else {
|
||||
this.teardownVideoTagsResizeObserver();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.cleanupVideoTags(root);
|
||||
|
||||
if (hasTags) {
|
||||
let hasVisibleButtons = false;
|
||||
if (normalized.length > 0) {
|
||||
const { root: strip } = renderTagPillStrip({
|
||||
document: root.ownerDocument || this.document,
|
||||
tags: normalized,
|
||||
@@ -2819,13 +2854,119 @@ export class VideoModal {
|
||||
|
||||
if (strip) {
|
||||
root.appendChild(strip);
|
||||
trimTagPillStripToFit({ strip, container: root });
|
||||
|
||||
if (strip.querySelector("button[data-tag]")) {
|
||||
hasVisibleButtons = true;
|
||||
} else if (strip.parentNode === root) {
|
||||
root.removeChild(strip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.toggleVideoTagsVisibility(hasTags);
|
||||
this.toggleVideoTagsVisibility(hasVisibleButtons);
|
||||
if (hasVisibleButtons) {
|
||||
this.ensureVideoTagsResizeObserver();
|
||||
this.refreshTagPreferenceStates();
|
||||
} else {
|
||||
this.teardownVideoTagsResizeObserver();
|
||||
}
|
||||
} else {
|
||||
this.teardownVideoTagsResizeObserver();
|
||||
}
|
||||
|
||||
this.videoTagsData = Object.freeze([...normalized]);
|
||||
const stored = Object.freeze([...normalized]);
|
||||
this.videoTagsData = stored;
|
||||
this.videoTagsSourceData = stored;
|
||||
}
|
||||
|
||||
reflowVideoTags() {
|
||||
if (!Array.isArray(this.videoTagsSourceData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.videoTagsSourceData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderVideoTags([...this.videoTagsSourceData], { force: true });
|
||||
}
|
||||
|
||||
ensureVideoTagsResizeObserver() {
|
||||
const root = this.videoTagsRoot;
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ResizeObserverCtor =
|
||||
(this.window && this.window.ResizeObserver) ||
|
||||
globalThis.ResizeObserver ||
|
||||
null;
|
||||
if (typeof ResizeObserverCtor !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.videoTagsResizeObserver) {
|
||||
this.videoTagsResizeObserver = new ResizeObserverCtor(
|
||||
this.handleVideoTagsResize,
|
||||
);
|
||||
} else if (typeof this.videoTagsResizeObserver.disconnect === "function") {
|
||||
this.videoTagsResizeObserver.disconnect();
|
||||
}
|
||||
|
||||
try {
|
||||
this.videoTagsResizeObserver.observe(root);
|
||||
} catch (error) {
|
||||
// Ignore observer errors (e.g., observing a detached node).
|
||||
}
|
||||
}
|
||||
|
||||
teardownVideoTagsResizeObserver() {
|
||||
if (this.videoTagsResizeObserver) {
|
||||
try {
|
||||
this.videoTagsResizeObserver.disconnect();
|
||||
} catch (error) {
|
||||
// Ignore teardown errors.
|
||||
}
|
||||
}
|
||||
this.videoTagsResizeObserver = null;
|
||||
this.videoTagsLastObservedWidth = 0;
|
||||
}
|
||||
|
||||
handleVideoTagsResize(entries = []) {
|
||||
if (!this.videoTagsRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry =
|
||||
Array.isArray(entries) && entries.length > 0
|
||||
? entries.find((item) => item?.target === this.videoTagsRoot) ||
|
||||
entries[0]
|
||||
: null;
|
||||
|
||||
const measuredWidthRaw = entry
|
||||
? entry.contentRect && typeof entry.contentRect.width === "number"
|
||||
? entry.contentRect.width
|
||||
: typeof entry.contentBoxSize?.[0]?.inlineSize === "number"
|
||||
? entry.contentBoxSize[0].inlineSize
|
||||
: this.videoTagsRoot.clientWidth || 0
|
||||
: this.videoTagsRoot.clientWidth || 0;
|
||||
|
||||
const measuredWidth = Number.isFinite(measuredWidthRaw)
|
||||
? Math.max(0, Math.round(measuredWidthRaw))
|
||||
: 0;
|
||||
|
||||
if (measuredWidth <= 0) {
|
||||
this.videoTagsLastObservedWidth = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.videoTagsLastObservedWidth === measuredWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.videoTagsLastObservedWidth = measuredWidth;
|
||||
this.reflowVideoTags();
|
||||
}
|
||||
|
||||
handleVideoTagActivate(tag, { event, button } = {}) {
|
||||
@@ -3506,7 +3647,43 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
|
||||
receipts.forEach((receipt) => {
|
||||
const normalized = receipts.filter(
|
||||
(receipt) => receipt && typeof receipt === "object"
|
||||
);
|
||||
|
||||
const validatedReceipts = normalized.filter(
|
||||
(receipt) => receipt?.validation?.status === "passed" && receipt.validation.event
|
||||
);
|
||||
|
||||
const paymentFailures = normalized.filter((receipt) => {
|
||||
if (!receipt) {
|
||||
return false;
|
||||
}
|
||||
const status =
|
||||
typeof receipt.status === "string" ? receipt.status.toLowerCase() : "";
|
||||
return status && status !== "success";
|
||||
});
|
||||
|
||||
const unvalidatedReceipts = normalized.filter((receipt) => {
|
||||
if (!receipt) {
|
||||
return false;
|
||||
}
|
||||
const status =
|
||||
typeof receipt.status === "string" ? receipt.status.toLowerCase() : "success";
|
||||
if (status !== "success" && status !== "") {
|
||||
return false;
|
||||
}
|
||||
const validationStatus =
|
||||
typeof receipt.validation?.status === "string"
|
||||
? receipt.validation.status.toLowerCase()
|
||||
: "";
|
||||
if (!validationStatus || validationStatus === "skipped") {
|
||||
return false;
|
||||
}
|
||||
return validationStatus !== "passed";
|
||||
});
|
||||
|
||||
const renderReceiptItem = ({ receipt, tone }) => {
|
||||
const li = this.document.createElement("li");
|
||||
li.className = "rounded border border-border p-3 bg-overlay-panel-soft";
|
||||
|
||||
@@ -3528,11 +3705,16 @@ export class VideoModal {
|
||||
)} sats`;
|
||||
|
||||
const status = this.document.createElement("span");
|
||||
const isSuccess = receipt.status
|
||||
? receipt.status === "success"
|
||||
: !receipt.error;
|
||||
status.textContent = isSuccess ? "Success" : "Failed";
|
||||
status.className = isSuccess ? "text-info" : "text-critical";
|
||||
if (tone === "validated") {
|
||||
status.textContent = "Validated";
|
||||
status.className = "text-info";
|
||||
} else if (tone === "failed") {
|
||||
status.textContent = "Failed";
|
||||
status.className = "text-critical";
|
||||
} else {
|
||||
status.textContent = "Pending";
|
||||
status.className = "text-muted";
|
||||
}
|
||||
|
||||
header.appendChild(shareLabel);
|
||||
header.appendChild(status);
|
||||
@@ -3547,7 +3729,14 @@ export class VideoModal {
|
||||
|
||||
const detail = this.document.createElement("p");
|
||||
detail.className = "mt-2 text-xs text-muted";
|
||||
if (isSuccess) {
|
||||
if (tone === "failed") {
|
||||
const errorMessage =
|
||||
(receipt.error && receipt.error.message) ||
|
||||
(typeof receipt.error === "string"
|
||||
? receipt.error
|
||||
: "Payment failed.");
|
||||
detail.textContent = errorMessage;
|
||||
} else {
|
||||
let detailMessage = "Invoice settled.";
|
||||
const preimage = receipt.payment?.result?.preimage;
|
||||
if (typeof preimage === "string" && preimage) {
|
||||
@@ -3556,18 +3745,59 @@ export class VideoModal {
|
||||
}`;
|
||||
}
|
||||
detail.textContent = detailMessage;
|
||||
} else {
|
||||
const errorMessage =
|
||||
(receipt.error && receipt.error.message) ||
|
||||
(typeof receipt.error === "string"
|
||||
? receipt.error
|
||||
: "Payment failed.");
|
||||
detail.textContent = errorMessage;
|
||||
}
|
||||
li.appendChild(detail);
|
||||
|
||||
this.modalZapReceipts.appendChild(li);
|
||||
};
|
||||
|
||||
validatedReceipts.forEach((receipt) => {
|
||||
renderReceiptItem({ receipt, tone: "validated" });
|
||||
});
|
||||
|
||||
paymentFailures.forEach((receipt) => {
|
||||
renderReceiptItem({ receipt, tone: "failed" });
|
||||
});
|
||||
|
||||
if (!validatedReceipts.length && !paymentFailures.length && !unvalidatedReceipts.length) {
|
||||
const empty = this.document.createElement("li");
|
||||
empty.className = "text-sm text-text";
|
||||
empty.textContent = partial
|
||||
? "No zap receipts available."
|
||||
: "No zap receipts published yet.";
|
||||
this.modalZapReceipts.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unvalidatedReceipts.length) {
|
||||
const warning = this.document.createElement("li");
|
||||
warning.className =
|
||||
"rounded border border-border p-3 bg-overlay-panel-soft text-xs text-warning-strong";
|
||||
const summaries = unvalidatedReceipts.map((receipt) => {
|
||||
const shareType = receipt.recipientType || receipt.type || "creator";
|
||||
const label =
|
||||
shareType === "platform"
|
||||
? "Platform"
|
||||
: shareType === "creator"
|
||||
? "Creator"
|
||||
: "Lightning";
|
||||
const address =
|
||||
typeof receipt.address === "string" && receipt.address
|
||||
? ` (${receipt.address})`
|
||||
: "";
|
||||
const reason =
|
||||
typeof receipt.validation?.reason === "string" && receipt.validation.reason
|
||||
? ` — ${receipt.validation.reason}`
|
||||
: " — Awaiting compliant receipt.";
|
||||
return `${label}${address}${reason}`;
|
||||
});
|
||||
|
||||
const intro = validatedReceipts.length
|
||||
? "Awaiting validated zap receipt for remaining share(s)."
|
||||
: "No validated zap receipts yet.";
|
||||
warning.textContent = `${intro} ${summaries.join(" ")}`.trim();
|
||||
this.modalZapReceipts.appendChild(warning);
|
||||
}
|
||||
}
|
||||
|
||||
setZapPending(pending) {
|
||||
@@ -3715,8 +3945,35 @@ export class VideoModal {
|
||||
creator,
|
||||
tags,
|
||||
} = {}) {
|
||||
if (this.videoTitle && title !== undefined) {
|
||||
this.videoTitle.textContent = title || "Untitled";
|
||||
if (title !== undefined) {
|
||||
// Try to find the title element if not already cached
|
||||
if (!this.videoTitle) {
|
||||
if (this.playerModal) {
|
||||
this.videoTitle = this.playerModal.querySelector("#videoTitle") || null;
|
||||
}
|
||||
// Fallback: try document query if playerModal query failed
|
||||
if (!this.videoTitle) {
|
||||
this.videoTitle = this.document.getElementById("videoTitle") || null;
|
||||
}
|
||||
}
|
||||
if (this.videoTitle) {
|
||||
const titleText =
|
||||
typeof title === "string" && title.trim()
|
||||
? title.trim()
|
||||
: "Untitled";
|
||||
this.videoTitle.textContent = titleText;
|
||||
// Ensure element is visible
|
||||
this.videoTitle.hidden = false;
|
||||
if (this.videoTitle.style) {
|
||||
this.videoTitle.style.display = "";
|
||||
}
|
||||
} else {
|
||||
// Log for debugging if element cannot be found
|
||||
this.logger?.log?.(
|
||||
"[VideoModal] Could not find #videoTitle element to set title:",
|
||||
title
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.videoDescription && description !== undefined) {
|
||||
this.renderVideoDescription(description);
|
||||
@@ -5063,12 +5320,30 @@ export class VideoModal {
|
||||
continue;
|
||||
}
|
||||
const trimmed = candidate.trim();
|
||||
if (trimmed) {
|
||||
picture = trimmed;
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const sanitized = sanitizeProfileMediaUrl(trimmed);
|
||||
if (sanitized) {
|
||||
picture = sanitized;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!picture && pubkey) {
|
||||
const robohash = `https://robohash.org/${pubkey}`;
|
||||
picture = sanitizeProfileMediaUrl(robohash) || robohash;
|
||||
}
|
||||
|
||||
if (!picture) {
|
||||
const fallbackAvatar =
|
||||
typeof this.DEFAULT_PROFILE_AVATAR === "string" &&
|
||||
this.DEFAULT_PROFILE_AVATAR.trim()
|
||||
? this.DEFAULT_PROFILE_AVATAR.trim()
|
||||
: "assets/svg/default-profile.svg";
|
||||
picture = sanitizeProfileMediaUrl(fallbackAvatar) || fallbackAvatar;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
npub,
|
||||
|
||||
@@ -199,6 +199,8 @@ export class Nip71FormManager {
|
||||
this.setFieldValue(entry, "dim", value.dim);
|
||||
this.setFieldValue(entry, "url", value.url);
|
||||
this.setFieldValue(entry, "x", value.x);
|
||||
this.setFieldValue(entry, "duration", value.duration);
|
||||
this.setFieldValue(entry, "bitrate", value.bitrate);
|
||||
this.hydrateNested(entry, "image", value.image);
|
||||
this.hydrateNested(entry, "fallback", value.fallback);
|
||||
this.hydrateNested(entry, "service", value.service);
|
||||
@@ -302,6 +304,26 @@ export class Nip71FormManager {
|
||||
service: this.collectNestedValues(entry, "service"),
|
||||
};
|
||||
|
||||
const durationValue = this.getFieldValue(entry, "duration");
|
||||
if (durationValue !== "") {
|
||||
const parsed = this.normalizeNumber(durationValue);
|
||||
if (parsed != null && parsed >= 0) {
|
||||
variant.duration = parsed;
|
||||
} else {
|
||||
variant.duration = durationValue;
|
||||
}
|
||||
}
|
||||
|
||||
const bitrateValue = this.getFieldValue(entry, "bitrate");
|
||||
if (bitrateValue !== "") {
|
||||
const parsed = this.normalizeNumber(bitrateValue);
|
||||
if (parsed != null && parsed >= 0) {
|
||||
variant.bitrate = parsed;
|
||||
} else {
|
||||
variant.bitrate = bitrateValue;
|
||||
}
|
||||
}
|
||||
|
||||
const hasContent =
|
||||
variant.m ||
|
||||
variant.dim ||
|
||||
@@ -309,7 +331,9 @@ export class Nip71FormManager {
|
||||
variant.x ||
|
||||
variant.image.length > 0 ||
|
||||
variant.fallback.length > 0 ||
|
||||
variant.service.length > 0;
|
||||
variant.service.length > 0 ||
|
||||
variant.duration !== undefined ||
|
||||
variant.bitrate !== undefined;
|
||||
|
||||
return hasContent ? variant : null;
|
||||
});
|
||||
|
||||
@@ -115,17 +115,99 @@ function createTagButton({ doc, tag, onTagActivate, getTagState }) {
|
||||
return button;
|
||||
}
|
||||
|
||||
function cleanupButtonHandler(button) {
|
||||
if (!button || typeof button !== "object" || button.nodeType !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = button.__tagPillClickHandler;
|
||||
if (handler) {
|
||||
button.removeEventListener("click", handler);
|
||||
delete button.__tagPillClickHandler;
|
||||
}
|
||||
}
|
||||
|
||||
function removeTagButton(button) {
|
||||
if (!button || typeof button !== "object" || button.nodeType !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
cleanupButtonHandler(button);
|
||||
|
||||
if (typeof button.remove === "function") {
|
||||
button.remove();
|
||||
} else if (button.parentNode) {
|
||||
button.parentNode.removeChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupExistingButtons(root) {
|
||||
for (const button of root.querySelectorAll("button")) {
|
||||
const handler = button.__tagPillClickHandler;
|
||||
if (handler) {
|
||||
button.removeEventListener("click", handler);
|
||||
delete button.__tagPillClickHandler;
|
||||
}
|
||||
cleanupButtonHandler(button);
|
||||
}
|
||||
root.textContent = "";
|
||||
}
|
||||
|
||||
function getAvailableContainerWidth(container) {
|
||||
if (!container || container.nodeType !== 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const clientWidth = Number(container.clientWidth || 0);
|
||||
if (Number.isFinite(clientWidth) && clientWidth > 0) {
|
||||
return clientWidth;
|
||||
}
|
||||
|
||||
if (typeof container.getBoundingClientRect === "function") {
|
||||
try {
|
||||
const rect = container.getBoundingClientRect();
|
||||
if (rect && typeof rect.width === "number" && rect.width > 0) {
|
||||
return rect.width;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore measurement errors and fall through to return 0.
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function trimTagPillStripToFit({ strip, container } = {}) {
|
||||
if (!strip || typeof strip !== "object" || strip.nodeType !== 1) {
|
||||
return { removedButtons: [] };
|
||||
}
|
||||
|
||||
const host =
|
||||
container && typeof container === "object" && container.nodeType === 1
|
||||
? container
|
||||
: strip.parentElement;
|
||||
|
||||
const availableWidth = Math.max(0, getAvailableContainerWidth(host));
|
||||
const removedButtons = [];
|
||||
|
||||
const buttons = Array.from(strip.querySelectorAll("button"));
|
||||
if (!buttons.length) {
|
||||
return { removedButtons };
|
||||
}
|
||||
|
||||
if (availableWidth <= 0) {
|
||||
return { removedButtons };
|
||||
}
|
||||
|
||||
let currentButtons = buttons.length;
|
||||
while (currentButtons > 0 && strip.scrollWidth > availableWidth) {
|
||||
const button = buttons[currentButtons - 1];
|
||||
if (!button) {
|
||||
break;
|
||||
}
|
||||
removeTagButton(button);
|
||||
removedButtons.push(button);
|
||||
currentButtons -= 1;
|
||||
}
|
||||
|
||||
return { removedButtons };
|
||||
}
|
||||
|
||||
export function renderTagPillStrip({
|
||||
document: doc,
|
||||
tags = [],
|
||||
|
||||
@@ -288,7 +288,11 @@ export function createVideoMoreMenuPanel({
|
||||
appendMenuAction(doc, list, {
|
||||
text: "Report",
|
||||
action: "report",
|
||||
dataset: { eventId: metadata.id },
|
||||
dataset: {
|
||||
eventId: metadata.id,
|
||||
author: metadata.pubkey,
|
||||
pointerRelay: pointer && pointer.relay ? pointer.relay : "",
|
||||
},
|
||||
});
|
||||
|
||||
return panel;
|
||||
|
||||
Reference in new issue
Block a user