mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-03-14 06:44:19 +00:00
Merge pull request #131 from PR0M3TH3AN/codex/update-edit-video-modal-form
Add comment toggle and magnet hint editing to video forms
This commit is contained in:
@@ -94,6 +94,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoWs" class="block text-sm font-medium text-gray-200">
|
||||
Web seed (ws=)
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="url"
|
||||
id="editVideoWs"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
placeholder="https://cdn.example.com/video.mp4"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
data-edit-target="editVideoWs"
|
||||
>
|
||||
Edit field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoXs" class="block text-sm font-medium text-gray-200">
|
||||
.torrent URL (xs=)
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<input
|
||||
type="url"
|
||||
id="editVideoXs"
|
||||
class="flex-1 rounded-md border border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
placeholder="https://cdn.example.com/video.torrent"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
data-edit-target="editVideoXs"
|
||||
>
|
||||
Edit field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editVideoMagnet" class="block text-sm font-medium text-gray-200">
|
||||
Magnet link
|
||||
@@ -157,6 +199,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="editEnableComments" class="block text-sm font-medium text-gray-200">
|
||||
Enable comments
|
||||
</label>
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-md border border-gray-800 bg-black/30 p-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="editEnableComments"
|
||||
class="h-5 w-5 rounded border-gray-600 bg-gray-800 text-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
<span class="text-sm text-gray-300">
|
||||
Allow replies once comment threads are available.
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="edit-field-button hidden"
|
||||
data-edit-target="editEnableComments"
|
||||
>
|
||||
Edit field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:justify-end">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -147,106 +147,6 @@
|
||||
cost control.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<details
|
||||
id="quickR2Section"
|
||||
class="rounded-md border border-gray-800 bg-gray-900/60 p-4"
|
||||
>
|
||||
<summary class="cursor-pointer text-sm font-semibold text-gray-200">
|
||||
Optional: Upload directly to Cloudflare R2
|
||||
</summary>
|
||||
<div class="mt-3 space-y-4 text-sm text-gray-300">
|
||||
<p class="text-xs text-gray-400">
|
||||
Supply your Cloudflare credentials to upload in-browser. Keys
|
||||
stay on this device unless you opt in to remember them.
|
||||
</p>
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<label class="block text-xs font-medium text-gray-300">
|
||||
Account ID
|
||||
<input
|
||||
id="quickR2AccountId"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="023e105f4ecef8ad9ca31a8372d0c353"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-xs font-medium text-gray-300">
|
||||
S3 Access Key ID
|
||||
<input
|
||||
id="quickR2AccessKeyId"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-xs font-medium text-gray-300">
|
||||
S3 Secret Access Key
|
||||
<input
|
||||
id="quickR2SecretAccessKey"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
class="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-xs font-medium text-gray-300">
|
||||
Cloudflare API Token (R2 write)
|
||||
<input
|
||||
id="quickR2ApiToken"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
class="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Token with Workers R2 Storage Write"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-xs font-medium text-gray-300">
|
||||
Custom domain (optional)
|
||||
<input
|
||||
id="quickR2CustomDomain"
|
||||
type="text"
|
||||
class="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="media.example.com"
|
||||
/>
|
||||
</label>
|
||||
<label class="block text-xs font-medium text-gray-300">
|
||||
Zone ID (for custom domain)
|
||||
<input
|
||||
id="quickR2ZoneId"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-4 text-xs text-gray-300">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input id="quickR2Remember" type="checkbox" class="h-4 w-4 rounded border-gray-600 bg-gray-800 text-blue-500 focus:ring-blue-500" />
|
||||
Remember keys on this device
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input id="quickR2AllowManaged" type="checkbox" class="h-4 w-4 rounded border-gray-600 bg-gray-800 text-blue-500 focus:ring-blue-500" checked />
|
||||
Allow r2.dev fallback when no custom domain
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<input
|
||||
id="quickR2File"
|
||||
type="file"
|
||||
class="w-full max-w-xs rounded-md border border-dashed border-gray-600 bg-gray-900 px-3 py-2 text-xs text-gray-300 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
accept="video/*,application/vnd.apple.mpegurl,application/dash+xml"
|
||||
/>
|
||||
<button
|
||||
id="quickR2UploadButton"
|
||||
type="button"
|
||||
class="rounded-md bg-blue-500 px-4 py-2 text-xs font-semibold text-white transition hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Upload to R2
|
||||
</button>
|
||||
</div>
|
||||
<p id="quickR2Status" class="text-xs text-gray-400" role="status"></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="uploadThumbnail"
|
||||
@@ -272,6 +172,23 @@
|
||||
class="mt-1 block w-full rounded-md border-gray-700 bg-gray-800 text-gray-100 focus:border-blue-500 focus:ring-blue-500"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-3 rounded-md border border-gray-800 bg-black/30 p-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="uploadEnableComments"
|
||||
class="mt-1 h-5 w-5 rounded border-gray-600 bg-gray-800 text-blue-500 focus:ring-blue-500"
|
||||
checked
|
||||
/>
|
||||
<div>
|
||||
<label for="uploadEnableComments" class="text-sm font-medium text-gray-200"
|
||||
>Enable comments</label
|
||||
>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Keep this on to let viewers discuss the video once comment threads launch.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="flex items-center space-x-2">
|
||||
<input
|
||||
@@ -519,6 +436,22 @@
|
||||
class="w-full rounded-md border border-gray-700 bg-gray-800 px-3 py-2 text-gray-100 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 rounded-md border border-gray-800 bg-black/30 p-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="cloudflareEnableComments"
|
||||
class="mt-1 h-5 w-5 rounded border-gray-600 bg-gray-800 text-blue-500 focus:ring-blue-500"
|
||||
checked
|
||||
/>
|
||||
<div>
|
||||
<label for="cloudflareEnableComments" class="text-sm font-medium text-gray-200"
|
||||
>Enable comments</label
|
||||
>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Publish with comments switched on. Toggle off to quietly disable replies on this video.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="cloudflareFile" class="block text-sm font-medium text-gray-200"
|
||||
>Video or HLS file</label
|
||||
|
||||
198
js/app.js
198
js/app.js
@@ -6,7 +6,7 @@ import { torrentClient } from "./webtorrent.js";
|
||||
import { isDevMode } from "./config.js";
|
||||
import { isWhitelistEnabled } from "./config.js";
|
||||
import { safeDecodeMagnet } from "./magnetUtils.js";
|
||||
import { normalizeAndAugmentMagnet } from "./magnet.js";
|
||||
import { extractMagnetHints, normalizeAndAugmentMagnet } from "./magnet.js";
|
||||
import { deriveTorrentPlaybackConfig } from "./playbackUtils.js";
|
||||
import { URL_FIRST_ENABLED } from "./constants.js";
|
||||
import { trackVideoView } from "./analytics.js";
|
||||
@@ -506,6 +506,7 @@ class bitvidApp {
|
||||
this.closeUploadModalBtn =
|
||||
document.getElementById("closeUploadModal") || null;
|
||||
this.uploadForm = document.getElementById("uploadForm") || null;
|
||||
this.uploadEnableCommentsInput = null;
|
||||
this.uploadModeToggleButtons = [];
|
||||
this.customUploadSection = null;
|
||||
this.cloudflareUploadSection = null;
|
||||
@@ -527,6 +528,7 @@ class bitvidApp {
|
||||
this.cloudflareMagnetInput = null;
|
||||
this.cloudflareWsInput = null;
|
||||
this.cloudflareXsInput = null;
|
||||
this.cloudflareEnableCommentsInput = null;
|
||||
this.cloudflareAdvancedToggle = null;
|
||||
this.cloudflareAdvancedToggleLabel = null;
|
||||
this.cloudflareAdvancedToggleIcon = null;
|
||||
@@ -1186,6 +1188,8 @@ class bitvidApp {
|
||||
this.closeUploadModalBtn =
|
||||
document.getElementById("closeUploadModal") || null;
|
||||
this.uploadForm = document.getElementById("uploadForm") || null;
|
||||
this.uploadEnableCommentsInput =
|
||||
document.getElementById("uploadEnableComments") || null;
|
||||
this.uploadModeToggleButtons = Array.from(
|
||||
document.querySelectorAll(".upload-mode-toggle[data-upload-mode]")
|
||||
);
|
||||
@@ -1225,6 +1229,8 @@ class bitvidApp {
|
||||
document.getElementById("cloudflareWs") || null;
|
||||
this.cloudflareXsInput =
|
||||
document.getElementById("cloudflareXs") || null;
|
||||
this.cloudflareEnableCommentsInput =
|
||||
document.getElementById("cloudflareEnableComments") || null;
|
||||
this.cloudflareAdvancedToggle =
|
||||
document.getElementById("cloudflareAdvancedToggle") || null;
|
||||
this.cloudflareAdvancedToggleLabel =
|
||||
@@ -1407,16 +1413,24 @@ class bitvidApp {
|
||||
"editVideoTitle",
|
||||
"editVideoUrl",
|
||||
"editVideoMagnet",
|
||||
"editVideoWs",
|
||||
"editVideoXs",
|
||||
"editVideoThumbnail",
|
||||
"editVideoDescription",
|
||||
"editEnableComments",
|
||||
];
|
||||
|
||||
fieldIds.forEach((id) => {
|
||||
const input = this.editVideoModal.querySelector(`#${id}`);
|
||||
if (input) {
|
||||
input.value = "";
|
||||
input.readOnly = false;
|
||||
input.classList.remove("locked-input");
|
||||
if (input.type === "checkbox") {
|
||||
input.checked = true;
|
||||
input.disabled = false;
|
||||
} else {
|
||||
input.value = "";
|
||||
input.readOnly = false;
|
||||
input.classList.remove("locked-input");
|
||||
}
|
||||
delete input.dataset.originalValue;
|
||||
}
|
||||
const button = this.editVideoModal.querySelector(
|
||||
@@ -2225,12 +2239,31 @@ class bitvidApp {
|
||||
|
||||
this.resetEditVideoForm();
|
||||
|
||||
const magnetSource = video.magnet || video.rawMagnet || "";
|
||||
const magnetHints = extractMagnetHints(magnetSource);
|
||||
const effectiveWs = video.ws || magnetHints.ws || "";
|
||||
const effectiveXs = video.xs || magnetHints.xs || "";
|
||||
const enableCommentsValue =
|
||||
typeof video.enableComments === "boolean"
|
||||
? video.enableComments
|
||||
: true;
|
||||
|
||||
const editContext = {
|
||||
...video,
|
||||
ws: effectiveWs,
|
||||
xs: effectiveXs,
|
||||
enableComments: enableCommentsValue,
|
||||
};
|
||||
|
||||
const fieldMap = {
|
||||
editVideoTitle: video.title || "",
|
||||
editVideoUrl: video.url || "",
|
||||
editVideoMagnet: video.magnet || "",
|
||||
editVideoThumbnail: video.thumbnail || "",
|
||||
editVideoDescription: video.description || "",
|
||||
editVideoTitle: editContext.title || "",
|
||||
editVideoUrl: editContext.url || "",
|
||||
editVideoMagnet: editContext.magnet || "",
|
||||
editVideoWs: editContext.ws || "",
|
||||
editVideoXs: editContext.xs || "",
|
||||
editVideoThumbnail: editContext.thumbnail || "",
|
||||
editVideoDescription: editContext.description || "",
|
||||
editEnableComments: editContext.enableComments,
|
||||
};
|
||||
|
||||
Object.entries(fieldMap).forEach(([id, rawValue]) => {
|
||||
@@ -2238,19 +2271,47 @@ class bitvidApp {
|
||||
const button = this.editVideoModal.querySelector(
|
||||
`[data-edit-target="${id}"]`
|
||||
);
|
||||
if (!input) {
|
||||
if (button) {
|
||||
button.classList.add("hidden");
|
||||
button.dataset.mode = "locked";
|
||||
button.textContent = "Edit field";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const isCheckbox = input.type === "checkbox";
|
||||
if (isCheckbox) {
|
||||
const hasValue = rawValue !== undefined;
|
||||
const boolValue = rawValue === true;
|
||||
input.checked = boolValue;
|
||||
input.disabled = hasValue;
|
||||
input.dataset.originalValue = boolValue ? "true" : "false";
|
||||
if (button) {
|
||||
if (hasValue) {
|
||||
button.classList.remove("hidden");
|
||||
button.dataset.mode = "locked";
|
||||
button.textContent = "Edit field";
|
||||
} else {
|
||||
button.classList.add("hidden");
|
||||
button.dataset.mode = "locked";
|
||||
button.textContent = "Edit field";
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const value = typeof rawValue === "string" ? rawValue : "";
|
||||
const hasValue = value.trim().length > 0;
|
||||
|
||||
if (input) {
|
||||
input.value = value;
|
||||
input.dataset.originalValue = value;
|
||||
if (hasValue) {
|
||||
input.readOnly = true;
|
||||
input.classList.add("locked-input");
|
||||
} else {
|
||||
input.readOnly = false;
|
||||
input.classList.remove("locked-input");
|
||||
}
|
||||
input.value = value;
|
||||
input.dataset.originalValue = value;
|
||||
if (hasValue) {
|
||||
input.readOnly = true;
|
||||
input.classList.add("locked-input");
|
||||
} else {
|
||||
input.readOnly = false;
|
||||
input.classList.remove("locked-input");
|
||||
}
|
||||
|
||||
if (button) {
|
||||
@@ -2266,7 +2327,7 @@ class bitvidApp {
|
||||
}
|
||||
});
|
||||
|
||||
this.activeEditVideo = video;
|
||||
this.activeEditVideo = editContext;
|
||||
}
|
||||
|
||||
handleEditFieldToggle(event) {
|
||||
@@ -2286,12 +2347,18 @@ class bitvidApp {
|
||||
}
|
||||
|
||||
const mode = button.dataset.mode || "locked";
|
||||
const isCheckbox = input.type === "checkbox";
|
||||
|
||||
if (mode === "locked") {
|
||||
input.readOnly = false;
|
||||
input.classList.remove("locked-input");
|
||||
if (isCheckbox) {
|
||||
input.disabled = false;
|
||||
} else {
|
||||
input.readOnly = false;
|
||||
input.classList.remove("locked-input");
|
||||
}
|
||||
button.dataset.mode = "editing";
|
||||
button.textContent = "Restore original";
|
||||
if (typeof input.focus === "function") {
|
||||
if (!isCheckbox && typeof input.focus === "function") {
|
||||
input.focus();
|
||||
if (typeof input.setSelectionRange === "function") {
|
||||
const length = input.value.length;
|
||||
@@ -2306,6 +2373,15 @@ class bitvidApp {
|
||||
}
|
||||
|
||||
const originalValue = input.dataset?.originalValue || "";
|
||||
|
||||
if (isCheckbox) {
|
||||
input.checked = originalValue === "true";
|
||||
input.disabled = true;
|
||||
button.dataset.mode = "locked";
|
||||
button.textContent = "Edit field";
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = originalValue;
|
||||
|
||||
if (originalValue) {
|
||||
@@ -2461,6 +2537,10 @@ class bitvidApp {
|
||||
if (this.cloudflareFileInput) {
|
||||
this.cloudflareFileInput.disabled = Boolean(isUploading);
|
||||
}
|
||||
|
||||
if (this.cloudflareEnableCommentsInput) {
|
||||
this.cloudflareEnableCommentsInput.disabled = Boolean(isUploading);
|
||||
}
|
||||
}
|
||||
|
||||
updateCloudflareProgress(fraction) {
|
||||
@@ -2488,6 +2568,8 @@ class bitvidApp {
|
||||
if (this.cloudflareMagnetInput) this.cloudflareMagnetInput.value = "";
|
||||
if (this.cloudflareWsInput) this.cloudflareWsInput.value = "";
|
||||
if (this.cloudflareXsInput) this.cloudflareXsInput.value = "";
|
||||
if (this.cloudflareEnableCommentsInput)
|
||||
this.cloudflareEnableCommentsInput.checked = true;
|
||||
if (this.cloudflareFileInput) this.cloudflareFileInput.value = "";
|
||||
this.updateCloudflareProgress(Number.NaN);
|
||||
}
|
||||
@@ -2981,6 +3063,9 @@ class bitvidApp {
|
||||
const magnet = (this.cloudflareMagnetInput?.value || "").trim();
|
||||
const ws = (this.cloudflareWsInput?.value || "").trim();
|
||||
const xs = (this.cloudflareXsInput?.value || "").trim();
|
||||
const enableComments = this.cloudflareEnableCommentsInput
|
||||
? this.cloudflareEnableCommentsInput.checked
|
||||
: true;
|
||||
|
||||
const accountId = (this.cloudflareSettings?.accountId || "").trim();
|
||||
const accessKeyId = (this.cloudflareSettings?.accessKeyId || "").trim();
|
||||
@@ -3076,6 +3161,7 @@ class bitvidApp {
|
||||
description,
|
||||
ws,
|
||||
xs,
|
||||
enableComments,
|
||||
};
|
||||
|
||||
const published = await this.publishVideoNote(payload, {
|
||||
@@ -3536,6 +3622,12 @@ class bitvidApp {
|
||||
const description = (payload?.description || "").trim();
|
||||
const ws = (payload?.ws || "").trim();
|
||||
const xs = (payload?.xs || "").trim();
|
||||
const enableComments =
|
||||
payload?.enableComments === false
|
||||
? false
|
||||
: payload?.enableComments === true
|
||||
? true
|
||||
: true;
|
||||
|
||||
const formData = {
|
||||
version: 3,
|
||||
@@ -3545,6 +3637,7 @@ class bitvidApp {
|
||||
thumbnail,
|
||||
description,
|
||||
mode: isDevMode ? "dev" : "live",
|
||||
enableComments,
|
||||
};
|
||||
|
||||
if (!formData.title || (!formData.url && !formData.magnet)) {
|
||||
@@ -3558,10 +3651,17 @@ class bitvidApp {
|
||||
}
|
||||
|
||||
if (formData.magnet) {
|
||||
formData.magnet = normalizeAndAugmentMagnet(formData.magnet, {
|
||||
const normalizedMagnet = normalizeAndAugmentMagnet(formData.magnet, {
|
||||
ws,
|
||||
xs,
|
||||
});
|
||||
formData.magnet = normalizedMagnet;
|
||||
const hints = extractMagnetHints(normalizedMagnet);
|
||||
formData.ws = hints.ws;
|
||||
formData.xs = hints.xs;
|
||||
} else {
|
||||
formData.ws = "";
|
||||
formData.xs = "";
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -3601,14 +3701,38 @@ class bitvidApp {
|
||||
const newTitle = fieldValue("editVideoTitle");
|
||||
const newUrl = fieldValue("editVideoUrl");
|
||||
const newMagnet = fieldValue("editVideoMagnet");
|
||||
const newWs = fieldValue("editVideoWs");
|
||||
const newXs = fieldValue("editVideoXs");
|
||||
const wsInput = this.editVideoModal.querySelector("#editVideoWs");
|
||||
const xsInput = this.editVideoModal.querySelector("#editVideoXs");
|
||||
const newThumbnail = fieldValue("editVideoThumbnail");
|
||||
const newDescription = fieldValue("editVideoDescription");
|
||||
const commentsEl = this.editVideoModal.querySelector(
|
||||
"#editEnableComments"
|
||||
);
|
||||
|
||||
const finalTitle = newTitle || original.title || "";
|
||||
const finalUrl = newUrl || original.url || "";
|
||||
const finalMagnet = newMagnet || original.magnet || "";
|
||||
const shouldUseOriginalWs = wsInput ? wsInput.readOnly !== false : true;
|
||||
const shouldUseOriginalXs = xsInput ? xsInput.readOnly !== false : true;
|
||||
let finalWs = shouldUseOriginalWs ? original.ws || "" : newWs;
|
||||
let finalXs = shouldUseOriginalXs ? original.xs || "" : newXs;
|
||||
let finalMagnet = newMagnet || original.magnet || "";
|
||||
const finalThumbnail = newThumbnail || original.thumbnail || "";
|
||||
const finalDescription = newDescription || original.description || "";
|
||||
const originalEnableComments =
|
||||
typeof original.enableComments === "boolean"
|
||||
? original.enableComments
|
||||
: true;
|
||||
|
||||
let finalEnableComments = originalEnableComments;
|
||||
if (commentsEl) {
|
||||
if (commentsEl.disabled) {
|
||||
finalEnableComments = commentsEl.dataset.originalValue === "true";
|
||||
} else {
|
||||
finalEnableComments = commentsEl.checked;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalTitle || (!finalUrl && !finalMagnet)) {
|
||||
this.showError("Title and at least one of URL or Magnet is required.");
|
||||
@@ -3620,6 +3744,20 @@ class bitvidApp {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalMagnet) {
|
||||
const normalizedMagnet = normalizeAndAugmentMagnet(finalMagnet, {
|
||||
ws: finalWs,
|
||||
xs: finalXs,
|
||||
});
|
||||
finalMagnet = normalizedMagnet;
|
||||
const hints = extractMagnetHints(normalizedMagnet);
|
||||
finalWs = hints.ws;
|
||||
finalXs = hints.xs;
|
||||
} else {
|
||||
finalWs = "";
|
||||
finalXs = "";
|
||||
}
|
||||
|
||||
const updatedData = {
|
||||
version: original.version || 2,
|
||||
title: finalTitle,
|
||||
@@ -3628,6 +3766,11 @@ class bitvidApp {
|
||||
thumbnail: finalThumbnail,
|
||||
description: finalDescription,
|
||||
mode: isDevMode ? "dev" : "live",
|
||||
ws: finalWs,
|
||||
xs: finalXs,
|
||||
wsEdited: !shouldUseOriginalWs,
|
||||
xsEdited: !shouldUseOriginalXs,
|
||||
enableComments: finalEnableComments,
|
||||
};
|
||||
|
||||
const originalEvent = {
|
||||
@@ -3661,6 +3804,7 @@ class bitvidApp {
|
||||
const thumbEl = document.getElementById("uploadThumbnail");
|
||||
const descEl = document.getElementById("uploadDescription");
|
||||
const privEl = document.getElementById("uploadIsPrivate");
|
||||
const commentsEl = document.getElementById("uploadEnableComments");
|
||||
|
||||
const title = titleEl?.value.trim() || "";
|
||||
const url = urlEl?.value.trim() || "";
|
||||
@@ -3669,6 +3813,7 @@ class bitvidApp {
|
||||
const xs = xsEl?.value.trim() || "";
|
||||
const thumbnail = thumbEl?.value.trim() || "";
|
||||
const description = descEl?.value.trim() || "";
|
||||
const enableComments = commentsEl ? commentsEl.checked : true;
|
||||
|
||||
const payload = {
|
||||
title,
|
||||
@@ -3678,6 +3823,7 @@ class bitvidApp {
|
||||
description,
|
||||
ws,
|
||||
xs,
|
||||
enableComments,
|
||||
};
|
||||
|
||||
await this.publishVideoNote(payload, {
|
||||
@@ -3690,6 +3836,7 @@ class bitvidApp {
|
||||
if (thumbEl) thumbEl.value = "";
|
||||
if (descEl) descEl.value = "";
|
||||
if (privEl) privEl.checked = false;
|
||||
if (commentsEl) commentsEl.checked = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -4655,6 +4802,7 @@ class bitvidApp {
|
||||
thumbnail: typeof video.thumbnail === "string" ? video.thumbnail : "",
|
||||
url: typeof video.url === "string" ? video.url : "",
|
||||
magnet: typeof video.magnet === "string" ? video.magnet : "",
|
||||
enableComments: video.enableComments === false ? false : true,
|
||||
}));
|
||||
const signature = JSON.stringify(signaturePayload);
|
||||
|
||||
|
||||
54
js/magnet.js
54
js/magnet.js
@@ -168,3 +168,57 @@ export function normalizeAndAugmentMagnet(rawValue, { ws = "", xs = "" } = {}) {
|
||||
|
||||
return `${normalizedScheme}${queryString ? `?${queryString}` : ""}${fragment}`;
|
||||
}
|
||||
|
||||
export function extractMagnetHints(rawValue) {
|
||||
const hints = { ws: "", xs: "" };
|
||||
if (typeof rawValue !== "string") {
|
||||
return hints;
|
||||
}
|
||||
|
||||
const trimmed = rawValue.trim();
|
||||
if (!trimmed || !trimmed.toLowerCase().startsWith("magnet:")) {
|
||||
return hints;
|
||||
}
|
||||
|
||||
const queryIndex = trimmed.indexOf("?");
|
||||
if (queryIndex === -1 || queryIndex === trimmed.length - 1) {
|
||||
return hints;
|
||||
}
|
||||
|
||||
const query = trimmed.slice(queryIndex + 1);
|
||||
if (!query) {
|
||||
return hints;
|
||||
}
|
||||
|
||||
const parts = query.split("&");
|
||||
for (const part of parts) {
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
const [rawKey, rawValue = ""] = part.split("=", 2);
|
||||
if (!rawKey) {
|
||||
continue;
|
||||
}
|
||||
const lowerKey = rawKey.trim().toLowerCase();
|
||||
if (lowerKey !== "ws" && lowerKey !== "xs") {
|
||||
continue;
|
||||
}
|
||||
if (hints[lowerKey]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let decoded = rawValue;
|
||||
try {
|
||||
decoded = decodeURIComponent(rawValue);
|
||||
} catch (err) {
|
||||
// Ignore decode errors and use the raw value.
|
||||
}
|
||||
const clean = typeof decoded === "string" ? decoded.trim() : "";
|
||||
if (!clean) {
|
||||
continue;
|
||||
}
|
||||
hints[lowerKey] = clean;
|
||||
}
|
||||
|
||||
return hints;
|
||||
}
|
||||
|
||||
58
js/nostr.js
58
js/nostr.js
@@ -5,6 +5,7 @@ import { ACCEPT_LEGACY_V1 } from "./constants.js";
|
||||
import { accessControl } from "./accessControl.js";
|
||||
// 🔧 merged conflicting changes from codex/update-video-publishing-and-parsing-logic vs unstable
|
||||
import { deriveTitleFromEvent, magnetFromText } from "./videoEventUtils.js";
|
||||
import { extractMagnetHints } from "./magnet.js";
|
||||
|
||||
/**
|
||||
* The usual relays
|
||||
@@ -196,6 +197,10 @@ function convertEventToVideo(event = {}) {
|
||||
const deleted = parsedContent.deleted === true;
|
||||
const isPrivate = parsedContent.isPrivate === true;
|
||||
const videoRootId = safeTrim(parsedContent.videoRootId) || event.id;
|
||||
const wsField = safeTrim(parsedContent.ws);
|
||||
const xsField = safeTrim(parsedContent.xs);
|
||||
const enableComments =
|
||||
parsedContent.enableComments === false ? false : true;
|
||||
|
||||
let infoHash = "";
|
||||
const pushInfoHash = (candidate) => {
|
||||
@@ -282,6 +287,12 @@ function convertEventToVideo(event = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
const magnetHints = magnet
|
||||
? extractMagnetHints(magnet)
|
||||
: { ws: "", xs: "" };
|
||||
const ws = wsField || magnetHints.ws || "";
|
||||
const xs = xsField || magnetHints.xs || "";
|
||||
|
||||
return {
|
||||
id: event.id,
|
||||
videoRootId,
|
||||
@@ -296,6 +307,9 @@ function convertEventToVideo(event = {}) {
|
||||
description,
|
||||
mode,
|
||||
deleted,
|
||||
ws,
|
||||
xs,
|
||||
enableComments,
|
||||
pubkey: event.pubkey,
|
||||
created_at: event.created_at,
|
||||
tags,
|
||||
@@ -612,6 +626,13 @@ class NostrClient {
|
||||
const videoRootId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
const dTagValue = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
|
||||
const finalEnableComments =
|
||||
videoData.enableComments === false ? false : true;
|
||||
const finalWs =
|
||||
typeof videoData.ws === "string" ? videoData.ws.trim() : "";
|
||||
const finalXs =
|
||||
typeof videoData.xs === "string" ? videoData.xs.trim() : "";
|
||||
|
||||
const contentObject = {
|
||||
version: 3,
|
||||
title: finalTitle,
|
||||
@@ -623,8 +644,17 @@ class NostrClient {
|
||||
videoRootId,
|
||||
deleted: false,
|
||||
isPrivate: videoData.isPrivate ?? false,
|
||||
enableComments: finalEnableComments,
|
||||
};
|
||||
|
||||
if (finalWs) {
|
||||
contentObject.ws = finalWs;
|
||||
}
|
||||
|
||||
if (finalXs) {
|
||||
contentObject.xs = finalXs;
|
||||
}
|
||||
|
||||
const event = {
|
||||
kind: 30078,
|
||||
pubkey,
|
||||
@@ -799,6 +829,25 @@ class NostrClient {
|
||||
typeof updatedData.url === "string" ? updatedData.url.trim() : "";
|
||||
const finalUrl = newUrlValue || oldUrl;
|
||||
|
||||
const wsEdited = updatedData.wsEdited === true;
|
||||
const xsEdited = updatedData.xsEdited === true;
|
||||
const newWsValue =
|
||||
typeof updatedData.ws === "string" ? updatedData.ws.trim() : "";
|
||||
const newXsValue =
|
||||
typeof updatedData.xs === "string" ? updatedData.xs.trim() : "";
|
||||
const baseWs =
|
||||
typeof baseEvent.ws === "string" ? baseEvent.ws.trim() : "";
|
||||
const baseXs =
|
||||
typeof baseEvent.xs === "string" ? baseEvent.xs.trim() : "";
|
||||
const finalWs = wsEdited ? newWsValue : baseWs;
|
||||
const finalXs = xsEdited ? newXsValue : baseXs;
|
||||
const finalEnableComments =
|
||||
typeof updatedData.enableComments === "boolean"
|
||||
? updatedData.enableComments
|
||||
: baseEvent.enableComments === false
|
||||
? false
|
||||
: true;
|
||||
|
||||
// Use the existing videoRootId (or fall back to the base event's ID)
|
||||
const oldRootId = baseEvent.videoRootId || baseEvent.id;
|
||||
|
||||
@@ -817,8 +866,17 @@ class NostrClient {
|
||||
thumbnail: updatedData.thumbnail ?? baseEvent.thumbnail,
|
||||
description: updatedData.description ?? baseEvent.description,
|
||||
mode: updatedData.mode ?? baseEvent.mode ?? "live",
|
||||
enableComments: finalEnableComments,
|
||||
};
|
||||
|
||||
if (finalWs) {
|
||||
contentObject.ws = finalWs;
|
||||
}
|
||||
|
||||
if (finalXs) {
|
||||
contentObject.xs = finalXs;
|
||||
}
|
||||
|
||||
const event = {
|
||||
kind: 30078,
|
||||
// Use the provided userPubkey (or you can also force it to lowercase here if desired)
|
||||
|
||||
Reference in New Issue
Block a user