playlists(#37): profile-modal "Playlists" tab — rename / delete / reorder / remove

A central management surface, a sibling tab to "My Videos" (gated by
FEATURE_PLAYLISTS — hidden when off).

- components/profile-modal.html: "Playlists" nav item + #profilePanePlaylists.
- js/ui/profileModal/ProfilePlaylistsController.js: lists the user's playlists
  (incl. empty) with a collapsible row each — Rename, Delete, and an expand to a
  video sublist with ↑/↓ reorder and Remove. Fetches the referenced creators'
  videos so the sublist shows real titles/thumbnails. Edits are optimistic and
  re-sync on failure; publishes via the facade; dispatches
  bitvid:playlists-changed so the channel section + this pane stay in sync.
- profileModalController.js: instantiate + bind nav/pane + cache DOM + register
  listeners + selectPane("playlists") refresh; hide the tab when the flag is off.
- promptDialog.js: showTextPrompt (plain text prompt) for rename.
- playlistFacade.js: deletePlaylist — NIP-09 kind-5 (a + e refs) plus an empty
  republish so kind-5-ignoring readers also drop it.
- tailwind.source.css: .playlist-row* styles (token-only).

Lint + build clean; profile-modal-controller tests 26/26.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-06 15:16:06 -04:00
1 parent 2a543c3ba9
commit 73a99358a2
6 files changed
+777

No files matched your search

+15
View File
@@ -45,6 +45,8 @@ import { DMSettingsModalController } from "./dm/DMSettingsModalController.js";
import { ProfileWalletController } from "./profileModal/ProfileWalletController.js";
import { ProfileStorageController } from "./profileModal/ProfileStorageController.js";
import { MyVideosController } from "./profileModal/MyVideosController.js";
import { ProfilePlaylistsController } from "./profileModal/ProfilePlaylistsController.js";
import { FEATURE_PLAYLISTS } from "../constants.js";
import { ProfileDirectMessageController } from "./profileModal/ProfileDirectMessageController.js";
import { ProfileRelayController } from "./profileModal/ProfileRelayController.js";
import { ProfileHashtagController } from "./profileModal/ProfileHashtagController.js";
@@ -530,6 +532,7 @@ export class ProfileModalController {
this.walletController = new ProfileWalletController(this);
this.storageController = new ProfileStorageController(this);
this.myVideosController = new MyVideosController(this);
this.playlistsController = new ProfilePlaylistsController(this);
this.mobileViewState = "menu";
this.lastMobileViewState = "menu";
this.setActivePane(this.getActivePane());
@@ -696,6 +699,12 @@ export class ProfileModalController {
this.navButtons.storage = document.getElementById("profileNavStorage") || null;
this.navButtons.myvideos =
document.getElementById("profileNavMyVideos") || null;
this.navButtons.playlists =
document.getElementById("profileNavPlaylists") || null;
// Hide the Playlists tab entirely while the feature is off.
if (!FEATURE_PLAYLISTS && this.navButtons.playlists) {
this.navButtons.playlists.classList.add("hidden");
}
this.navButtons.hashtags =
document.getElementById("profileNavHashtags") || null;
this.navButtons.subscriptions =
@@ -721,6 +730,8 @@ export class ProfileModalController {
this.panes.storage = document.getElementById("profilePaneStorage") || null;
this.panes.myvideos =
document.getElementById("profilePaneMyVideos") || null;
this.panes.playlists =
document.getElementById("profilePanePlaylists") || null;
this.panes.hashtags = document.getElementById("profilePaneHashtags") || null;
this.panes.subscriptions =
document.getElementById("profilePaneSubscriptions") || null;
@@ -816,6 +827,7 @@ export class ProfileModalController {
this.walletController.cacheDomReferences();
this.storageController.cacheDomReferences();
this.myVideosController.cacheDomReferences();
this.playlistsController.cacheDomReferences();
if (this.dmController.pendingMessagesRender) {
const { messages, actorPubkey } = this.dmController.pendingMessagesRender;
@@ -1976,6 +1988,7 @@ export class ProfileModalController {
this.walletController.registerEventListeners();
this.storageController.registerEventListeners();
this.myVideosController.registerEventListeners();
this.playlistsController.registerEventListeners();
this.adminController.registerEventListeners();
this.moderationController.registerEventListeners();
@@ -3413,6 +3426,8 @@ export class ProfileModalController {
this.storageController.populateStoragePane();
} else if (target === "myvideos") {
void this.myVideosController.populate({ forceFetch: true });
} else if (target === "playlists") {
void this.playlistsController.refresh();
} else if (target === "hashtags") {
this.hashtagController.populateHashtagPreferences();
if (activeHex && this.services.hashtagPreferences) {