Files
bitvid/js/forYouView.js
google-labs-jules[bot] bae4020682 feat: add info icon with filter logic popover to For You feed
Adds a clickable info icon next to the "For You" title that displays a popover explaining the feed's filtering logic ("Most Recent Video - Disinterests Tags - Watch History + Interests Tags"). Implemented as a reusable helper `attachFeedInfoPopover` in `js/ui/components/FeedInfoPopover.js` to support future use on other feed pages. Updated `views/for-you.html` and `js/forYouView.js` to integrate the new component. Verified with Playwright.
2026-01-21 17:48:01 +00:00

153 lines
4.0 KiB
JavaScript

import { getApplication } from "./applicationContext.js";
import { devLogger } from "./utils/logger.js";
import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js";
const VIDEO_CARD_SELECTOR = "[data-component=\"video-card\"]";
const EMPTY_STATE_SELECTOR = "[data-for-you-empty-state]";
const LOADING_SELECTOR = ".sidebar-loading-wrapper";
class ForYouView {
constructor({ document: doc = document } = {}) {
this.document = doc;
this.container = null;
this.observer = null;
this.handleContainerClick = this.handleContainerClick.bind(this);
this.handleMutation = this.handleMutation.bind(this);
}
init() {
this.container = this.document?.getElementById("videoList") || null;
if (!this.container) {
return;
}
this.container.addEventListener("click", this.handleContainerClick);
this.observeContainer();
this.updateEmptyState();
const infoTrigger = this.document.getElementById("feedInfoTrigger");
if (infoTrigger) {
attachFeedInfoPopover(
infoTrigger,
"Most Recent Video - Disinterests Tags - Watch History + Interests Tags"
);
}
}
observeContainer() {
if (!this.container || typeof MutationObserver === "undefined") {
return;
}
if (this.observer) {
this.observer.disconnect();
}
this.observer = new MutationObserver(this.handleMutation);
this.observer.observe(this.container, {
childList: true,
subtree: true,
});
}
handleMutation() {
this.updateEmptyState();
}
handleContainerClick(event) {
const target = event?.target instanceof HTMLElement ? event.target : null;
const actionButton = target?.closest?.("[data-action=\"open-hashtag-preferences\"]");
if (!(actionButton instanceof HTMLElement)) {
return;
}
event.preventDefault();
this.openHashtagPreferences();
}
openHashtagPreferences() {
const app = getApplication();
if (app?.profileController && typeof app.profileController.show === "function") {
app.profileController.show("hashtags");
return;
}
devLogger.warn("[ForYouView] Profile modal controller unavailable.");
}
updateEmptyState() {
if (!this.container) {
return;
}
const hasVideos = Boolean(this.container.querySelector(VIDEO_CARD_SELECTOR));
const isLoading = Boolean(this.container.querySelector(LOADING_SELECTOR));
const existingEmptyState = this.container.querySelector(EMPTY_STATE_SELECTOR);
if (isLoading) {
if (existingEmptyState) {
existingEmptyState.remove();
}
return;
}
if (hasVideos) {
if (existingEmptyState) {
existingEmptyState.remove();
}
return;
}
if (existingEmptyState) {
return;
}
const emptyState = this.buildEmptyState();
if (emptyState) {
this.container.innerHTML = "";
this.container.appendChild(emptyState);
}
}
buildEmptyState() {
if (!this.document) {
return null;
}
const wrapper = this.document.createElement("div");
wrapper.className = "col-span-full";
wrapper.dataset.forYouEmptyState = "true";
const card = this.document.createElement("div");
card.className = "card p-lg text-center";
const title = this.document.createElement("h3");
title.className = "text-lg font-semibold text-text";
title.textContent = "Your For You feed is empty";
const copy = this.document.createElement("p");
copy.className = "mt-2 text-sm text-muted";
copy.textContent =
"Add some interests to personalize your recommendations and start discovering videos.";
const button = this.document.createElement("button");
button.type = "button";
button.className = "btn focus-ring mt-4";
button.dataset.action = "open-hashtag-preferences";
button.textContent = "Add interests";
card.appendChild(title);
card.appendChild(copy);
card.appendChild(button);
wrapper.appendChild(card);
return wrapper;
}
}
export function initForYouView() {
const view = new ForYouView();
view.init();
return view;
}