diff --git a/js/app/authSessionCoordinator.js b/js/app/authSessionCoordinator.js index b02afd82..17523195 100644 --- a/js/app/authSessionCoordinator.js +++ b/js/app/authSessionCoordinator.js @@ -10,6 +10,7 @@ */ import { clearDecryptionSchemeCache } from "../nostr/decryptionSchemeCache.js"; +import { FEED_TYPES } from "../constants.js"; /** * @param {object} deps - Injected dependencies. @@ -573,9 +574,9 @@ export function createAuthSessionCoordinator(deps) { const logoutView = getHashViewName(); if ( typeof logoutView === "string" && - logoutView.trim().toLowerCase() === "for-you" + logoutView.trim().toLowerCase() === FEED_TYPES.FOR_YOU ) { - setHashView("most-recent-videos"); + setHashView(FEED_TYPES.RECENT); } const activeModalVideo = diff --git a/js/app/feedCoordinator.js b/js/app/feedCoordinator.js index f0b782ea..06d6237d 100644 --- a/js/app/feedCoordinator.js +++ b/js/app/feedCoordinator.js @@ -9,6 +9,8 @@ * Methods use `this` which is bound to the Application instance. */ +import { FEED_TYPES } from "../constants.js"; + /** * @param {object} deps - Injected dependencies. * @returns {object} Methods to be bound to the Application instance. @@ -115,7 +117,7 @@ export function createFeedCoordinator(deps) { }, /** - * Register the "for-you" feed pipeline. + * Register the FEED_TYPES.FOR_YOU feed pipeline. */ registerForYouFeed() { if (!this.feedEngine || typeof this.feedEngine.registerFeed !== "function") { @@ -124,7 +126,7 @@ export function createFeedCoordinator(deps) { const existingDefinition = typeof this.feedEngine.getFeedDefinition === "function" - ? this.feedEngine.getFeedDefinition("for-you") + ? this.feedEngine.getFeedDefinition(FEED_TYPES.FOR_YOU) : null; if (existingDefinition) { return existingDefinition; @@ -153,7 +155,7 @@ export function createFeedCoordinator(deps) { return defaultValue; }; - return this.feedEngine.registerFeed("for-you", { + return this.feedEngine.registerFeed(FEED_TYPES.FOR_YOU, { source: createActiveNostrSource({ service: this.nostrService }), stages: [ // Note: Tag-preference filtering is consolidated in createTagPreferenceFilterStage @@ -191,7 +193,7 @@ export function createFeedCoordinator(deps) { }, /** - * Register the "kids" feed pipeline. + * Register the FEED_TYPES.KIDS feed pipeline. */ registerKidsFeed() { if (!this.feedEngine || typeof this.feedEngine.registerFeed !== "function") { @@ -200,7 +202,7 @@ export function createFeedCoordinator(deps) { const existingDefinition = typeof this.feedEngine.getFeedDefinition === "function" - ? this.feedEngine.getFeedDefinition("kids") + ? this.feedEngine.getFeedDefinition(FEED_TYPES.KIDS) : null; if (existingDefinition) { return existingDefinition; @@ -276,7 +278,7 @@ export function createFeedCoordinator(deps) { }), ); - return this.feedEngine.registerFeed("kids", { + return this.feedEngine.registerFeed(FEED_TYPES.KIDS, { source: createActiveNostrSource({ service: this.nostrService }), stages: [ createBlacklistFilterStage({ @@ -332,7 +334,7 @@ export function createFeedCoordinator(deps) { }, /** - * Register the "explore" feed pipeline. + * Register the FEED_TYPES.EXPLORE feed pipeline. */ registerExploreFeed() { if (!this.feedEngine || typeof this.feedEngine.registerFeed !== "function") { @@ -341,7 +343,7 @@ export function createFeedCoordinator(deps) { const existingDefinition = typeof this.feedEngine.getFeedDefinition === "function" - ? this.feedEngine.getFeedDefinition("explore") + ? this.feedEngine.getFeedDefinition(FEED_TYPES.EXPLORE) : null; if (existingDefinition) { return existingDefinition; @@ -370,7 +372,7 @@ export function createFeedCoordinator(deps) { return defaultValue; }; - return this.feedEngine.registerFeed("explore", { + return this.feedEngine.registerFeed(FEED_TYPES.EXPLORE, { source: createActiveNostrSource({ service: this.nostrService }), stages: [ createDisinterestFilterStage(), @@ -412,7 +414,7 @@ export function createFeedCoordinator(deps) { const existingDefinition = typeof this.feedEngine.getFeedDefinition === "function" - ? this.feedEngine.getFeedDefinition("subscriptions") + ? this.feedEngine.getFeedDefinition(FEED_TYPES.SUBSCRIPTIONS) : null; if (existingDefinition) { return existingDefinition; @@ -441,7 +443,7 @@ export function createFeedCoordinator(deps) { return defaultValue; }; - return this.feedEngine.registerFeed("subscriptions", { + return this.feedEngine.registerFeed(FEED_TYPES.SUBSCRIPTIONS, { source: createSubscriptionAuthorsSource({ service: this.nostrService }), stages: [ // Note: Tag-preference filtering is consolidated in createTagPreferenceFilterStage @@ -667,7 +669,7 @@ export function createFeedCoordinator(deps) { const feedDefinition = this.feedEngine && typeof this.feedEngine.getFeedDefinition === "function" - ? this.feedEngine.getFeedDefinition("kids") + ? this.feedEngine.getFeedDefinition(FEED_TYPES.KIDS) : null; const configDefaults = feedDefinition && typeof feedDefinition.configDefaults === "object" @@ -816,7 +818,7 @@ export function createFeedCoordinator(deps) { } return this.feedEngine - .run("for-you", { runtime }) + .run(FEED_TYPES.FOR_YOU, { runtime }) .then((result) => { const videos = Array.isArray(result?.videos) ? result.videos : []; const metadata = { @@ -875,13 +877,13 @@ export function createFeedCoordinator(deps) { if (this.videoListView) { this.videoListView.state.videosMap = this.videosMap; } - this.updateFeedTelemetryMetadata("kids", [], metadata); + this.updateFeedTelemetryMetadata(FEED_TYPES.KIDS, [], metadata); this.renderVideoList({ videos: fallback, metadata }); return Promise.resolve({ videos: fallback, metadata }); } return this.feedEngine - .run("kids", { runtime }) + .run(FEED_TYPES.KIDS, { runtime }) .then((result) => { const videos = Array.isArray(result?.videos) ? result.videos : []; const metadata = { @@ -901,7 +903,7 @@ export function createFeedCoordinator(deps) { } const items = Array.isArray(result?.items) ? result.items : []; - this.updateFeedTelemetryMetadata("kids", items, metadata); + this.updateFeedTelemetryMetadata(FEED_TYPES.KIDS, items, metadata); const payload = { videos, metadata }; this.renderVideoList(payload); @@ -921,7 +923,7 @@ export function createFeedCoordinator(deps) { if (this.videoListView) { this.videoListView.state.videosMap = this.videosMap; } - this.updateFeedTelemetryMetadata("kids", [], metadata); + this.updateFeedTelemetryMetadata(FEED_TYPES.KIDS, [], metadata); const payload = { videos: fallback, metadata }; this.renderVideoList(payload); return payload; @@ -935,7 +937,7 @@ export function createFeedCoordinator(deps) { const applyExploreOrderingMetadata = (source) => { const next = source && typeof source === "object" ? { ...source } : {}; if (!next.sortOrder) { - next.sortOrder = "explore"; + next.sortOrder = FEED_TYPES.EXPLORE; } next.preserveOrder = true; return next; @@ -956,7 +958,7 @@ export function createFeedCoordinator(deps) { } return this.feedEngine - .run("explore", { runtime }) + .run(FEED_TYPES.EXPLORE, { runtime }) .then((result) => { const videos = Array.isArray(result?.videos) ? result.videos : []; const metadata = applyExploreOrderingMetadata({ @@ -1157,7 +1159,7 @@ export function createFeedCoordinator(deps) { async loadForYouVideos(forceFetch = false) { devLogger.log("Starting loadForYouVideos... (forceFetch =", forceFetch, ")"); - this.setFeedTelemetryContext("for-you"); + this.setFeedTelemetryContext(FEED_TYPES.FOR_YOU); this.checkRelayHealthWarning(); const container = this.mountVideoListView({ includeTags: false }); @@ -1206,7 +1208,7 @@ export function createFeedCoordinator(deps) { async loadKidsVideos(forceFetch = false) { devLogger.log("Starting loadKidsVideos... (forceFetch =", forceFetch, ")"); - this.setFeedTelemetryContext("kids"); + this.setFeedTelemetryContext(FEED_TYPES.KIDS); const container = this.mountVideoListView({ includeTags: true }); const hasCachedVideos = @@ -1254,7 +1256,7 @@ export function createFeedCoordinator(deps) { async loadExploreVideos(forceFetch = false) { devLogger.log("Starting loadExploreVideos... (forceFetch =", forceFetch, ")"); - this.setFeedTelemetryContext("explore"); + this.setFeedTelemetryContext(FEED_TYPES.EXPLORE); const container = this.mountVideoListView({ includeTags: false }); const hasCachedVideos = @@ -1483,7 +1485,7 @@ export function createFeedCoordinator(deps) { }, isForYouFeedActive() { - return this.feedTelemetryState?.activeFeed === "for-you"; + return this.feedTelemetryState?.activeFeed === FEED_TYPES.FOR_YOU; }, updateFeedTelemetryMetadata(feedName = "", items = [], metadata = {}) { @@ -1554,7 +1556,7 @@ export function createFeedCoordinator(deps) { }, updateForYouTelemetryMetadata(items = [], metadata = {}) { - this.updateFeedTelemetryMetadata("for-you", items, metadata); + this.updateFeedTelemetryMetadata(FEED_TYPES.FOR_YOU, items, metadata); }, resolveVideoForTelemetry(videoId) { @@ -1640,7 +1642,7 @@ export function createFeedCoordinator(deps) { videoId: eventId, }; - if (feedName === "for-you") { + if (feedName === FEED_TYPES.FOR_YOU) { const matchedTagsRaw = feedState.matchedTagsById?.get(eventId) || []; const matchedTags = Array.isArray(matchedTagsRaw) ? Array.from( @@ -1701,7 +1703,7 @@ export function createFeedCoordinator(deps) { }, buildForYouTelemetryPayload({ video, videoId, position } = {}) { - return this.buildFeedTelemetryPayload("for-you", { + return this.buildFeedTelemetryPayload(FEED_TYPES.FOR_YOU, { video, videoId, position, @@ -1742,8 +1744,8 @@ export function createFeedCoordinator(deps) { } const prefixMap = new Map([ - ["for-you", "for_you"], - ["kids", "kids_feed"], + [FEED_TYPES.FOR_YOU, "for_you"], + [FEED_TYPES.KIDS, "kids_feed"], ]); const prefix = prefixMap.get(normalized); @@ -1824,7 +1826,7 @@ export function createFeedCoordinator(deps) { }, emitForYouImpressions(videos = []) { - this.emitFeedImpressions(videos, { feedName: "for-you" }); + this.emitFeedImpressions(videos, { feedName: FEED_TYPES.FOR_YOU }); }, recordFeedClick(videoId, { feedName } = {}) { @@ -1862,7 +1864,7 @@ export function createFeedCoordinator(deps) { }, recordForYouClick(videoId) { - this.recordFeedClick(videoId, { feedName: "for-you" }); + this.recordFeedClick(videoId, { feedName: FEED_TYPES.FOR_YOU }); }, handleFeedViewTelemetry(detail = {}) { diff --git a/js/constants.js b/js/constants.js index 8978f265..eedfe2f5 100644 --- a/js/constants.js +++ b/js/constants.js @@ -483,3 +483,15 @@ export function getTrustedMuteHideThreshold() { export function getTrustedSpamHideThreshold() { return TRUSTED_SPAM_HIDE_THRESHOLD; } + +export const FEED_TYPES = Object.freeze({ + RECENT: "most-recent-videos", + FOR_YOU: "for-you", + KIDS: "kids", + EXPLORE: "explore", + SUBSCRIPTIONS: "subscriptions", + HISTORY: "history", + CHANNEL: "channel-profile", + DOCS: "docs", + SEARCH: "search", +}); diff --git a/js/hashChangeHandler.js b/js/hashChangeHandler.js index 71303a00..705d0a57 100644 --- a/js/hashChangeHandler.js +++ b/js/hashChangeHandler.js @@ -1,4 +1,5 @@ import { devLogger as defaultDevLogger, userLogger as defaultUserLogger } from "./utils/logger.js"; +import { FEED_TYPES } from "./constants.js"; const LEGACY_DOCS_VIEWS = { about: "about", @@ -81,8 +82,8 @@ export function createHashChangeHandler({ typeof window !== "undefined" && window.location && window.location.hash ? window.location.hash : ""; - if (hash === "#kids") { - window.location.hash = "#view=kids"; + if (hash === `#${FEED_TYPES.KIDS}`) { + window.location.hash = `#view=${FEED_TYPES.KIDS}`; return; } const match = hash.match(/^#view=([^&]+)/); @@ -90,8 +91,8 @@ export function createHashChangeHandler({ try { if (!match || !match[1]) { const defaultViewName = resolveIsLoggedIn() - ? "for-you" - : "most-recent-videos"; + ? FEED_TYPES.FOR_YOU + : FEED_TYPES.RECENT; if (defaultViewName === currentViewName) { return; @@ -119,7 +120,7 @@ export function createHashChangeHandler({ return; } - if (typeof viewName === "string" && viewName.toLowerCase() === "history") { + if (typeof viewName === "string" && viewName.toLowerCase() === FEED_TYPES.HISTORY) { } const viewUrl = `views/${viewName}.html`; diff --git a/js/viewManager.js b/js/viewManager.js index a1e41e4c..fe23b0d5 100644 --- a/js/viewManager.js +++ b/js/viewManager.js @@ -10,6 +10,7 @@ import { applyDesignSystemAttributes } from "./designSystem.js"; import { devLogger, userLogger } from "./utils/logger.js"; import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js"; import { initDocsView } from "./docsView.js"; +import { FEED_TYPES } from "./constants.js"; const TRACKING_SCRIPT_PATTERN = /(?:^|\/)tracking\.js(?:$|\?)/; @@ -83,7 +84,7 @@ export async function loadView(viewUrl) { * Registry of view-specific initialization functions. */ export const viewInitRegistry = { - "most-recent-videos": () => { + [FEED_TYPES.RECENT]: () => { const app = getApplication(); if (app && typeof app.loadVideos === "function") { if (typeof app.mountVideoListView === "function") { @@ -105,7 +106,7 @@ export const viewInitRegistry = { ); } }, - "for-you": () => { + [FEED_TYPES.FOR_YOU]: () => { initForYouView(); const app = getApplication(); if (app && typeof app.loadForYouVideos === "function") { @@ -120,7 +121,7 @@ export const viewInitRegistry = { refreshApp.forceRefreshAllProfiles(); } }, - kids: () => { + [FEED_TYPES.KIDS]: () => { initKidsView(); const app = getApplication(); if (app && typeof app.loadKidsVideos === "function") { @@ -134,7 +135,7 @@ export const viewInitRegistry = { refreshApp.forceRefreshAllProfiles(); } }, - explore: () => { + [FEED_TYPES.EXPLORE]: () => { initExploreView(); const app = getApplication(); if (app && typeof app.loadExploreVideos === "function") { @@ -148,10 +149,10 @@ export const viewInitRegistry = { refreshApp.forceRefreshAllProfiles(); } }, - docs: async () => { + [FEED_TYPES.DOCS]: async () => { await initDocsView(); }, - history: async () => { + [FEED_TYPES.HISTORY]: async () => { try { const module = await import("./historyView.js"); if (typeof module.initHistoryView === "function") { @@ -167,7 +168,7 @@ export const viewInitRegistry = { * - If user is logged in, calls subscriptions.showSubscriptionVideos * which loads subs if needed and renders the video grid in #subscriptionsVideoList */ - subscriptions: async () => { + [FEED_TYPES.SUBSCRIPTIONS]: async () => { devLogger.log("Subscriptions view loaded."); const infoTrigger = document.getElementById("subscriptionsInfoTrigger"); @@ -195,11 +196,11 @@ export const viewInitRegistry = { ); }, - "channel-profile": () => { + [FEED_TYPES.CHANNEL]: () => { // Call the initialization function from channelProfile.js initChannelProfileView(); }, - search: async () => { + [FEED_TYPES.SEARCH]: async () => { try { const module = await import("./searchView.js"); if (typeof module.initSearchView === "function") { diff --git a/package-lock.json b/package-lock.json index 7dc2f8fa..27ff9d46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,9 @@ "stylelint": "^16.12.0", "stylelint-config-standard": "^36.0.1", "vitest": "^4.0.18" + }, + "engines": { + "node": ">=22" } }, "node_modules/@acemir/cssom": {