This commit is contained in:
Keep Creating Online
2025-01-08 22:06:26 -05:00
parent a6b66f9ca3
commit 71e95c395e
8 changed files with 1222 additions and 766 deletions

View File

@@ -69,6 +69,7 @@
</div>
</main>
<!-- Footer -->
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
@@ -79,6 +80,24 @@
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
|
<a
href="https://bitvid.netlify.app"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.netlify.app
</a>
<div class="mt-2 space-x-4">
<a
href="https://github.com/PR0M3TH3AN/bitvid"
@@ -132,7 +151,10 @@
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS: k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>

View File

@@ -1,11 +1,5 @@
![](https://bitvid.netlify.app/assets/jpg/bitvid.jpg)
BTC DNS: [bitvid.btc.us](https://bitvid.btc.us)
ETH DNS: [bitvid.eth.limo](https://bitvid.eth.limo)
**_IPNS: k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1_**
# About bitvid
Welcome to bitvid, a new kind of video platform that puts you in control. Unlike traditional video sites that keep your content on their servers, bitvid lets videos flow directly between creators and viewers. Think of it like a digital potluck where everyone brings and shares content directly with each other!

19
src/content/ipns.md Normal file
View File

@@ -0,0 +1,19 @@
# IPNS Gateways
Below is a list of available IPNS gateways you can use with the hash `k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1`:
1. **[FLK IPFS Gateway](https://k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1.ipns.flk-ipfs.xyz/)**
A public gateway that resolves the provided IPNS hash.
2. **[Aragon IPFS Gateway](https://ipfs.eth.aragon.network/ipfs/bafybeih2ebj55ki3wvasj5i3rhwgjn6e72f6vxsrlrjfqvzezot2eoeqz4/)**
A gateway hosted by Aragon for IPFS content resolution.
3. **[Dweb.link Gateway](https://dweb.link/ipns/k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1)**
A subdomain resolution gateway provided by Protocol Labs.
4. **[IPFS.io Gateway](https://ipfs.io/ipns/k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1)**
A public gateway operated by Protocol Labs.
---
**Note:** The availability and performance of these gateways may vary.

View File

@@ -69,6 +69,7 @@
</div>
</main>
<!-- Footer -->
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
@@ -79,6 +80,24 @@
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
|
<a
href="https://bitvid.netlify.app"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.netlify.app
</a>
<div class="mt-2 space-x-4">
<a
href="https://github.com/PR0M3TH3AN/bitvid"
@@ -132,7 +151,10 @@
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS: k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>

View File

@@ -198,7 +198,9 @@
<video id="video" controls class="w-full rounded-lg shadow-md"></video>
<!-- Status and Stats -->
<div class="mt-4">
<div id="status" class="text-gray-700 mb-2">Initializing...</div>
<div id="status" class="text-gray-700 mb-2">
Initializing... Just give it a sec.
</div>
<div class="w-full bg-gray-300 rounded-full h-2 mb-2">
<div
class="bg-blue-500 h-2 rounded-full"
@@ -232,27 +234,6 @@
<div
class="bg-gray-900 rounded-lg max-w-4xl w-full relative overflow-hidden"
>
<!-- Close button -->
<button
id="closePlayer"
class="absolute top-4 right-4 z-50 text-white bg-gray-800 hover:bg-gray-700 rounded-full p-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
<!-- Video container -->
<div class="aspect-w-16 aspect-h-9">
<video id="modalVideo" controls class="w-full rounded-t-lg"></video>
@@ -268,7 +249,9 @@
class="flex items-center justify-between text-sm text-gray-400 mb-4"
>
<span id="videoTimestamp">just now</span>
<div id="modalStatus" class="text-gray-300">Initializing...</div>
<div id="modalStatus" class="text-gray-300">
Initializing... Just give it a sec.
</div>
</div>
<!-- Creator info -->
@@ -320,6 +303,108 @@
seed. zap. subscribe.
</h2>
</div>
<!-- Disclaimer Modal -->
<div
id="disclaimerModal"
class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4"
>
<div
class="bg-gray-900 rounded-lg max-w-2xl w-full relative overflow-hidden"
>
<!-- Content -->
<div class="p-6 text-white">
<!-- Logo -->
<div class="flex justify-center mb-8">
<img
src="assets/svg/bitvid-logo-dark-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</div>
<h2 class="text-2xl font-bold mb-4 text-center">
Welcome to bitvid
</h2>
<!-- Warning Alert -->
<div
class="bg-yellow-900/20 border border-yellow-700/50 rounded-lg p-4 mb-6 flex items-start"
>
<svg
class="h-5 w-5 text-yellow-500 mt-0.5 mr-3 flex-shrink-0"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<p class="text-yellow-200">
This platform is still in development. You may encounter bugs or
missing features.
</p>
</div>
<!-- Main Content -->
<div class="space-y-6 text-gray-300">
<p>
bitvid is a decentralized video platform where content is shared
directly between users. We want you to understand a few
important points before you continue:
</p>
<div class="space-y-4">
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">
Content Responsibility
</h3>
<p class="text-gray-400">
We don't host or control any videos shared on bitvid. All
content is the responsibility of the creators who share it.
Please follow your local laws and use the platform
responsibly.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">Platform Status</h3>
<p class="text-gray-400">
bitvid is a work in progress. Features may change or break,
and security improvements are ongoing. Your feedback and
patience help us build a better platform.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">Get Involved</h3>
<p class="text-gray-400">
Are you a developer? We'd love your help! Visit our GitHub
repository to contribute to building the future of
decentralized video sharing.
</p>
</div>
</div>
</div>
<!-- Action Button -->
<div class="mt-6">
<button
id="acceptDisclaimer"
class="w-full bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900 transition-colors duration-200"
>
I Understand
</button>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="https://bitvid.btc.us"
@@ -329,6 +414,24 @@
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
|
<a
href="https://bitvid.netlify.app"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.netlify.app
</a>
<div class="mt-2 space-x-4">
<a
href="https://github.com/PR0M3TH3AN/bitvid"
@@ -382,7 +485,10 @@
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS: k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>

192
src/ipns.html Normal file
View File

@@ -0,0 +1,192 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | About</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="BitVid - Markdown Viewer" />
<meta
property="og:description"
content="View and render markdown content dynamically."
/>
<meta
property="og:image"
content="https://bitvid.netlify.app/assets/jpg/bitvid.jpg"
/>
<meta property="og:url" content="https://bitvid.btc.us" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Tailwind CSS -->
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<!-- Main Layout Styles -->
<link href="css/style.css" rel="stylesheet" />
<!-- Markdown-Specific Styles -->
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo links back to index.html (or "/") -->
<a href="index.html">
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
</div>
</header>
<!-- Markdown Content Section -->
<main>
<!--
We give this section a white background and a shadow
just like you originally had for other cards.
-->
<div id="markdown-container" class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">Loading Content...</h2>
</div>
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
|
<a
href="https://bitvid.netlify.app"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.netlify.app
</a>
<div class="mt-2 space-x-4">
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>
<!-- Marked.js (for converting markdown to HTML) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js (optional for code block highlighting) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
async function loadMarkdown() {
const response = await fetch("content/ipns.md");
if (response.ok) {
const markdown = await response.text();
const container = document.getElementById("markdown-container");
// Convert markdown to HTML
const html = marked.parse(markdown);
// Insert the HTML into the container
container.innerHTML = html;
// (Optional) Highlight code blocks
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
} else {
document.getElementById("markdown-container").innerHTML =
'<p class="text-red-500">Error loading content. Please try again later.</p>';
}
}
loadMarkdown();
</script>
</body>
</html>

View File

@@ -1,63 +1,66 @@
// js/app.js
import { nostrClient } from './nostr.js';
import { torrentClient } from './webtorrent.js';
import { isDevMode } from './config.js';
import { nostrClient } from "./nostr.js";
import { torrentClient } from "./webtorrent.js";
import { isDevMode } from "./config.js";
import { disclaimerModal } from "./disclaimer.js";
class bitvidApp {
constructor() {
// Authentication Elements
this.loginButton = document.getElementById('loginButton');
this.logoutButton = document.getElementById('logoutButton');
this.userStatus = document.getElementById('userStatus');
this.userPubKey = document.getElementById('userPubKey');
this.loginButton = document.getElementById("loginButton");
this.logoutButton = document.getElementById("logoutButton");
this.userStatus = document.getElementById("userStatus");
this.userPubKey = document.getElementById("userPubKey");
// Form Elements
this.submitForm = document.getElementById('submitForm');
this.videoFormContainer = document.getElementById('videoFormContainer');
this.submitForm = document.getElementById("submitForm");
this.videoFormContainer = document.getElementById("videoFormContainer");
// Video List Element
this.videoList = document.getElementById('videoList');
this.videoList = document.getElementById("videoList");
// Video Player Elements
this.playerSection = document.getElementById('playerSection');
this.videoElement = document.getElementById('video');
this.status = document.getElementById('status');
this.progressBar = document.getElementById('progress');
this.peers = document.getElementById('peers');
this.speed = document.getElementById('speed');
this.downloaded = document.getElementById('downloaded');
this.playerSection = document.getElementById("playerSection");
this.videoElement = document.getElementById("video");
this.status = document.getElementById("status");
this.progressBar = document.getElementById("progress");
this.peers = document.getElementById("peers");
this.speed = document.getElementById("speed");
this.downloaded = document.getElementById("downloaded");
// Modal Elements
this.playerModal = document.getElementById('playerModal');
this.modalVideo = document.getElementById('modalVideo');
this.modalStatus = document.getElementById('modalStatus');
this.modalProgress = document.getElementById('modalProgress');
this.modalPeers = document.getElementById('modalPeers');
this.modalSpeed = document.getElementById('modalSpeed');
this.modalDownloaded = document.getElementById('modalDownloaded');
this.closePlayerBtn = document.getElementById('closePlayer');
this.playerModal = document.getElementById("playerModal");
this.modalVideo = document.getElementById("modalVideo");
this.modalStatus = document.getElementById("modalStatus");
this.modalProgress = document.getElementById("modalProgress");
this.modalPeers = document.getElementById("modalPeers");
this.modalSpeed = document.getElementById("modalSpeed");
this.modalDownloaded = document.getElementById("modalDownloaded");
this.closePlayerBtn = document.getElementById("closePlayer");
// Video Info Elements
this.videoTitle = document.getElementById('videoTitle');
this.videoDescription = document.getElementById('videoDescription');
this.videoTimestamp = document.getElementById('videoTimestamp');
this.videoTitle = document.getElementById("videoTitle");
this.videoDescription = document.getElementById("videoDescription");
this.videoTimestamp = document.getElementById("videoTimestamp");
// Creator Info Elements
this.creatorAvatar = document.getElementById('creatorAvatar').querySelector('img');
this.creatorName = document.getElementById('creatorName');
this.creatorNpub = document.getElementById('creatorNpub');
this.creatorAvatar = document
.getElementById("creatorAvatar")
.querySelector("img");
this.creatorName = document.getElementById("creatorName");
this.creatorNpub = document.getElementById("creatorNpub");
// Notification Containers
this.errorContainer = document.getElementById('errorContainer');
this.successContainer = document.getElementById('successContainer');
this.errorContainer = document.getElementById("errorContainer");
this.successContainer = document.getElementById("successContainer");
this.pubkey = null;
this.currentMagnetUri = null;
// ADDED FOR VERSIONING/PRIVATE/DELETE:
// If you created an <input type="checkbox" id="isPrivate" /> in your HTML form:
this.isPrivateCheckbox = document.getElementById('isPrivate');
this.isPrivateCheckbox = document.getElementById("isPrivate");
}
/**
@@ -67,22 +70,23 @@ class bitvidApp {
async init() {
try {
// Hide and reset player states
this.playerSection.style.display = 'none';
this.playerModal.style.display = 'none';
this.playerSection.style.display = "none";
this.playerModal.style.display = "none";
this.currentMagnetUri = null;
// Initialize Nostr and check login
await nostrClient.init();
const savedPubKey = localStorage.getItem('userPubKey');
const savedPubKey = localStorage.getItem("userPubKey");
if (savedPubKey) {
this.login(savedPubKey, false);
}
this.setupEventListeners();
disclaimerModal.show(); // Add this line here
await this.loadVideos();
} catch (error) {
console.error('Init failed:', error);
this.showError('Failed to connect to Nostr relay');
console.error("Init failed:", error);
this.showError("Failed to connect to Nostr relay");
}
}
@@ -90,24 +94,24 @@ class bitvidApp {
* Formats a timestamp into a "time ago" format.
*/
formatTimeAgo(timestamp) {
const seconds = Math.floor((Date.now() / 1000) - timestamp);
const seconds = Math.floor(Date.now() / 1000 - timestamp);
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60
minute: 60,
};
for (const [unit, secondsInUnit] of Object.entries(intervals)) {
const interval = Math.floor(seconds / secondsInUnit);
if (interval >= 1) {
return `${interval} ${unit}${interval === 1 ? '' : 's'} ago`;
return `${interval} ${unit}${interval === 1 ? "" : "s"} ago`;
}
}
return 'just now';
return "just now";
}
/**
@@ -115,34 +119,34 @@ class bitvidApp {
*/
setupEventListeners() {
// Login Button
this.loginButton.addEventListener('click', async () => {
this.loginButton.addEventListener("click", async () => {
try {
const pubkey = await nostrClient.login();
this.login(pubkey, true);
} catch (error) {
this.log('Login failed:', error);
this.showError('Failed to login. Please try again.');
this.log("Login failed:", error);
this.showError("Failed to login. Please try again.");
}
});
// Logout Button
this.logoutButton.addEventListener('click', () => {
this.logoutButton.addEventListener("click", () => {
this.logout();
});
// Form submission
this.submitForm.addEventListener('submit', (e) => this.handleSubmit(e));
this.submitForm.addEventListener("submit", (e) => this.handleSubmit(e));
// Close Modal Button
if (this.closePlayerBtn) {
this.closePlayerBtn.addEventListener('click', async () => {
this.closePlayerBtn.addEventListener("click", async () => {
await this.hideModal();
});
}
// Close Modal by clicking outside content
if (this.playerModal) {
this.playerModal.addEventListener('click', async (e) => {
this.playerModal.addEventListener("click", async (e) => {
if (e.target === this.playerModal) {
await this.hideModal();
}
@@ -150,41 +154,45 @@ class bitvidApp {
}
// Video error handling
this.videoElement.addEventListener('error', (e) => {
this.videoElement.addEventListener("error", (e) => {
const error = e.target.error;
this.log('Video error:', error);
this.log("Video error:", error);
if (error) {
this.showError(`Video playback error: ${error.message || 'Unknown error'}`);
this.showError(
`Video playback error: ${error.message || "Unknown error"}`
);
}
});
// Detailed Modal Video Event Listeners
if (this.modalVideo) {
this.modalVideo.addEventListener('error', (e) => {
this.modalVideo.addEventListener("error", (e) => {
const error = e.target.error;
this.log('Modal video error:', error);
this.log("Modal video error:", error);
if (error) {
this.log('Error code:', error.code);
this.log('Error message:', error.message);
this.showError(`Video playback error: ${error.message || 'Unknown error'}`);
this.log("Error code:", error.code);
this.log("Error message:", error.message);
this.showError(
`Video playback error: ${error.message || "Unknown error"}`
);
}
});
this.modalVideo.addEventListener('loadstart', () => {
this.log('Video loadstart event fired');
this.modalVideo.addEventListener("loadstart", () => {
this.log("Video loadstart event fired");
});
this.modalVideo.addEventListener('loadedmetadata', () => {
this.log('Video loadedmetadata event fired');
this.modalVideo.addEventListener("loadedmetadata", () => {
this.log("Video loadedmetadata event fired");
});
this.modalVideo.addEventListener('canplay', () => {
this.log('Video canplay event fired');
this.modalVideo.addEventListener("canplay", () => {
this.log("Video canplay event fired");
});
}
// Cleanup on page unload
window.addEventListener('beforeunload', async () => {
window.addEventListener("beforeunload", async () => {
await this.cleanup();
});
}
@@ -194,15 +202,15 @@ class bitvidApp {
*/
login(pubkey, saveToStorage = true) {
this.pubkey = pubkey;
this.loginButton.classList.add('hidden');
this.logoutButton.classList.remove('hidden');
this.userStatus.classList.remove('hidden');
this.loginButton.classList.add("hidden");
this.logoutButton.classList.remove("hidden");
this.userStatus.classList.remove("hidden");
this.userPubKey.textContent = pubkey;
this.videoFormContainer.classList.remove('hidden');
this.videoFormContainer.classList.remove("hidden");
this.log(`User logged in as: ${pubkey}`);
if (saveToStorage) {
localStorage.setItem('userPubKey', pubkey);
localStorage.setItem("userPubKey", pubkey);
}
}
@@ -212,13 +220,13 @@ class bitvidApp {
logout() {
nostrClient.logout();
this.pubkey = null;
this.loginButton.classList.remove('hidden');
this.logoutButton.classList.add('hidden');
this.userStatus.classList.add('hidden');
this.userPubKey.textContent = '';
this.videoFormContainer.classList.add('hidden');
localStorage.removeItem('userPubKey');
this.log('User logged out.');
this.loginButton.classList.remove("hidden");
this.logoutButton.classList.add("hidden");
this.userStatus.classList.add("hidden");
this.userPubKey.textContent = "";
this.videoFormContainer.classList.add("hidden");
localStorage.removeItem("userPubKey");
this.log("User logged out.");
}
/**
@@ -228,17 +236,17 @@ class bitvidApp {
try {
if (this.videoElement) {
this.videoElement.pause();
this.videoElement.src = '';
this.videoElement.src = "";
this.videoElement.load();
}
if (this.modalVideo) {
this.modalVideo.pause();
this.modalVideo.src = '';
this.modalVideo.src = "";
this.modalVideo.load();
}
await torrentClient.cleanup();
} catch (error) {
this.log('Cleanup error:', error);
this.log("Cleanup error:", error);
}
}
@@ -247,7 +255,7 @@ class bitvidApp {
*/
async hideVideoPlayer() {
await this.cleanup();
this.playerSection.classList.add('hidden');
this.playerSection.classList.add("hidden");
}
/**
@@ -255,8 +263,8 @@ class bitvidApp {
*/
async hideModal() {
await this.cleanup();
this.playerModal.style.display = 'none';
this.playerModal.classList.add('hidden');
this.playerModal.style.display = "none";
this.playerModal.classList.add("hidden");
}
/**
@@ -266,11 +274,11 @@ class bitvidApp {
e.preventDefault();
if (!this.pubkey) {
this.showError('Please login to post a video.');
this.showError("Please login to post a video.");
return;
}
const descriptionElement = document.getElementById('description');
const descriptionElement = document.getElementById("description");
// ADDED FOR VERSIONING/PRIVATE/DELETE:
// If you have a checkbox with id="isPrivate" in HTML
@@ -280,18 +288,18 @@ class bitvidApp {
const formData = {
version: 2, // We set the version to 2 for new posts
title: document.getElementById('title')?.value.trim() || '',
magnet: document.getElementById('magnet')?.value.trim() || '',
thumbnail: document.getElementById('thumbnail')?.value.trim() || '',
description: descriptionElement?.value.trim() || '',
mode: isDevMode ? 'dev' : 'live',
isPrivate // new field to handle private listings
title: document.getElementById("title")?.value.trim() || "",
magnet: document.getElementById("magnet")?.value.trim() || "",
thumbnail: document.getElementById("thumbnail")?.value.trim() || "",
description: descriptionElement?.value.trim() || "",
mode: isDevMode ? "dev" : "live",
isPrivate, // new field to handle private listings
};
this.log('Form Data Collected:', formData);
this.log("Form Data Collected:", formData);
if (!formData.title || !formData.magnet) {
this.showError('Title and Magnet URI are required.');
this.showError("Title and Magnet URI are required.");
return;
}
@@ -305,10 +313,10 @@ class bitvidApp {
}
await this.loadVideos();
this.showSuccess('Video shared successfully!');
this.showSuccess("Video shared successfully!");
} catch (error) {
this.log('Failed to publish video:', error.message);
this.showError('Failed to share video. Please try again later.');
this.log("Failed to publish video:", error.message);
this.showError("Failed to share video. Please try again later.");
}
}
@@ -318,11 +326,11 @@ class bitvidApp {
async loadVideos() {
try {
const videos = await nostrClient.fetchVideos();
this.log('Fetched videos (raw):', videos);
this.log("Fetched videos (raw):", videos);
if (!videos) {
this.log('No videos received');
throw new Error('No videos received from relays');
this.log("No videos received");
throw new Error("No videos received from relays");
}
// Convert to array if not already
@@ -331,17 +339,17 @@ class bitvidApp {
// **Filter** so we only show:
// - isPrivate === false (public videos)
// - or isPrivate === true but pubkey === this.pubkey
const displayedVideos = videosArray.filter(video => {
const displayedVideos = videosArray.filter((video) => {
if (!video.isPrivate) {
// Public video => show it
return true;
}
// Else it's private; only show if it's owned by the logged-in user
return (this.pubkey && video.pubkey === this.pubkey);
return this.pubkey && video.pubkey === this.pubkey;
});
if (displayedVideos.length === 0) {
this.log('No valid videos found after filtering.');
this.log("No valid videos found after filtering.");
this.videoList.innerHTML = `
<p class="text-center text-gray-500">
No public videos available yet. Be the first to upload one!
@@ -349,7 +357,7 @@ class bitvidApp {
return;
}
this.log('Processing filtered videos:', displayedVideos);
this.log("Processing filtered videos:", displayedVideos);
displayedVideos.forEach((video, index) => {
this.log(`Video ${index} details:`, {
@@ -358,7 +366,7 @@ class bitvidApp {
magnet: video.magnet,
isPrivate: video.isPrivate,
pubkey: video.pubkey,
created_at: video.created_at
created_at: video.created_at,
});
});
@@ -366,8 +374,10 @@ class bitvidApp {
this.renderVideoList(displayedVideos);
this.log(`Rendered ${displayedVideos.length} videos successfully`);
} catch (error) {
this.log('Failed to fetch videos:', error);
this.showError('An error occurred while loading videos. Please try again later.');
this.log("Failed to fetch videos:", error);
this.showError(
"An error occurred while loading videos. Please try again later."
);
this.videoList.innerHTML = `
<p class="text-center text-gray-500">
No videos available at the moment. Please try again later.
@@ -381,14 +391,14 @@ class bitvidApp {
*/
async renderVideoList(videos) {
try {
console.log('RENDER VIDEO LIST - Start', {
console.log("RENDER VIDEO LIST - Start", {
videosReceived: videos,
videosCount: videos ? videos.length : 'N/A',
videosType: typeof videos
videosCount: videos ? videos.length : "N/A",
videosType: typeof videos,
});
if (!videos) {
console.error('NO VIDEOS RECEIVED');
console.error("NO VIDEOS RECEIVED");
this.videoList.innerHTML = `<p class="text-center text-gray-500">No videos found.</p>`;
return;
}
@@ -396,7 +406,7 @@ class bitvidApp {
const videoArray = Array.isArray(videos) ? videos : [videos];
if (videoArray.length === 0) {
console.error('VIDEO ARRAY IS EMPTY');
console.error("VIDEO ARRAY IS EMPTY");
this.videoList.innerHTML = `<p class="text-center text-gray-500">No videos available.</p>`;
return;
}
@@ -406,58 +416,62 @@ class bitvidApp {
// Prepare to fetch user profiles
const userProfiles = new Map();
const uniquePubkeys = [...new Set(videoArray.map(v => v.pubkey))];
const uniquePubkeys = [...new Set(videoArray.map((v) => v.pubkey))];
for (const pubkey of uniquePubkeys) {
try {
const userEvents = await nostrClient.pool.list(nostrClient.relays, [{
const userEvents = await nostrClient.pool.list(nostrClient.relays, [
{
kinds: [0],
authors: [pubkey],
limit: 1
}]);
limit: 1,
},
]);
if (userEvents[0]?.content) {
const profile = JSON.parse(userEvents[0].content);
userProfiles.set(pubkey, {
name: profile.name || profile.display_name || 'Unknown',
picture: profile.picture || `https://robohash.org/${pubkey}`
name: profile.name || profile.display_name || "Unknown",
picture: profile.picture || `https://robohash.org/${pubkey}`,
});
} else {
userProfiles.set(pubkey, {
name: 'Unknown',
picture: `https://robohash.org/${pubkey}`
name: "Unknown",
picture: `https://robohash.org/${pubkey}`,
});
}
} catch (error) {
console.error(`Profile fetch error for ${pubkey}:`, error);
userProfiles.set(pubkey, {
name: 'Unknown',
picture: `https://robohash.org/${pubkey}`
name: "Unknown",
picture: `https://robohash.org/${pubkey}`,
});
}
}
// Build HTML for each video
const renderedVideos = videoArray.map((video, index) => {
const renderedVideos = videoArray
.map((video, index) => {
try {
if (!this.validateVideo(video, index)) {
console.error(`Invalid video: ${video.title}`);
return '';
return "";
}
const profile = userProfiles.get(video.pubkey) || {
name: 'Unknown',
picture: `https://robohash.org/${video.pubkey}`
name: "Unknown",
picture: `https://robohash.org/${video.pubkey}`,
};
const timeAgo = this.formatTimeAgo(video.created_at);
// If user is the owner
const canEdit = (video.pubkey === this.pubkey);
const canEdit = video.pubkey === this.pubkey;
// If it's private + user owns it => highlight with a special border
const highlightClass = (video.isPrivate && canEdit)
? 'border-2 border-yellow-500'
: 'border-none'; // normal case
const highlightClass =
video.isPrivate && canEdit
? "border-2 border-yellow-500"
: "border-none"; // normal case
// Gear menu (unchanged)
const gearMenu = canEdit
@@ -496,7 +510,7 @@ class bitvidApp {
</div>
</div>
`
: '';
: "";
return `
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 ${highlightClass}">
@@ -504,7 +518,9 @@ class bitvidApp {
<!-- VIDEO THUMBNAIL -->
<div
class="aspect-w-16 aspect-h-9 bg-gray-800 cursor-pointer relative group"
onclick="app.playVideo('${encodeURIComponent(video.magnet)}')"
onclick="app.playVideo('${encodeURIComponent(
video.magnet
)}')"
>
${
video.thumbnail
@@ -530,7 +546,9 @@ class bitvidApp {
<!-- TITLE -->
<h3
class="text-lg font-bold text-white line-clamp-2 hover:text-blue-400 cursor-pointer mb-3"
onclick="app.playVideo('${encodeURIComponent(video.magnet)}')"
onclick="app.playVideo('${encodeURIComponent(
video.magnet
)}')"
>
${this.escapeHTML(video.title)}
</h3>
@@ -541,7 +559,9 @@ class bitvidApp {
<div class="flex items-center space-x-3">
<div class="w-8 h-8 rounded-full bg-gray-700 overflow-hidden">
<img
src="${this.escapeHTML(profile.picture)}"
src="${this.escapeHTML(
profile.picture
)}"
alt="${profile.name}"
class="w-full h-full object-cover"
>
@@ -563,22 +583,22 @@ class bitvidApp {
`;
} catch (error) {
console.error(`Error processing video ${index}:`, error);
return '';
return "";
}
}).filter(html => html.length > 0);
})
.filter((html) => html.length > 0);
console.log('Rendered videos:', renderedVideos.length);
console.log("Rendered videos:", renderedVideos.length);
if (renderedVideos.length === 0) {
this.videoList.innerHTML = `<p class="text-center text-gray-500">No valid videos to display.</p>`;
return;
}
this.videoList.innerHTML = renderedVideos.join('');
console.log('Videos rendered successfully');
this.videoList.innerHTML = renderedVideos.join("");
console.log("Videos rendered successfully");
} catch (error) {
console.error('Rendering error:', error);
console.error("Rendering error:", error);
this.videoList.innerHTML = `<p class="text-center text-gray-500">Error loading videos.</p>`;
}
}
@@ -589,19 +609,25 @@ class bitvidApp {
validateVideo(video, index) {
const validationResults = {
hasId: Boolean(video?.id),
isValidId: typeof video?.id === 'string' && video.id.trim().length > 0,
isValidId: typeof video?.id === "string" && video.id.trim().length > 0,
hasVideo: Boolean(video),
hasTitle: Boolean(video?.title),
hasMagnet: Boolean(video?.magnet),
hasMode: Boolean(video?.mode),
hasPubkey: Boolean(video?.pubkey),
isValidTitle: typeof video?.title === 'string' && video.title.length > 0,
isValidMagnet: typeof video?.magnet === 'string' && video.magnet.length > 0,
isValidMode: typeof video?.mode === 'string' && ['dev', 'live'].includes(video.mode)
isValidTitle: typeof video?.title === "string" && video.title.length > 0,
isValidMagnet:
typeof video?.magnet === "string" && video.magnet.length > 0,
isValidMode:
typeof video?.mode === "string" && ["dev", "live"].includes(video.mode),
};
const passed = Object.values(validationResults).every(Boolean);
console.log(`Video ${video?.title} validation results:`, validationResults, passed ? 'PASSED' : 'FAILED');
console.log(
`Video ${video?.title} validation results:`,
validationResults,
passed ? "PASSED" : "FAILED"
);
return passed;
}
@@ -610,14 +636,14 @@ class bitvidApp {
* Gets a user-friendly error message.
*/
getErrorMessage(error) {
if (error.message.includes('404')) {
return 'Service worker not found. Please check server configuration.';
} else if (error.message.includes('Brave')) {
return 'Please disable Brave Shields for this site to play videos.';
} else if (error.message.includes('timeout')) {
return 'Connection timeout. Please check your internet connection.';
if (error.message.includes("404")) {
return "Service worker not found. Please check server configuration.";
} else if (error.message.includes("Brave")) {
return "Please disable Brave Shields for this site to play videos.";
} else if (error.message.includes("timeout")) {
return "Connection timeout. Please check your internet connection.";
} else {
return 'Failed to play video. Please try again.';
return "Failed to play video. Please try again.";
}
}
@@ -627,10 +653,10 @@ class bitvidApp {
showError(message) {
if (this.errorContainer) {
this.errorContainer.textContent = message;
this.errorContainer.classList.remove('hidden');
this.errorContainer.classList.remove("hidden");
setTimeout(() => {
this.errorContainer.classList.add('hidden');
this.errorContainer.textContent = '';
this.errorContainer.classList.add("hidden");
this.errorContainer.textContent = "";
}, 5000);
} else {
alert(message);
@@ -643,10 +669,10 @@ class bitvidApp {
showSuccess(message) {
if (this.successContainer) {
this.successContainer.textContent = message;
this.successContainer.classList.remove('hidden');
this.successContainer.classList.remove("hidden");
setTimeout(() => {
this.successContainer.classList.add('hidden');
this.successContainer.textContent = '';
this.successContainer.classList.add("hidden");
this.successContainer.textContent = "";
}, 5000);
} else {
alert(message);
@@ -679,33 +705,37 @@ class bitvidApp {
async playVideo(magnetURI) {
try {
if (!magnetURI) {
this.showError('Invalid Magnet URI.');
this.showError("Invalid Magnet URI.");
return;
}
const decodedMagnet = decodeURIComponent(magnetURI);
if (this.currentMagnetUri === decodedMagnet) {
this.log('Same video requested - already playing');
this.log("Same video requested - already playing");
return;
}
this.currentMagnetUri = decodedMagnet;
this.playerModal.style.display = 'flex';
this.playerModal.classList.remove('hidden');
this.playerModal.style.display = "flex";
this.playerModal.classList.remove("hidden");
// Re-fetch the latest from relays
const videos = await nostrClient.fetchVideos();
const video = videos.find(v => v.magnet === decodedMagnet);
const video = videos.find((v) => v.magnet === decodedMagnet);
if (!video) {
this.showError('Video data not found.');
this.showError("Video data not found.");
return;
}
// Decrypt only once if user owns it
if (video.isPrivate && video.pubkey === this.pubkey && !video.alreadyDecrypted) {
this.log('User owns a private video => decrypting magnet link...');
if (
video.isPrivate &&
video.pubkey === this.pubkey &&
!video.alreadyDecrypted
) {
this.log("User owns a private video => decrypting magnet link...");
video.magnet = fakeDecrypt(video.magnet);
// Mark it so we don't do it again
video.alreadyDecrypted = true;
@@ -714,44 +744,53 @@ class bitvidApp {
const finalMagnet = video.magnet;
// Profile fetch
let creatorProfile = { name: 'Unknown', picture: `https://robohash.org/${video.pubkey}` };
let creatorProfile = {
name: "Unknown",
picture: `https://robohash.org/${video.pubkey}`,
};
try {
const userEvents = await nostrClient.pool.list(nostrClient.relays, [{
const userEvents = await nostrClient.pool.list(nostrClient.relays, [
{
kinds: [0],
authors: [video.pubkey],
limit: 1
}]);
limit: 1,
},
]);
// Ensure userEvents isn't empty before accessing [0]
if (userEvents.length > 0 && userEvents[0]?.content) {
const profile = JSON.parse(userEvents[0].content);
creatorProfile = {
name: profile.name || profile.display_name || 'Unknown',
picture: profile.picture || `https://robohash.org/${video.pubkey}`
name: profile.name || profile.display_name || "Unknown",
picture: profile.picture || `https://robohash.org/${video.pubkey}`,
};
}
} catch (error) {
this.log('Error fetching creator profile:', error);
this.log("Error fetching creator profile:", error);
}
let creatorNpub = 'Unknown';
let creatorNpub = "Unknown";
try {
creatorNpub = window.NostrTools.nip19.npubEncode(video.pubkey);
} catch (error) {
this.log('Error converting pubkey to npub:', error);
this.log("Error converting pubkey to npub:", error);
creatorNpub = video.pubkey;
}
this.videoTitle.textContent = video.title || 'Untitled';
this.videoDescription.textContent = video.description || 'No description available.';
this.videoTitle.textContent = video.title || "Untitled";
this.videoDescription.textContent =
video.description || "No description available.";
this.videoTimestamp.textContent = this.formatTimeAgo(video.created_at);
this.creatorName.textContent = creatorProfile.name;
this.creatorNpub.textContent = `${creatorNpub.slice(0, 8)}...${creatorNpub.slice(-4)}`;
this.creatorNpub.textContent = `${creatorNpub.slice(
0,
8
)}...${creatorNpub.slice(-4)}`;
this.creatorAvatar.src = creatorProfile.picture;
this.creatorAvatar.alt = creatorProfile.name;
this.log('Starting video stream with:', finalMagnet);
this.log("Starting video stream with:", finalMagnet);
await torrentClient.streamVideo(finalMagnet, this.modalVideo);
const updateInterval = setInterval(() => {
@@ -760,21 +799,21 @@ class bitvidApp {
return;
}
const status = document.getElementById('status');
const progress = document.getElementById('progress');
const peers = document.getElementById('peers');
const speed = document.getElementById('speed');
const downloaded = document.getElementById('downloaded');
const status = document.getElementById("status");
const progress = document.getElementById("progress");
const peers = document.getElementById("peers");
const speed = document.getElementById("speed");
const downloaded = document.getElementById("downloaded");
if (status) this.modalStatus.textContent = status.textContent;
if (progress) this.modalProgress.style.width = progress.style.width;
if (peers) this.modalPeers.textContent = peers.textContent;
if (speed) this.modalSpeed.textContent = speed.textContent;
if (downloaded) this.modalDownloaded.textContent = downloaded.textContent;
if (downloaded)
this.modalDownloaded.textContent = downloaded.textContent;
}, 1000);
} catch (error) {
this.log('Error in playVideo:', error);
this.log("Error in playVideo:", error);
this.showError(`Playback error: ${error.message}`);
}
}
@@ -785,11 +824,16 @@ class bitvidApp {
this.modalStatus.textContent = torrent.status;
this.modalProgress.style.width = `${(torrent.progress * 100).toFixed(2)}%`;
this.modalPeers.textContent = `Peers: ${torrent.numPeers}`;
this.modalSpeed.textContent = `${(torrent.downloadSpeed / 1024).toFixed(2)} KB/s`;
this.modalDownloaded.textContent = `${(torrent.downloaded / (1024 * 1024)).toFixed(2)} MB / ${(torrent.length / (1024 * 1024)).toFixed(2)} MB`;
this.modalSpeed.textContent = `${(torrent.downloadSpeed / 1024).toFixed(
2
)} KB/s`;
this.modalDownloaded.textContent = `${(
torrent.downloaded /
(1024 * 1024)
).toFixed(2)} MB / ${(torrent.length / (1024 * 1024)).toFixed(2)} MB`;
if (torrent.ready) {
this.modalStatus.textContent = 'Ready to play';
this.modalStatus.textContent = "Ready to play";
} else {
setTimeout(() => this.updateTorrentStatus(torrent), 1000);
}
@@ -805,28 +849,52 @@ class bitvidApp {
const video = videos[index];
if (!this.pubkey) {
this.showError('Please login to edit videos.');
this.showError("Please login to edit videos.");
return;
}
if (video.pubkey !== this.pubkey) {
this.showError('You do not own this video.');
this.showError("You do not own this video.");
return;
}
// Prompt for new fields or keep old
const newTitle = prompt('New Title? (Leave blank to keep existing)', video.title);
const newMagnet = prompt('New Magnet? (Leave blank to keep existing)', video.magnet);
const newThumbnail = prompt('New Thumbnail? (Leave blank to keep existing)', video.thumbnail);
const newDescription = prompt('New Description? (Leave blank to keep existing)', video.description);
const newTitle = prompt(
"New Title? (Leave blank to keep existing)",
video.title
);
const newMagnet = prompt(
"New Magnet? (Leave blank to keep existing)",
video.magnet
);
const newThumbnail = prompt(
"New Thumbnail? (Leave blank to keep existing)",
video.thumbnail
);
const newDescription = prompt(
"New Description? (Leave blank to keep existing)",
video.description
);
// Ask user if they want the note private or public
const wantPrivate = confirm('Make this video private? OK=Yes, Cancel=No');
const wantPrivate = confirm("Make this video private? OK=Yes, Cancel=No");
// Fallback to old if user typed nothing
const title = (newTitle === null || newTitle.trim() === '') ? video.title : newTitle.trim();
const magnet = (newMagnet === null || newMagnet.trim() === '') ? video.magnet : newMagnet.trim();
const thumbnail = (newThumbnail === null || newThumbnail.trim() === '') ? video.thumbnail : newThumbnail.trim();
const description = (newDescription === null || newDescription.trim() === '') ? video.description : newDescription.trim();
const title =
newTitle === null || newTitle.trim() === ""
? video.title
: newTitle.trim();
const magnet =
newMagnet === null || newMagnet.trim() === ""
? video.magnet
: newMagnet.trim();
const thumbnail =
newThumbnail === null || newThumbnail.trim() === ""
? video.thumbnail
: newThumbnail.trim();
const description =
newDescription === null || newDescription.trim() === ""
? video.description
: newDescription.trim();
// Build final updated data
const updatedData = {
@@ -836,21 +904,21 @@ class bitvidApp {
magnet,
thumbnail,
description,
mode: isDevMode ? 'dev' : 'live'
mode: isDevMode ? "dev" : "live",
};
// Edit
const originalEvent = {
id: video.id,
pubkey: video.pubkey,
tags: video.tags
tags: video.tags,
};
await nostrClient.editVideo(originalEvent, updatedData, this.pubkey);
this.showSuccess('Video updated successfully!');
this.showSuccess("Video updated successfully!");
await this.loadVideos();
} catch (err) {
this.log('Failed to edit video:', err.message);
this.showError('Failed to edit video. Please try again later.');
this.log("Failed to edit video:", err.message);
this.showError("Failed to edit video. Please try again later.");
}
}
@@ -864,30 +932,34 @@ class bitvidApp {
const video = videos[index];
if (!this.pubkey) {
this.showError('Please login to delete videos.');
this.showError("Please login to delete videos.");
return;
}
if (video.pubkey !== this.pubkey) {
this.showError('You do not own this video.');
this.showError("You do not own this video.");
return;
}
if (!confirm(`Are you sure you want to delete "${video.title}"? This action cannot be undone.`)) {
if (
!confirm(
`Are you sure you want to delete "${video.title}"? This action cannot be undone.`
)
) {
return;
}
const originalEvent = {
id: video.id,
pubkey: video.pubkey,
tags: video.tags
tags: video.tags,
};
await nostrClient.deleteVideo(originalEvent, this.pubkey);
this.showSuccess('Video deleted (hidden) successfully!');
this.showSuccess("Video deleted (hidden) successfully!");
await this.loadVideos();
} catch (err) {
this.log('Failed to delete video:', err.message);
this.showError('Failed to delete video. Please try again later.');
this.log("Failed to delete video:", err.message);
this.showError("Failed to delete video. Please try again later.");
}
}
}

29
src/js/disclaimer.js Normal file
View File

@@ -0,0 +1,29 @@
class DisclaimerModal {
constructor() {
this.modal = document.getElementById("disclaimerModal");
this.acceptButton = document.getElementById("acceptDisclaimer");
this.hasSeenDisclaimer = localStorage.getItem("hasSeenDisclaimer");
this.setupEventListeners();
}
setupEventListeners() {
const closeModal = () => {
this.modal.style.display = "none";
document.body.style.overflow = "unset";
localStorage.setItem("hasSeenDisclaimer", "true");
};
// Only keep the accept button event listener
this.acceptButton.addEventListener("click", closeModal);
}
show() {
if (!this.hasSeenDisclaimer) {
this.modal.style.display = "flex";
document.body.style.overflow = "hidden";
}
}
}
export const disclaimerModal = new DisclaimerModal();