mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-08 23:18:43 +00:00
added "revert" option to profile grid list
This commit is contained in:
@@ -14,7 +14,9 @@ export async function initChannelProfileView() {
|
|||||||
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||||
const npub = hashParams.get("npub");
|
const npub = hashParams.get("npub");
|
||||||
if (!npub) {
|
if (!npub) {
|
||||||
console.error("No npub found in hash. Example: #view=channel-profile&npub=npub1...");
|
console.error(
|
||||||
|
"No npub found in hash. Example: #view=channel-profile&npub=npub1..."
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,12 +37,12 @@ export async function initChannelProfileView() {
|
|||||||
// 3) Load user’s profile (banner, avatar, etc.)
|
// 3) Load user’s profile (banner, avatar, etc.)
|
||||||
await loadUserProfile(hexPub);
|
await loadUserProfile(hexPub);
|
||||||
|
|
||||||
// 4) Load user’s videos (filtered and rendered like home feed)
|
// 4) Load user’s videos (filtered + rendered like the home feed)
|
||||||
await loadUserVideos(hexPub);
|
await loadUserVideos(hexPub);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches and displays the user’s metadata (kind:0).
|
* Fetches and displays the user’s metadata (kind=0).
|
||||||
*/
|
*/
|
||||||
async function loadUserProfile(pubkey) {
|
async function loadUserProfile(pubkey) {
|
||||||
try {
|
try {
|
||||||
@@ -97,7 +99,8 @@ async function loadUserProfile(pubkey) {
|
|||||||
// Lightning Address
|
// Lightning Address
|
||||||
const lnEl = document.getElementById("channelLightning");
|
const lnEl = document.getElementById("channelLightning");
|
||||||
if (lnEl) {
|
if (lnEl) {
|
||||||
lnEl.textContent = meta.lud16 || meta.lud06 || "No lightning address found.";
|
lnEl.textContent =
|
||||||
|
meta.lud16 || meta.lud06 || "No lightning address found.";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("No metadata found for this user.");
|
console.warn("No metadata found for this user.");
|
||||||
@@ -109,8 +112,7 @@ async function loadUserProfile(pubkey) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches and displays this user's videos (kind=30078),
|
* Fetches and displays this user's videos (kind=30078),
|
||||||
* filtering out older/overshadowed events and blacklisted/non‐whitelisted entries.
|
* filtering out older overshadowed notes, blacklisted, non‐whitelisted, etc.
|
||||||
* Renders the video cards using the same `.ratio-16-9` approach as the home view.
|
|
||||||
*/
|
*/
|
||||||
async function loadUserVideos(pubkey) {
|
async function loadUserVideos(pubkey) {
|
||||||
try {
|
try {
|
||||||
@@ -133,7 +135,7 @@ async function loadUserVideos(pubkey) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Convert events to "video" objects
|
// 3) Convert to "video" objects
|
||||||
let videos = [];
|
let videos = [];
|
||||||
for (const evt of events) {
|
for (const evt of events) {
|
||||||
const vid = localConvertEventToVideo(evt);
|
const vid = localConvertEventToVideo(evt);
|
||||||
@@ -142,30 +144,27 @@ async function loadUserVideos(pubkey) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Deduplicate older overshadowed versions (newest only)
|
// 4) Deduplicate older overshadowed versions => newest only
|
||||||
videos = dedupeToNewestByRoot(videos);
|
videos = dedupeToNewestByRoot(videos);
|
||||||
|
|
||||||
// 5) Filter out blacklisted event IDs and authors
|
// 5) Filter out blacklisted IDs / authors
|
||||||
videos = videos.filter((video) => {
|
videos = videos.filter((video) => {
|
||||||
// Event-level blacklisting
|
// Event-level blacklisting
|
||||||
if (app.blacklistedEventIds.has(video.id)) {
|
if (app.blacklistedEventIds.has(video.id)) return false;
|
||||||
return false;
|
|
||||||
}
|
// Author-level
|
||||||
// Author-level checks
|
|
||||||
const authorNpub = app.safeEncodeNpub(video.pubkey) || video.pubkey;
|
const authorNpub = app.safeEncodeNpub(video.pubkey) || video.pubkey;
|
||||||
if (initialBlacklist.includes(authorNpub)) {
|
if (initialBlacklist.includes(authorNpub)) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isWhitelistEnabled && !initialWhitelist.includes(authorNpub)) {
|
if (isWhitelistEnabled && !initialWhitelist.includes(authorNpub)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6) Sort videos by newest first
|
// 6) Sort newest first
|
||||||
videos.sort((a, b) => b.created_at - a.created_at);
|
videos.sort((a, b) => b.created_at - a.created_at);
|
||||||
|
|
||||||
// 7) Render the videos in #channelVideoList
|
// 7) Render them
|
||||||
const container = document.getElementById("channelVideoList");
|
const container = document.getElementById("channelVideoList");
|
||||||
if (!container) {
|
if (!container) {
|
||||||
console.warn("channelVideoList element not found in DOM.");
|
console.warn("channelVideoList element not found in DOM.");
|
||||||
@@ -178,11 +177,13 @@ async function loadUserVideos(pubkey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
// We'll store them in a local array so gear handlers match the indexes
|
|
||||||
const channelVideos = videos;
|
const channelVideos = videos;
|
||||||
|
|
||||||
|
// We'll need all known events for revert-check
|
||||||
|
const allKnownEventsArray = Array.from(nostrClient.allEvents.values());
|
||||||
|
|
||||||
channelVideos.forEach((video, index) => {
|
channelVideos.forEach((video, index) => {
|
||||||
// If private + the user is the owner => decrypt
|
// Private => decrypt if owned by the user
|
||||||
if (
|
if (
|
||||||
video.isPrivate &&
|
video.isPrivate &&
|
||||||
video.pubkey === nostrClient.pubkey &&
|
video.pubkey === nostrClient.pubkey &&
|
||||||
@@ -192,8 +193,28 @@ async function loadUserVideos(pubkey) {
|
|||||||
video.alreadyDecrypted = true;
|
video.alreadyDecrypted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the logged-in user can edit
|
// Check if user can edit
|
||||||
const canEdit = video.pubkey === app.pubkey;
|
const canEdit = video.pubkey === app.pubkey;
|
||||||
|
let hasOlder = false;
|
||||||
|
if (canEdit && video.videoRootId) {
|
||||||
|
// Use the same hasOlderVersion approach as home feed
|
||||||
|
hasOlder = app.hasOlderVersion(video, allKnownEventsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's an older overshadowed version, show revert
|
||||||
|
const revertButton = hasOlder
|
||||||
|
? `
|
||||||
|
<button
|
||||||
|
class="block w-full text-left px-4 py-2 text-sm text-red-400
|
||||||
|
hover:bg-red-700 hover:text-white"
|
||||||
|
data-revert-index="${index}"
|
||||||
|
>
|
||||||
|
Revert
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
// Gear menu
|
||||||
let gearMenu = "";
|
let gearMenu = "";
|
||||||
if (canEdit) {
|
if (canEdit) {
|
||||||
gearMenu = `
|
gearMenu = `
|
||||||
@@ -224,6 +245,7 @@ async function loadUserVideos(pubkey) {
|
|||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
${revertButton}
|
||||||
<button
|
<button
|
||||||
class="block w-full text-left px-4 py-2 text-sm text-red-400
|
class="block w-full text-left px-4 py-2 text-sm text-red-400
|
||||||
hover:bg-red-700 hover:text-white"
|
hover:bg-red-700 hover:text-white"
|
||||||
@@ -237,7 +259,7 @@ async function loadUserVideos(pubkey) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse the `.ratio-16-9` approach from home feed
|
// Fallback thumbnail
|
||||||
const fallbackThumb = "assets/jpg/video-thumbnail-fallback.jpg";
|
const fallbackThumb = "assets/jpg/video-thumbnail-fallback.jpg";
|
||||||
const safeThumb = video.thumbnail || fallbackThumb;
|
const safeThumb = video.thumbnail || fallbackThumb;
|
||||||
|
|
||||||
@@ -253,7 +275,6 @@ async function loadUserVideos(pubkey) {
|
|||||||
"duration-300"
|
"duration-300"
|
||||||
);
|
);
|
||||||
|
|
||||||
// The "ratio-16-9" container ensures 16:9 cropping
|
|
||||||
cardEl.innerHTML = `
|
cardEl.innerHTML = `
|
||||||
<div class="cursor-pointer relative group">
|
<div class="cursor-pointer relative group">
|
||||||
<div class="ratio-16-9">
|
<div class="ratio-16-9">
|
||||||
@@ -290,11 +311,11 @@ async function loadUserVideos(pubkey) {
|
|||||||
|
|
||||||
container.appendChild(fragment);
|
container.appendChild(fragment);
|
||||||
|
|
||||||
// Use app's lazy loader for thumbs
|
// Lazy-load
|
||||||
const lazyEls = container.querySelectorAll("[data-lazy]");
|
const lazyEls = container.querySelectorAll("[data-lazy]");
|
||||||
lazyEls.forEach((el) => app.mediaLoader.observe(el));
|
lazyEls.forEach((el) => app.mediaLoader.observe(el));
|
||||||
|
|
||||||
// Gear menus
|
// Gear menu toggles
|
||||||
const gearButtons = container.querySelectorAll("[data-settings-dropdown]");
|
const gearButtons = container.querySelectorAll("[data-settings-dropdown]");
|
||||||
gearButtons.forEach((btn) => {
|
gearButtons.forEach((btn) => {
|
||||||
btn.addEventListener("click", (ev) => {
|
btn.addEventListener("click", (ev) => {
|
||||||
@@ -307,22 +328,41 @@ async function loadUserVideos(pubkey) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// "Edit" button
|
// "Edit" handler
|
||||||
const editBtns = container.querySelectorAll("[data-edit-index]");
|
const editBtns = container.querySelectorAll("[data-edit-index]");
|
||||||
editBtns.forEach((btn) => {
|
editBtns.forEach((btn) => {
|
||||||
btn.addEventListener("click", (ev) => {
|
btn.addEventListener("click", (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const idx = parseInt(btn.getAttribute("data-edit-index"), 10);
|
const idx = parseInt(btn.getAttribute("data-edit-index"), 10);
|
||||||
|
// Hide the dropdown
|
||||||
|
const dropdown = document.getElementById(`settingsDropdown-${idx}`);
|
||||||
|
if (dropdown) dropdown.classList.add("hidden");
|
||||||
app.handleEditVideo(idx);
|
app.handleEditVideo(idx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// "Delete All" button
|
// "Revert" handler
|
||||||
|
const revertBtns = container.querySelectorAll("[data-revert-index]");
|
||||||
|
revertBtns.forEach((btn) => {
|
||||||
|
btn.addEventListener("click", (ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const idx = parseInt(btn.getAttribute("data-revert-index"), 10);
|
||||||
|
// Hide the dropdown
|
||||||
|
const dropdown = document.getElementById(`settingsDropdown-${idx}`);
|
||||||
|
if (dropdown) dropdown.classList.add("hidden");
|
||||||
|
app.handleRevertVideo(idx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// "Delete All" handler
|
||||||
const deleteAllBtns = container.querySelectorAll("[data-delete-all-index]");
|
const deleteAllBtns = container.querySelectorAll("[data-delete-all-index]");
|
||||||
deleteAllBtns.forEach((btn) => {
|
deleteAllBtns.forEach((btn) => {
|
||||||
btn.addEventListener("click", (ev) => {
|
btn.addEventListener("click", (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const idx = parseInt(btn.getAttribute("data-delete-all-index"), 10);
|
const idx = parseInt(btn.getAttribute("data-delete-all-index"), 10);
|
||||||
|
// Hide the dropdown
|
||||||
|
const dropdown = document.getElementById(`settingsDropdown-${idx}`);
|
||||||
|
if (dropdown) dropdown.classList.add("hidden");
|
||||||
app.handleFullDeleteVideo(idx);
|
app.handleFullDeleteVideo(idx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user