Validate screenshot URLs with inline hints
This commit is contained in:
@@ -63,6 +63,8 @@ const translations = {
|
||||
"form.placeholder.youtube": "YouTube URL",
|
||||
"form.placeholder.gameUrl": "Game website URL",
|
||||
"form.playersInvalid": "Players must be between 1 and 32, and min cannot exceed max.",
|
||||
"form.screenshotHint": "Use a public direct image link (http/https), max 5 MB. Avoid shortlinks/redirects.",
|
||||
"form.screenshotInvalid": "Screenshot must be a direct http/https image URL (png, jpg, jpeg, gif, webp, avif) under 5 MB and not a redirect/shortlink.",
|
||||
|
||||
"section.mySuggestions": "Your suggestions",
|
||||
"section.allSuggestions": "All suggestions",
|
||||
@@ -229,6 +231,8 @@ const translations = {
|
||||
"form.placeholder.youtube": "YouTube-URL",
|
||||
"form.placeholder.gameUrl": "Spiel-Webseite",
|
||||
"form.playersInvalid": "Spielerzahl muss zwischen 1 und 32 liegen, und Min darf Max nicht überschreiten.",
|
||||
"form.screenshotHint": "Nutze einen öffentlichen Bildlink (http/https), max. 5 MB. Keine Kurzlinks/Weiterleitungen.",
|
||||
"form.screenshotInvalid": "Screenshot muss eine direkte http/https-Bild-URL sein (png, jpg, jpeg, gif, webp, avif), unter 5 MB und ohne Weiterleitung/Kurzlink.",
|
||||
|
||||
"section.mySuggestions": "Deine Vorschläge",
|
||||
"section.allSuggestions": "Alle Vorschläge",
|
||||
|
||||
@@ -520,6 +520,8 @@ function buildSuggestionForm(initial = {}, lockTitle = false) {
|
||||
<span class="char-counter" data-for="screenshotUrl"></span>
|
||||
</span>
|
||||
<input name="screenshotUrl" maxlength="2048" />
|
||||
<p class="hint" data-i18n="form.screenshotHint">${t("form.screenshotHint")}</p>
|
||||
<div class="form-error hidden" data-error="screenshot"></div>
|
||||
</label>
|
||||
<label class="stack">
|
||||
<span class="label-row">
|
||||
@@ -618,6 +620,8 @@ function openSuggestionModal({ title, submitLabel, initial = {}, onSubmit, lockT
|
||||
const errorBox = form.querySelector('[data-error="players"]');
|
||||
const minInput = form.querySelector('input[name="minPlayers"]');
|
||||
const maxInput = form.querySelector('input[name="maxPlayers"]');
|
||||
const screenshotError = form.querySelector('[data-error="screenshot"]');
|
||||
const screenshotInput = form.querySelector('input[name="screenshotUrl"]');
|
||||
const markError = (msg) => {
|
||||
if (errorBox) {
|
||||
errorBox.textContent = msg;
|
||||
@@ -626,6 +630,7 @@ function openSuggestionModal({ title, submitLabel, initial = {}, onSubmit, lockT
|
||||
};
|
||||
const clearError = () => {
|
||||
if (errorBox) errorBox.classList.add("hidden");
|
||||
if (screenshotError) screenshotError.classList.add("hidden");
|
||||
};
|
||||
clearError();
|
||||
const min = data.minPlayers;
|
||||
@@ -643,8 +648,14 @@ function openSuggestionModal({ title, submitLabel, initial = {}, onSubmit, lockT
|
||||
return;
|
||||
}
|
||||
if (data.screenshotUrl && !isValidImageUrl(data.screenshotUrl)) {
|
||||
return toast(t("toast.invalidImageUrl"), true);
|
||||
if (screenshotError) {
|
||||
screenshotError.textContent = t("form.screenshotInvalid");
|
||||
screenshotError.classList.remove("hidden");
|
||||
}
|
||||
screenshotInput?.classList.add("input-error");
|
||||
return;
|
||||
}
|
||||
screenshotInput?.classList.remove("input-error");
|
||||
if (!data.name?.trim()) return toast(t("toast.nameRequired"), true);
|
||||
try {
|
||||
await onSubmit(data, close, submitBtn);
|
||||
@@ -976,6 +987,23 @@ function isValidImageUrl(url) {
|
||||
const u = new URL(url);
|
||||
const allowed = ["http:", "https:"];
|
||||
if (!allowed.includes(u.protocol)) return false;
|
||||
const host = u.hostname.toLowerCase();
|
||||
const bannedHosts = [
|
||||
"bit.ly",
|
||||
"tinyurl.com",
|
||||
"t.co",
|
||||
"goo.gl",
|
||||
"ow.ly",
|
||||
"is.gd",
|
||||
"buff.ly",
|
||||
"rebrand.ly",
|
||||
"steamcommunity.com",
|
||||
"store.steampowered.com",
|
||||
"imgur.com",
|
||||
];
|
||||
if (bannedHosts.some((h) => host === h)) return false;
|
||||
if (host === "imgur.com" && !u.pathname.startsWith("/a/") && !u.pathname.startsWith("/gallery/")) return false;
|
||||
if (host === "steamcommunity.com") return false;
|
||||
const path = u.pathname.toLowerCase();
|
||||
return [".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif"].some((ext) =>
|
||||
path.endsWith(ext),
|
||||
|
||||
Reference in New Issue
Block a user