mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
bitvid authors kind-30078 and MIRRORS to NIP-71; it never authors NIP-71 directly. Verified before removing: the My Videos mirror (buildNip71MirrorEvent) derives everything from the bitvid note itself (duration/dims are auto-captured at upload, contentWarning comes from the NSFW toggle, imeta is built from the video), and the only consumer of the form's NIP-71 metadata (publishNip71Video) is gated behind FEATURE_PUBLISH_NIP71: false — the fields fed a dead path and confused uploaders. Removed from the upload modal: "NIP-71 Technical" (content warning, duration, summary + Customize toggle) and the IMETA variant editor. Removed from the edit modal: kind (21/22) selector, published_at, alt, duration, content warning, summary, IMETA, text tracks, segments, participants, references. KEPT in both: hashtags (they feed hashtag feeds, the suggestion chips, and the mirror's t tags) and the web seeds / torrent (xs) inputs (bitvid-note fields, not NIP-71). All the JS plumbing is missing-DOM tolerant, so no wiring changes were needed to collect. Compatibility: existing videos' NIP-71 extras survive a save. EditModal keeps the video's full stored metadata (storedNip71Metadata) and merges the edited hashtags into it on submit instead of replacing it with the reduced form's empty output (t mirror key kept in sync). Also fixes a latent pre-existing bug the reduction exposed: nip71Edited compared the submit-side copy AFTER publishedAt normalization (string→timestamp, delete-empty) against an UN-normalized original, so any video with publishedAt made every save look "edited". Both sides now share normalizeCollectedNip71. Tests: +2 in edit-modal-submit-state (stored extras survive a hashtag edit; untouched save keeps nip71Edited false). Modal/payload/mirror suites green (39 + 7). Lint + build green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
366 lines
19 KiB
HTML
366 lines
19 KiB
HTML
<!-- components/upload-modal.html -->
|
|
<div
|
|
id="uploadModal"
|
|
class="bv-modal hidden items-start justify-center md:items-center"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="uploadModalTitle"
|
|
aria-describedby="uploadModalDescription"
|
|
data-ds="new"
|
|
data-testid="upload-modal"
|
|
>
|
|
<div class="bv-modal-backdrop" data-dismiss></div>
|
|
|
|
<!-- Modal Container -->
|
|
<div class="modal-sheet max-h-modal-shell overflow-hidden flex flex-col w-full max-w-2xl" tabindex="-1">
|
|
<!-- Top Bar -->
|
|
<div class="modal-header transition-transform duration-150">
|
|
<h2 id="uploadModalTitle" class="text-xl font-bold text-text">
|
|
Share a Video
|
|
</h2>
|
|
<button
|
|
id="closeUploadModal"
|
|
class="modal-close"
|
|
type="button"
|
|
aria-label="Close upload modal"
|
|
>
|
|
<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>
|
|
</div>
|
|
|
|
<!-- Main Scrollable Body -->
|
|
<div class="modal-body space-y-6 overflow-y-auto flex-1 min-h-0 custom-scrollbar p-6">
|
|
|
|
<!-- Source Selector -->
|
|
<div class="flex justify-center mb-2">
|
|
<div
|
|
class="inline-flex items-center rounded-full bg-surface-alt p-1 text-sm shadow-inner"
|
|
role="group"
|
|
aria-label="Upload source selector"
|
|
>
|
|
<button
|
|
type="button"
|
|
id="btn-mode-upload"
|
|
class="rounded-full px-6 py-1.5 font-medium transition-colors text-text bg-surface shadow-sm"
|
|
aria-pressed="true"
|
|
>
|
|
Upload File
|
|
</button>
|
|
<button
|
|
type="button"
|
|
id="btn-mode-external"
|
|
class="rounded-full px-6 py-1.5 font-medium transition-colors text-muted hover:text-text"
|
|
aria-pressed="false"
|
|
>
|
|
External Link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="unifiedUploadForm" class="space-y-6">
|
|
|
|
<!-- Source: Upload File -->
|
|
<div id="section-source-upload" class="space-y-4">
|
|
<div class="relative">
|
|
<label
|
|
for="input-file"
|
|
class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer bg-surface/30 border-border hover:bg-surface/50 transition-colors"
|
|
>
|
|
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
|
<svg class="w-8 h-8 mb-3 text-muted" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
|
</svg>
|
|
<p class="mb-2 text-sm text-text"><span class="font-semibold">Click to upload</span> or drag and drop</p>
|
|
<p class="text-xs text-muted">MP4, WebM, MKV, MOV (Max 2GB recommended)</p>
|
|
</div>
|
|
<input id="input-file" type="file" class="hidden" accept="video/*,.m3u8,.ts,.mp4,.webm,.mov,.mkv,.mpg,.mpeg" />
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Upload Progress -->
|
|
<div id="upload-progress-container" class="space-y-2 hidden">
|
|
<div class="flex justify-between text-xs">
|
|
<span id="upload-status-text" class="text-text font-medium">Uploading...</span>
|
|
<span id="upload-percent-text" class="text-muted">0%</span>
|
|
</div>
|
|
<progress
|
|
id="input-progress"
|
|
class="progress progress--surface w-full"
|
|
max="100"
|
|
value="0"
|
|
></progress>
|
|
</div>
|
|
|
|
<!-- Generated Links (Results) -->
|
|
<div id="upload-results-container" class="space-y-3 bg-surface/30 p-3 rounded-lg border border-border/50 hidden">
|
|
<h4 class="text-xs font-semibold text-text uppercase tracking-wider">Generated Links</h4>
|
|
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-muted">Video URL</label>
|
|
<input id="result-video-url" type="text" readonly class="input input-sm text-xs bg-surface-alt font-mono text-muted" placeholder="Pending..." />
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-muted">Magnet URI</label>
|
|
<input id="result-magnet" type="text" readonly class="input input-sm text-xs bg-surface-alt font-mono text-muted" placeholder="Pending..." />
|
|
</div>
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-muted">Torrent URL (xs)</label>
|
|
<input id="result-torrent-url" type="text" readonly class="input input-sm text-xs bg-surface-alt font-mono text-muted" placeholder="Pending..." />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Storage Configuration Toggle -->
|
|
<div class="border-t border-border/50 pt-2">
|
|
<button
|
|
type="button"
|
|
id="btn-storage-settings"
|
|
class="flex items-center gap-2 text-xs text-muted hover:text-text transition-colors"
|
|
aria-expanded="false"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
<span>Storage Status</span>
|
|
</button>
|
|
|
|
<!-- Storage Settings Container -->
|
|
<div id="section-storage-settings" class="hidden mt-3 space-y-4 bg-surface-alt/50 p-4 rounded-lg border border-border/50">
|
|
|
|
<!-- Read-Only Summary (for configured storage) -->
|
|
<div id="storage-summary-view" class="hidden space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<h4 class="text-sm font-semibold text-text">Active Storage</h4>
|
|
<div class="flex items-center gap-2">
|
|
<span id="storage-lock-status" class="text-xs text-muted">Locked 🔒</span>
|
|
<button type="button" id="btn-storage-unlock" class="text-xs text-accent hover:underline hidden">Unlock</button>
|
|
<button type="button" id="btn-manage-storage" class="text-xs text-muted hover:text-text hover:underline">Manage</button>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-2 text-xs">
|
|
<div class="min-w-0">
|
|
<span class="block text-muted">Provider</span>
|
|
<span id="summary-provider" class="block font-medium text-text truncate" tabindex="0">--</span>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<span class="block text-muted">Bucket</span>
|
|
<span id="summary-bucket" class="block font-medium text-text truncate" tabindex="0">--</span>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<span class="block text-muted">URL Style</span>
|
|
<span id="summary-url-style" class="block font-medium text-text truncate" tabindex="0">--</span>
|
|
</div>
|
|
</div>
|
|
<p id="summary-copy" class="text-xs text-muted">
|
|
Your videos will be uploaded using this storage provider.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Empty State / Not Configured -->
|
|
<div id="storage-empty-view" class="space-y-4 text-center py-4">
|
|
<p class="text-sm text-muted">
|
|
Storage is not configured.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
id="btn-configure-storage"
|
|
class="btn btn-sm btn-secondary"
|
|
>
|
|
Configure Storage
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Source: External Link -->
|
|
<div id="section-source-external" class="space-y-4 hidden">
|
|
<div class="space-y-2">
|
|
<label for="input-url" class="block text-sm font-medium text-text">Video URL</label>
|
|
<input id="input-url" type="url" class="input" placeholder="https://example.com/video.mp4" data-testid="upload-url" />
|
|
<p class="text-xs text-muted">Primary hosted video source (URL-first playback).</p>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label for="input-magnet" class="block text-sm font-medium text-text">Magnet Link</label>
|
|
<input id="input-magnet" type="text" class="input" placeholder="magnet:?xt=urn:btih:..." data-testid="upload-magnet" />
|
|
<p class="text-xs text-muted">Optional WebTorrent fallback when the URL is unavailable.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="border-border/50" />
|
|
|
|
<!-- Metadata -->
|
|
<div class="space-y-4">
|
|
<div class="space-y-2">
|
|
<label for="input-title" class="block text-sm font-medium text-text">Title</label>
|
|
<input id="input-title" type="text" required class="input" placeholder="Give your video a title" data-testid="upload-title" />
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="input-description" class="block text-sm font-medium text-text">Description</label>
|
|
<textarea id="input-description" rows="4" class="textarea" placeholder="Tell viewers about your video"></textarea>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="input-thumbnail" class="block text-sm font-medium text-text">Thumbnail URL</label>
|
|
<div class="flex gap-2">
|
|
<input id="input-thumbnail" type="url" class="input flex-1" placeholder="https://example.com/thumbnail.jpg" />
|
|
<input id="input-thumbnail-file" type="file" class="hidden" accept="image/*" />
|
|
<button
|
|
type="button"
|
|
id="btn-thumbnail-file"
|
|
class="btn-secondary px-3 text-sm whitespace-nowrap"
|
|
aria-label="Upload thumbnail image"
|
|
>
|
|
Browse...
|
|
</button>
|
|
</div>
|
|
<!-- Thumbnail Progress -->
|
|
<div id="thumbnail-progress-container" class="space-y-1 hidden mt-2">
|
|
<div class="flex justify-between text-xs">
|
|
<span id="thumbnail-status-text" class="text-text font-medium">Uploading Thumbnail...</span>
|
|
<span id="thumbnail-percent-text" class="text-muted">0%</span>
|
|
</div>
|
|
<progress
|
|
id="thumbnail-progress"
|
|
class="progress progress--surface w-full h-1.5"
|
|
max="100"
|
|
value="0"
|
|
></progress>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hashtags -->
|
|
<div class="space-y-2">
|
|
<label class="block text-sm font-medium text-text">Hashtags</label>
|
|
<div id="hashtag-suggestions-wrap" class="space-y-1 hidden">
|
|
<p class="text-xs text-muted">Reuse a tag from your videos:</p>
|
|
<div id="hashtag-suggestions" class="flex flex-wrap gap-2"></div>
|
|
</div>
|
|
<div
|
|
class="rounded-lg border border-border/70 bg-surface/30 p-3 space-y-3"
|
|
data-nip71-repeater="t"
|
|
>
|
|
<div id="hashtag-list" class="flex flex-wrap gap-2" data-nip71-list="t"></div>
|
|
<div class="flex gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn-ghost px-3 py-1.5 text-xs bg-surface border border-border/50"
|
|
data-nip71-add="t"
|
|
>
|
|
+ Add Tag
|
|
</button>
|
|
</div>
|
|
<template data-nip71-template="t">
|
|
<div class="inline-flex items-center bg-surface-alt rounded-full px-1 border border-border/50" data-nip71-entry="t">
|
|
<span class="text-muted pl-2 text-sm">#</span>
|
|
<input
|
|
type="text"
|
|
data-nip71-field="value"
|
|
placeholder="nostr"
|
|
class="bg-transparent border-none text-sm text-text focus:ring-0 p-1 w-24"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="text-muted hover:text-critical p-1 rounded-full"
|
|
data-nip71-remove="t"
|
|
aria-label="Remove hashtag"
|
|
>
|
|
×
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Flags / Toggles -->
|
|
<div class="flex flex-wrap gap-4 py-2">
|
|
<label class="flex items-center gap-2 cursor-pointer select-none">
|
|
<input type="checkbox" id="check-comments" class="checkbox" checked />
|
|
<span class="text-sm text-text">Enable Comments</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 cursor-pointer select-none">
|
|
<input type="checkbox" id="check-nsfw" class="checkbox" />
|
|
<span class="text-sm text-text">NSFW</span>
|
|
</label>
|
|
<label class="flex items-center gap-2 cursor-pointer select-none">
|
|
<input type="checkbox" id="check-kids" class="checkbox" />
|
|
<span class="text-sm text-text">For Kids</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Advanced Options -->
|
|
<div class="border-t border-border/70 pt-4">
|
|
<button
|
|
type="button"
|
|
id="btn-advanced-toggle"
|
|
class="flex items-center gap-2 text-sm font-medium text-text hover:text-accent transition-colors w-full"
|
|
aria-expanded="false"
|
|
>
|
|
<svg
|
|
id="icon-advanced-toggle"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4 transform text-muted transition-transform duration-150"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
<span>Advanced Options</span>
|
|
</button>
|
|
|
|
<div id="section-advanced" class="hidden mt-4 space-y-6 pl-2 border-l-2 border-border/30">
|
|
|
|
<!-- The old "NIP-71 Technical" inputs (content warning, duration,
|
|
summary, IMETA variants) were removed for a cleaner UI: they fed
|
|
the disabled FEATURE_PUBLISH_NIP71 path, and everything the NIP-71
|
|
mirror needs (duration/dimensions, content warning from the NSFW
|
|
toggle, the primary imeta) is derived automatically. -->
|
|
|
|
<!-- Additional Technical Sections (Web Seeds, etc) -->
|
|
<div class="grid gap-3 md:grid-cols-2">
|
|
<div class="space-y-2">
|
|
<label class="block text-xs font-medium text-muted-strong">Web seeds (one per line)</label>
|
|
<textarea id="input-ws" rows="2" class="textarea text-sm" placeholder="https://cdn.example.com/video.mp4 https://backup.example.com/video.mp4"></textarea>
|
|
<p class="text-xs text-muted">Add a backup origin so the swarm can keep streaming if the primary is overloaded. Each must serve the exact same file.</p>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label class="block text-xs font-medium text-muted-strong">Torrent File (xs)</label>
|
|
<input id="input-xs" type="url" class="input text-sm" placeholder="https://..." />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit -->
|
|
<div class="pt-4">
|
|
<button type="submit" id="btn-submit" class="btn w-full py-3 text-base font-semibold shadow-md" data-testid="upload-submit">
|
|
Publish Video
|
|
</button>
|
|
<p id="submit-status" class="text-center text-xs text-muted mt-2"></p>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|