Add thumbnail upload support to UploadModal and R2Service

- Added file input and browse button for thumbnails in `upload-modal.html`.
- Updated `UploadModal.js` to handle thumbnail file selection and UI toggling.
- Updated `r2Service.js` to upload thumbnail file (if present) to R2 and update metadata.
- Ensured thumbnail keys are derived from video keys for consistent naming.
This commit is contained in:
google-labs-jules[bot] committed 2026-01-09 03:03:35 +00:00
1 parent 77fba07fc3
commit 44aed00b5e
3 files changed
+76 -1

No files matched your search

+12 -1
View File
@@ -199,7 +199,18 @@
<div class="space-y-2">
<label for="input-thumbnail" class="block text-sm font-medium text-text">Thumbnail URL</label>
<input id="input-thumbnail" type="url" class="input" placeholder="https://example.com/thumbnail.jpg" />
<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 -->
+28
View File
@@ -636,6 +636,7 @@ class R2Service {
async handleCloudflareUploadSubmit({
npub = "",
file = null,
thumbnailFile = null,
metadata = {},
settingsInput = null,
publishVideoNote,
@@ -748,6 +749,33 @@ class R2Service {
try {
const s3 = makeR2Client({ accountId, accessKeyId, secretAccessKey });
if (thumbnailFile) {
this.setCloudflareUploadStatus("Uploading thumbnail...", "info");
const thumbExt = thumbnailFile.name.split('.').pop() || 'jpg';
const thumbKey = key.replace(/\.[^/.]+$/, "") + `.thumb.${thumbExt}`;
try {
await multipartUpload({
s3,
bucket: bucketEntry.bucket,
key: thumbKey,
file: thumbnailFile,
contentType: thumbnailFile.type || "image/jpeg",
});
const thumbUrl = buildPublicUrl(bucketEntry.publicBaseUrl, thumbKey);
if (typeof metadata === "object") {
metadata.thumbnail = thumbUrl;
}
} catch (err) {
userLogger.warn("Thumbnail upload failed, continuing with video...", err);
}
}
this.setCloudflareUploadStatus(
statusMessage,
bucketResult?.usedManagedFallback ? "warning" : "info"
);
await multipartUpload({
s3,
bucket: bucketEntry.bucket,
+36
View File
@@ -146,6 +146,7 @@ export class UploadModal {
title: $("#input-title"),
description: $("#input-description"),
thumbnail: $("#input-thumbnail"),
thumbnailFile: $("#input-thumbnail-file"),
file: $("#input-file"),
url: $("#input-url"),
magnet: $("#input-magnet"),
@@ -180,6 +181,7 @@ export class UploadModal {
storageSettings: $("#btn-storage-settings"),
r2Advanced: $("#btn-r2-advanced"),
saveSettings: $("#btn-save-settings"),
browseThumbnail: $("#btn-thumbnail-file"),
};
// Status text
@@ -217,6 +219,7 @@ export class UploadModal {
this.setupDescriptionMirror();
this.setupMutuallyExclusiveCheckboxes(this.toggles.nsfw, this.toggles.kids);
this.setupNsfwToContentWarning();
this.setupThumbnailInput();
// Close
this.closeButton.addEventListener("click", () => this.close());
@@ -329,6 +332,27 @@ export class UploadModal {
});
}
setupThumbnailInput() {
const { thumbnailFile, thumbnail } = this.inputs;
const { browseThumbnail } = this.toggles;
if (!thumbnailFile || !browseThumbnail) return;
browseThumbnail.addEventListener("click", () => thumbnailFile.click());
thumbnailFile.addEventListener("change", () => {
const file = thumbnailFile.files?.[0];
if (file) {
thumbnail.value = ""; // Clear explicit URL
thumbnail.placeholder = `Selected: ${file.name}`;
thumbnail.disabled = true;
} else {
thumbnail.placeholder = "https://example.com/thumbnail.jpg";
thumbnail.disabled = false;
}
});
}
// --- R2 Integration ---
async loadR2Settings() {
@@ -481,6 +505,8 @@ export class UploadModal {
const file = this.inputs.file?.files?.[0];
if (!file) throw new Error("Please select a video file to upload.");
const thumbnailFile = this.inputs.thumbnailFile?.files?.[0];
if (!this.hasValidR2Settings()) throw new Error("Please configure R2 storage credentials.");
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
@@ -491,6 +517,7 @@ export class UploadModal {
await this.r2Service.uploadVideo({
npub,
file,
thumbnailFile,
metadata,
settingsInput: this.collectSettingsForm(),
publishVideoNote: this.publishVideoNote,
@@ -546,6 +573,15 @@ export class UploadModal {
this.toggles.nsfw.checked = false;
this.toggles.kids.checked = false;
this.updateProgress(null);
// Reset thumbnail UI
if (this.inputs.thumbnail) {
this.inputs.thumbnail.disabled = false;
this.inputs.thumbnail.placeholder = "https://example.com/thumbnail.jpg";
}
if (this.inputs.thumbnailFile) {
this.inputs.thumbnailFile.value = "";
}
}
// --- Modal Control ---