Files
bitvid/js/ui/components/video-modal/commentsController.js
T

292 lines
9.1 KiB
JavaScript

import logger from "../../../utils/logger.js";
export class CommentsController {
constructor({ modal } = {}) {
this.modal = modal;
this.root = null;
this.playerModal = null;
}
initialize({ playerModal } = {}) {
if (!this.modal) {
return;
}
this.playerModal = playerModal || null;
const root = playerModal?.querySelector("[data-comments-root]") || null;
const commentsDisabledPlaceholder =
playerModal?.querySelector("[data-comments-disabled-placeholder]") ||
root?.querySelector("[data-comments-disabled]") ||
this.ensureCommentsDisabledPlaceholder({ root }) ||
null;
this.root = root;
this.modal.commentsRoot = root;
this.modal.commentsContainer = root;
this.modal.commentsDisabledPlaceholder = commentsDisabledPlaceholder;
if (commentsDisabledPlaceholder) {
const disabledText = commentsDisabledPlaceholder.textContent || "";
if (disabledText) {
this.modal.commentsDisabledPlaceholderDefaultText = disabledText;
}
}
if (!root) {
logger.user.warn(
"[VideoModal:comments] Missing [data-comments-root]; disabling comments UI"
);
this.disableComments({
reason: "missing-root",
message: this.modal.commentsDisabledPlaceholderDefaultText ||
"Comments are unavailable right now.",
});
return;
}
this.modal.commentsCountLabel =
root?.querySelector("[data-comments-count]") || null;
this.modal.commentsEmptyState =
root?.querySelector("[data-comments-empty]") || null;
this.modal.commentsList =
root?.querySelector("[data-comments-list]") || null;
this.modal.commentsLoadMoreButton =
root?.querySelector("[data-comments-load-more]") || null;
this.modal.commentsComposer =
root?.querySelector("[data-comments-composer]") || null;
this.modal.commentsInput =
this.modal.commentsComposer?.querySelector("[data-comments-input]") || null;
this.modal.commentsSubmitButton =
this.modal.commentsComposer?.querySelector("[data-comments-submit]") || null;
this.modal.commentsStatusMessage =
this.modal.commentsComposer?.querySelector("[data-comments-status]") ||
root?.querySelector("[data-comments-status]") ||
null;
this.modal.commentsCharCount =
this.modal.commentsComposer?.querySelector("[data-comments-char-count]") ||
null;
this.modal.commentComposerHint =
this.modal.commentsComposer?.querySelector("#commentComposerHelp") ||
this.modal.commentsComposer?.querySelector(".comment-composer__hint") ||
this.modal.commentComposerHint ||
null;
const missingSelectors = [
{ selector: "[data-comments-list]", element: this.modal.commentsList },
{
selector: "[data-comments-composer]",
element: this.modal.commentsComposer,
},
{
selector: "[data-comments-input]",
element: this.modal.commentsInput,
},
{
selector: "[data-comments-submit]",
element: this.modal.commentsSubmitButton,
},
]
.filter(({ element }) => !element)
.map(({ selector }) => selector);
if (missingSelectors.length > 0) {
logger.user.warn(
`[VideoModal:comments] Missing required selectors: ${missingSelectors.join(", ")}; disabling comments UI`
);
this.disableComments({
reason: "missing-selectors",
message: this.modal.commentsDisabledPlaceholderDefaultText ||
"Comments are unavailable right now.",
});
return;
}
if (this.playerModal) {
this.playerModal.removeAttribute("data-comments-disabled");
this.playerModal.classList?.remove("comments-disabled");
}
if (this.modal.commentComposerHint) {
const hintText = this.modal.commentComposerHint.textContent || "";
if (hintText) {
this.modal.commentComposerDefaultHint = hintText;
}
}
if (this.modal.commentsCharCount) {
const countText = this.modal.commentsCharCount.textContent || "";
if (countText) {
this.modal.commentComposerDefaultCountText = countText;
}
}
if (this.modal.commentsStatusMessage) {
const statusText = this.modal.commentsStatusMessage.textContent || "";
if (statusText) {
this.modal.commentStatusDefaultText = statusText;
}
}
if (this.modal.commentsInput) {
const parsedMax = Number(this.modal.commentsInput.maxLength);
if (Number.isFinite(parsedMax) && parsedMax > 0) {
this.modal.commentComposerMaxLength = parsedMax;
}
}
if (this.modal.commentsDisabledPlaceholder) {
this.modal.commentsDisabledPlaceholder.setAttribute("hidden", "");
}
if (this.modal.commentsComposer) {
this.modal.commentsComposer.removeAttribute("hidden");
}
this.modal.clearComments();
this.modal.resetCommentComposer();
this.modal.attachCommentEventHandlers();
}
disableComments({ message = "", reason = "" } = {}) {
const placeholder =
this.modal.commentsDisabledPlaceholder ||
this.ensureCommentsDisabledPlaceholder({ root: this.root }) ||
null;
if (!this.modal.commentsDisabledPlaceholder && placeholder) {
this.modal.commentsDisabledPlaceholder = placeholder;
if (placeholder.textContent) {
this.modal.commentsDisabledPlaceholderDefaultText = placeholder.textContent;
}
}
if (this.playerModal) {
this.playerModal.setAttribute("data-comments-disabled", "true");
this.playerModal.classList?.add("comments-disabled");
}
if (this.modal.commentsList) {
this.modal.commentsList.setAttribute("hidden", "");
this.modal.commentsList.classList?.add("hidden");
}
if (this.modal.commentsComposer) {
this.modal.commentsComposer.setAttribute("hidden", "");
this.modal.commentsComposer.classList?.add("hidden");
}
const nextMessage =
typeof message === "string" && message.trim()
? message.trim()
: this.modal.commentsDisabledPlaceholderDefaultText ||
"Comments are unavailable right now.";
this.modal.showCommentsDisabledMessage(nextMessage);
logger.dev?.info?.(
"[VideoModal:comments] Disabled comments handling",
{ reason: reason || "unknown" }
);
}
update(action = {}) {
if (!this.modal) {
return;
}
const { type } = action;
switch (type) {
case "set-visibility":
this.modal.setCommentsVisibility(action.visible);
break;
case "render":
this.modal.renderComments(action.snapshot);
break;
case "set-composer-state":
this.modal.setCommentComposerState(action.state);
break;
case "set-thread-context":
this.modal.setCommentThreadContext(action.context);
break;
case "clear":
this.modal.clearComments();
break;
default:
break;
}
}
destroy() {
if (!this.modal) {
return;
}
this.modal.detachCommentEventHandlers();
this.modal.removeCommentRetryButton();
this.modal.commentsRoot = null;
this.modal.commentsContainer = null;
this.modal.commentsCountLabel = null;
this.modal.commentsEmptyState = null;
this.modal.commentsList = null;
this.modal.commentsLoadMoreButton = null;
this.modal.commentsDisabledPlaceholder = null;
this.modal.commentsComposer = null;
this.modal.commentsInput = null;
this.modal.commentsSubmitButton = null;
this.modal.commentsStatusMessage = null;
this.modal.commentsCharCount = null;
this.modal.commentComposerHint = null;
if (this.playerModal) {
this.playerModal.removeAttribute("data-comments-disabled");
this.playerModal.classList?.remove("comments-disabled");
}
this.root = null;
}
getDocument() {
if (this.modal?.document) {
return this.modal.document;
}
if (this.playerModal?.ownerDocument) {
return this.playerModal.ownerDocument;
}
if (typeof document !== "undefined") {
return document;
}
return null;
}
ensureCommentsDisabledPlaceholder({ root } = {}) {
const existing =
this.modal?.commentsDisabledPlaceholder ||
root?.querySelector?.("[data-comments-disabled-placeholder]") ||
this.playerModal?.querySelector?.("[data-comments-disabled-placeholder]");
if (existing) {
return existing;
}
const doc = this.getDocument();
const container = root?.parentElement || this.playerModal || null;
if (!doc || !container) {
return null;
}
const placeholder = doc.createElement("div");
placeholder.setAttribute("data-comments-disabled-placeholder", "");
placeholder.setAttribute("role", "status");
placeholder.setAttribute("aria-live", "polite");
placeholder.setAttribute("hidden", "");
placeholder.classList.add("comment-thread__disabled");
const defaultText =
this.modal?.commentsDisabledPlaceholderDefaultText ||
"Comments are unavailable right now.";
placeholder.textContent = defaultText;
if (root?.parentElement) {
root.parentElement.insertBefore(placeholder, root);
} else {
container.insertBefore(placeholder, container.firstChild);
}
return placeholder;
}
}