diff --git a/public/s/js/upload.js b/public/s/js/upload.js index 89713fc..3f899e3 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -419,6 +419,28 @@ window.initUploadForm = (selector) => { updateSubmitButton(); }); + // In shitpost mode: auto-commit each pasted URL to the list immediately + urlInput.addEventListener('paste', (e) => { + if (!isShitpost) return; + e.preventDefault(); + const pasted = (e.clipboardData || window.clipboardData).getData('text'); + const lines = pasted.split(/[\n\r]+/).map(u => u.trim()).filter(u => /^https?:\/\//i.test(u)); + if (!lines.length) { + // Not a URL — let the browser insert it normally into the input + urlInput.value = pasted.trim(); + urlInput.dispatchEvent(new Event('input')); + return; + } + lines.forEach(url => { + if (!selectedFiles.some(item => item.type === 'url' && item.url === url)) { + selectedFiles.push({ type: 'url', url, rating: '', tags: [], comment: '', is_oc: false }); + } + }); + urlInput.value = ''; + if (urlBadge) urlBadge.style.display = 'none'; + handleFile(); + }); + if (urlBadge) { urlBadge.addEventListener('click', () => { const val = urlInput.value.trim(); @@ -429,30 +451,30 @@ window.initUploadForm = (selector) => { }); } + // Add URL button (shitpost mode) — commits the typed/pasted URL to the list + const addUrlFn = () => { + const val = urlInput.value.trim(); + if (!val || !/^https?:\/\//i.test(val)) return; + if (!selectedFiles.some(item => item.type === 'url' && item.url === val)) { + selectedFiles.push({ type: 'url', url: val, rating: '', tags: [], comment: '', is_oc: false }); + } + urlInput.value = ''; + if (urlBadge) urlBadge.style.display = 'none'; + handleFile(); + }; + const btnAddUrls = form.querySelector('.btn-add-urls'); if (btnAddUrls) { - btnAddUrls.addEventListener('click', () => { - const val = urlInput.value.trim(); - if (!val) return; - const lines = val.split('\n').map(u => u.trim()).filter(u => u.length > 0); - lines.forEach(url => { - if (/^https?:\/\//i.test(url)) { - if (!selectedFiles.some(item => item.type === 'url' && item.url === url)) { - selectedFiles.push({ - type: 'url', - url: url, - rating: '', - tags: [], - comment: '', - is_oc: false - }); - } - } - }); - urlInput.value = ''; - handleFile(); + btnAddUrls.addEventListener('click', addUrlFn); + } + + // Also commit on Enter key inside the URL input in shitpost mode + if (isShitpost) { + urlInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); addUrlFn(); } }); } + } const formatSize = (bytes) => { @@ -1653,7 +1675,8 @@ window.initUploadForm = (selector) => { rating: fileRating, tags: fileTags.join(','), is_oc: (isShitpost ? item.is_oc : isOc), - comment: fileComment + comment: fileComment, + is_shitpost: isShitpost ? true : undefined })); } else { xhr.send(formData); @@ -1663,6 +1686,12 @@ window.initUploadForm = (selector) => { if (res.success) { successCount++; lastData = res; + if (res.pending) { + // Background URL download — show toast using server's message + if (typeof window.flashMessage === 'function') { + window.flashMessage(res.msg, 4000, 'info'); + } + } if (res.itemid) { try { const ts = Date.now(); diff --git a/src/inc/routes/apiv2/upload.mjs b/src/inc/routes/apiv2/upload.mjs index b35e295..fa0c124 100644 --- a/src/inc/routes/apiv2/upload.mjs +++ b/src/inc/routes/apiv2/upload.mjs @@ -202,20 +202,25 @@ export default router => { return res.json({ success: false, msg: 'URL uploads are disabled' }, 403); } - const { url: inputUrl, rating, tags: tagsRaw, comment, is_oc } = req.post || {}; + const { url: inputUrl, rating, tags: tagsRaw, comment, is_oc, is_shitpost } = req.post || {}; if (!inputUrl || !inputUrl.trim()) { return res.json({ success: false, msg: 'URL is required' }, 400); } - if (!rating || !['sfw', 'nsfw', 'nsfl'].includes(rating)) { + + // In shitpost mode rating is optional; null = no rating tag assigned + const effectiveRating = (rating && ['sfw', 'nsfw', 'nsfl'].includes(rating)) ? rating : (is_shitpost ? null : null); + + if (!is_shitpost && !effectiveRating) { return res.json({ success: false, msg: 'Rating (sfw/nsfw/nsfl) is required' }, 400); } - if (rating === 'nsfl' && !cfg.enable_nsfl) { + if (effectiveRating === 'nsfl' && !cfg.enable_nsfl) { return res.json({ success: false, msg: 'NSFL mode is currently disabled' }, 400); } const tags = tagsRaw ? tagsRaw.split(',').map(t => t.trim()).filter(t => t.length > 0 && !['sfw', 'nsfw', 'nsfl'].includes(t.toLowerCase())) : []; const minTags = getMinTags(); - if (tags.length < minTags) { + // In shitpost mode tags are optional + if (!is_shitpost && tags.length < minTags) { return res.json({ success: false, msg: `At least ${minTags} tag${minTags !== 1 ? 's' : ''} required` }, 400); } @@ -287,11 +292,13 @@ export default router => { await queue.spawn('magick', ['-size', '128x128', 'xc:#1a1a1a', '-gravity', 'center', '-fill', '#666', '-pointsize', '20', '-annotate', '0', 'YouTube', path.join(tDir, `${itemid}.webp`)]).catch(() => {}); } - // Assign rating tag - const ratingTagId = rating === 'sfw' ? 1 : (rating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3)); - await db`insert into tags_assign ${db({ item_id: itemid, tag_id: ratingTagId, user_id: req.session.id })} on conflict do nothing`; - if (rating === 'nsfw' || rating === 'nsfl') { - await queue.genBlurredThumbnail(itemid, isApprovalRequired).catch(() => {}); + // Assign rating tag (only if a rating was selected) + if (effectiveRating) { + const ratingTagId = effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3)); + await db`insert into tags_assign ${db({ item_id: itemid, tag_id: ratingTagId, user_id: req.session.id })} on conflict do nothing`; + if (effectiveRating === 'nsfw' || effectiveRating === 'nsfl') { + await queue.genBlurredThumbnail(itemid, isApprovalRequired).catch(() => {}); + } } // Assign user tags + auto-tags @@ -563,14 +570,17 @@ export default router => { try { await queue.genThumbnail(filename, mime, itemid, url, isApprovalRequired); - if (rating === 'nsfw' || rating === 'nsfl') await queue.genBlurredThumbnail(itemid, isApprovalRequired); + if (effectiveRating === 'nsfw' || effectiveRating === 'nsfl') await queue.genBlurredThumbnail(itemid, isApprovalRequired); } catch (err) { const tDir = isApprovalRequired ? path.join(cfg.paths.pending, 't') : cfg.paths.t; await queue.spawn('magick', ['-size', '128x128', 'xc:#1a1a1a', path.join(tDir, `${itemid}.webp`)]).catch(() => {}); } - const ratingTagId = rating === 'sfw' ? 1 : (rating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3)); - await db`insert into tags_assign ${db({ item_id: itemid, tag_id: ratingTagId, user_id: session.id })}`; + // Assign rating tag (only if a rating was selected) + if (effectiveRating) { + const ratingTagId = effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3)); + await db`insert into tags_assign ${db({ item_id: itemid, tag_id: ratingTagId, user_id: session.id })}`; + } const autoTags = autoTagsFromUrl(url); const allTags = [...new Set([...tags, ...autoTags])]; for (const tagName of allTags) { @@ -594,7 +604,7 @@ export default router => { mime: mime, username: session.user, display_name: session.display_name || null, - tag_id: rating === 'sfw' ? 1 : (rating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3)), + tag_id: effectiveRating ? (effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3))) : 0, is_oc: !!is_oc })})`; } catch (err) { diff --git a/views/snippets/upload-form.html b/views/snippets/upload-form.html index 43f3fa6..55616ef 100644 --- a/views/snippets/upload-form.html +++ b/views/snippets/upload-form.html @@ -40,10 +40,10 @@