Files
bitvid/components/upload-modal.html
T
google-labs-jules[bot] f75531d2db chore(ai): format sources and fix moderation bugs (agent)
- Format `css/tailwind.source.css` with Prettier.
- Add allowlist entry for `VideoCard.js` media query pixel value in `scripts/check-design-tokens.mjs`.
- Remove accidental artifact `js/ui/dm/jules_session_4520373547615093905.zip`.
- Fix bug in `js/app.js` and `js/services/moderationActionController.js` where blocked users' videos were not correctly hidden in the UI immediately after blocking.
- Fix bug in `tests/app/moderation-overrides.test.mjs` where `userBlocks` mocks were insufficient to reflect state changes.
2026-01-22 14:58:35 +00:00

432 lines
24 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"
>
<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>
<!-- 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 Configuration</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 Storage</button>
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div>
<span class="block text-muted">Provider</span>
<span id="summary-provider" class="font-medium text-text">--</span>
</div>
<div>
<span class="block text-muted">Bucket</span>
<span id="summary-bucket" class="font-medium text-text">--</span>
</div>
</div>
<p class="text-xs text-muted">
Your videos will be uploaded to this bucket.
</p>
</div>
<!-- Editable Form (Legacy/Fallback) -->
<div id="storage-form-view" class="space-y-4">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-text">Storage Credentials</h4>
<span id="storage-status" class="text-xs text-muted hidden">Stored locally</span>
</div>
<div class="mb-2">
<p class="text-xs text-muted">
Required for direct file uploads.
<a href="#view=upload-content" id="link-r2-help" class="text-accent hover:underline font-medium ml-1">
Read the setup guide.
</a>
</p>
</div>
<!-- Target Bucket Display -->
<div class="space-y-1">
<label for="input-r2-bucket-name" class="text-xs font-medium text-text">Target Bucket Name (Required)</label>
<div class="flex gap-2">
<input
id="input-r2-bucket-name"
type="text"
class="input text-xs bg-surface-alt text-muted cursor-text select-all"
readonly
placeholder="Log in to see your bucket name"
/>
<button
type="button"
id="btn-copy-bucket"
class="btn-ghost px-2 text-xs border border-border/30"
aria-label="Copy bucket name"
>
Copy
</button>
</div>
<p class="text-4xs text-muted">Create a bucket with this exact name in your storage provider.</p>
</div>
<!-- Step 1: Credentials -->
<div id="step-credentials" class="space-y-3">
<div class="space-y-1">
<label for="input-r2-account" class="text-xs font-medium text-text">Account ID / Endpoint</label>
<input id="input-r2-account" type="text" class="input text-xs" placeholder="xxxxxxxx... or https://..." />
</div>
<div class="grid gap-3 md:grid-cols-2">
<div class="space-y-1">
<label for="input-r2-key" class="text-xs font-medium text-text">Access Key ID</label>
<input id="input-r2-key" type="text" class="input text-xs" placeholder="AKIA..." />
</div>
<div class="space-y-1">
<label for="input-r2-secret" class="text-xs font-medium text-text">Secret Access Key</label>
<input id="input-r2-secret" type="password" class="input text-xs" />
</div>
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" id="btn-next-step" class="btn btn-sm">Next</button>
</div>
</div>
<!-- Step 2: Verification -->
<div id="step-verification" class="hidden space-y-3 pt-2 border-t border-border/30">
<div class="space-y-1">
<label for="input-r2-domain" class="text-xs font-medium text-text">Public URL</label>
<input id="input-r2-domain" type="text" class="input text-xs" placeholder="https://pub-xxx.r2.dev" />
<p class="text-4xs text-muted leading-tight">
The public URL where your files will be accessible.
</p>
</div>
<!-- Error Container for Verification Failures -->
<div id="container-verification-error" class="hidden bg-critical/10 border border-critical/30 rounded p-3 text-xs text-critical space-y-2">
<p id="text-verification-error" class="font-medium">Verification Failed</p>
<a id="link-cloudflare-guide" href="#" target="_blank" class="block text-center bg-critical/10 hover:bg-critical/20 py-1.5 rounded font-medium transition-colors">
Check Provider Settings &rarr;
</a>
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" id="btn-back-step" class="btn-ghost px-2 text-xs">Back</button>
<button type="button" id="btn-save-settings" class="btn btn-sm">Verify & Save</button>
</div>
</div>
</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" />
<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:..." />
<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" />
</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>
</div>
<!-- Hashtags -->
<div class="space-y-2">
<label class="block text-sm font-medium text-text">Hashtags</label>
<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"
>
&times;
</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">
<!-- NIP-71 Technical -->
<div class="grid gap-4 md:grid-cols-2">
<div class="space-y-2">
<label class="block text-xs font-medium text-muted-strong">Content Warning</label>
<input id="input-content-warning" type="text" data-nip71-input="content-warning" class="input text-sm" placeholder="e.g. Nudity" />
</div>
<div class="space-y-2">
<label class="block text-xs font-medium text-muted-strong">Duration (sec)</label>
<input id="input-duration" type="number" step="1" data-nip71-input="duration" class="input text-sm" />
</div>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between">
<label class="block text-xs font-medium text-muted-strong">Summary (NIP-71 Content)</label>
<label class="flex items-center gap-2 text-xs text-muted cursor-pointer">
<input type="checkbox" id="check-summary-unlock" class="checkbox h-3 w-3" />
Customize
</label>
</div>
<textarea id="input-summary" rows="2" data-nip71-input="summary" class="textarea text-sm text-muted" readonly></textarea>
</div>
<!-- IMETA Editor -->
<div class="space-y-3" data-nip71-repeater="imeta">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-text">Video Variants (IMETA)</h4>
<button type="button" class="btn-ghost px-2 py-1 text-xs" data-nip71-add="imeta">Add Variant</button>
</div>
<div id="imeta-list" class="space-y-4" data-nip71-list="imeta"></div>
<!-- IMETA Template -->
<template data-nip71-template="imeta">
<article class="rounded-md border border-border/70 bg-surface/50 p-3 space-y-3" data-nip71-entry="imeta">
<div class="grid gap-2 md:grid-cols-2 text-xs">
<input type="text" data-nip71-field="m" placeholder="MIME (video/mp4)" class="input" />
<input type="text" data-nip71-field="dim" placeholder="Dimensions (1920x1080)" class="input" />
<input type="url" data-nip71-field="url" placeholder="URL" class="input md:col-span-2" />
<input type="text" data-nip71-field="x" placeholder="Magnet" class="input md:col-span-2" />
</div>
<div class="flex justify-end">
<button type="button" class="text-xs text-critical hover:underline" data-nip71-remove="imeta">Remove Variant</button>
</div>
</article>
</template>
</div>
<!-- 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 Seed (ws)</label>
<input id="input-ws" type="url" class="input text-sm" placeholder="https://..." />
</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">
Publish Video
</button>
<p id="submit-status" class="text-center text-xs text-muted mt-2"></p>
</div>
</form>
</div>
</div>
</div>