feat(webseed): upload form accepts multiple web seeds (slice 2/3)

Let uploaders declare a backup origin at creation time so the swarm can keep
streaming when the primary CDN is overloaded.

- upload-modal.html: the single "Web Seed (ws)" input becomes a "Web seeds
  (one per line)" textarea with help text explaining the backup-origin use
  case and the byte-identical-file requirement.
- UploadModal: in upload mode the R2/CDN url stays the primary webseed and any
  textarea entries follow it (passed as [primaryUrl, textarea]); external mode
  already flowed the field through. All parsing stays in the payload layer.
- videoNotePayload.normalizeWebSeedList now splits every entry (array OR
  string) on newlines/commas, so the upload-modal's [url, "a\nb"] shape and a
  plain multi-line string both expand + dedupe correctly.

Mutation-verified (array entries no longer split fails the upload-shape test);
build + full unit suite green. Lints relevant to the change (inline-styles,
tokens, innerHTML) pass.

Note: npm run lint also reports pre-existing file-size violations in unrelated
files (SignerManager, watchHistory, playbackService, webtorrent, etc.) that
grew in earlier commits — none are touched here. Flagging separately.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-19 08:36:13 -04:00
1 parent cf98d06cca
commit ce7ef9ef2d
4 files changed
+44 -13

No files matched your search

+15 -10
View File
@@ -47,25 +47,30 @@ function normalizeString(value) {
function normalizeWebSeedList(value) {
const out = [];
const seen = new Set();
// Each entry may itself carry several URLs separated by newlines/commas (a
// textarea field), so split every entry regardless of whether the caller
// passed an array or a single string.
const push = (entry) => {
if (typeof entry !== "string") {
return;
}
const trimmed = entry.trim();
if (!trimmed) {
return;
for (const piece of entry.split(/[\r\n,]+/)) {
const trimmed = piece.trim();
if (!trimmed) {
continue;
}
const key = trimmed.toLowerCase();
if (seen.has(key)) {
continue;
}
seen.add(key);
out.push(trimmed);
}
const key = trimmed.toLowerCase();
if (seen.has(key)) {
return;
}
seen.add(key);
out.push(trimmed);
};
if (Array.isArray(value)) {
value.forEach(push);
} else if (typeof value === "string") {
value.split(/[\r\n,]+/).forEach(push);
push(value);
}
return out;
}