mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-09 15:38:44 +00:00
update
This commit is contained in:
@@ -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>
|
||||
|
@@ -1,11 +1,5 @@
|
||||

|
||||
|
||||
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
19
src/content/ipns.md
Normal 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.
|
@@ -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>
|
||||
|
154
src/index.html
154
src/index.html
@@ -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
192
src/ipns.html
Normal 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>
|
498
src/js/app.js
498
src/js/app.js
@@ -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
29
src/js/disclaimer.js
Normal 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();
|
Reference in New Issue
Block a user