diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js
index 9af707a..1af7dd6 100644
--- a/public/s/js/f0ckm.js
+++ b/public/s/js/f0ckm.js
@@ -304,9 +304,13 @@ window.cancelAnimFrame = (function () {
}
const flash = document.createElement('div');
- flash.textContent = text;
+ if (typeof text === 'string' && text.includes(' {
const flash = document.createElement('div');
flash.className = `flash-message flash-${type}`;
- flash.textContent = message;
+ if (typeof message === 'string' && message.includes(' flash.remove();
+ flash.onclick = (e) => {
+ if (e.target.tagName.toLowerCase() !== 'a') {
+ flash.remove();
+ }
+ };
container.appendChild(flash);
diff --git a/public/s/js/upload.js b/public/s/js/upload.js
index aa958e0..64d105d 100644
--- a/public/s/js/upload.js
+++ b/public/s/js/upload.js
@@ -175,7 +175,11 @@ window.escapeHtmlUpload = window.escapeHtmlUpload || ((unsafe) => {
const errMsg = notif.data?.msg || i18n().url_tracker_failed || 'Upload failed';
const stageEl = row.querySelector('.uut-stage');
if (stageEl) {
- stageEl.textContent = errMsg;
+ if (notif.item_id) {
+ stageEl.innerHTML = `${window.escapeHtmlUpload(errMsg)} (view existing)`;
+ } else {
+ stageEl.textContent = errMsg;
+ }
stageEl.style.color = '#ff5050';
}
const spinner = row.querySelector('.uut-spinner');
@@ -250,7 +254,14 @@ window.escapeHtmlUpload = window.escapeHtmlUpload || ((unsafe) => {
if (row) {
const errMsg = n.data?.msg || i18n().url_tracker_failed || 'Upload failed';
const stageEl = row.querySelector('.uut-stage');
- if (stageEl) { stageEl.textContent = errMsg; stageEl.style.color = '#ff5050'; }
+ if (stageEl) {
+ if (n.item_id) {
+ stageEl.innerHTML = `${window.escapeHtmlUpload(errMsg)} (view existing)`;
+ } else {
+ stageEl.textContent = errMsg;
+ }
+ stageEl.style.color = '#ff5050';
+ }
const spinner = row.querySelector('.uut-spinner');
if (spinner) spinner.style.display = 'none';
const barFill = row.querySelector('.uut-bar-fill');
@@ -2425,12 +2436,18 @@ window.initUploadForm = (selector) => {
} catch(e) {}
}
} else if (!isShitpost) {
- throw new Error(data.msg || 'Upload failed');
+ const err = new Error(data.msg || 'Upload failed');
+ if (data.repost) err.repost = data.repost;
+ throw err;
}
} catch (err) {
console.error('[UPLOAD ERROR]', err);
if (!isShitpost) {
- statusDiv.textContent = '✕ ' + err.message;
+ if (err.repost) {
+ statusDiv.innerHTML = '✕ ' + window.escapeHtmlUpload(err.message) + ` (view existing)`;
+ } else {
+ statusDiv.textContent = '✕ ' + err.message;
+ }
statusDiv.className = 'upload-status error';
restoreBtn();
return;
@@ -2596,29 +2613,43 @@ window.initUploadForm = (selector) => {
const errMsg = res.msg || 'Upload failed';
if (isShitpost) {
// In shitpost mode there's no persistent statusDiv — use flash
+ let flashMsg = `✕ ${errMsg}`;
+ if (res.repost) {
+ flashMsg += ` (view existing)`;
+ }
if (typeof window.flashMessage === 'function') {
- window.flashMessage(`✕ ${errMsg}`, 5000, 'error');
+ window.flashMessage(flashMsg, 5000, 'error');
} else if (typeof window.showFlash === 'function') {
- window.showFlash(errMsg, 'error');
+ window.showFlash(res.repost ? errMsg + ` (view existing)` : errMsg, 'error');
}
} else {
- throw new Error(errMsg);
+ const err = new Error(errMsg);
+ if (res.repost) err.repost = res.repost;
+ throw err;
}
}
} catch (err) {
console.error('[UPLOAD ERROR]', err);
if (!isShitpost) {
- statusDiv.textContent = '✕ ' + err.message;
+ if (err.repost) {
+ statusDiv.innerHTML = '✕ ' + window.escapeHtmlUpload(err.message) + ` (view existing)`;
+ } else {
+ statusDiv.textContent = '✕ ' + err.message;
+ }
statusDiv.className = 'upload-status error';
if (progressContainer) progressContainer.style.display = 'none';
restoreBtn();
return;
} else {
- // Shitpost mode: show via flash toast
+ // Shitpost mode: show via flash toast for general exceptions
+ let flashMsg = `✕ ${err.message}`;
+ if (err.repost) {
+ flashMsg += ` (view existing)`;
+ }
if (typeof window.flashMessage === 'function') {
- window.flashMessage(`✕ ${err.message}`, 5000, 'error');
+ window.flashMessage(flashMsg, 5000, 'error');
} else if (typeof window.showFlash === 'function') {
- window.showFlash(err.message, 'error');
+ window.showFlash(err.repost ? err.message + ` (view existing)` : err.message, 'error');
}
}
}
diff --git a/src/inc/routes/apiv2/upload.mjs b/src/inc/routes/apiv2/upload.mjs
index eac1d52..ab89c8e 100644
--- a/src/inc/routes/apiv2/upload.mjs
+++ b/src/inc/routes/apiv2/upload.mjs
@@ -625,7 +625,9 @@ export default router => {
const repostSum = await queue.checkrepostsum(checksum);
if (repostSum) {
await fs.unlink(source).catch(() => {});
- await db`INSERT INTO notifications (user_id, type, reference_id, item_id, data) VALUES (${session.id}, 'upload_error', 0, ${repostSum}, ${db.json({ jobId, url, msg: 'Duplicate detected (Checksum)' })})`;
+ setProgress(jobId, { stage: 'error', done: true, error: 'This file already exists', itemId: repostSum });
+ cleanupJob(jobId);
+ await db`INSERT INTO notifications (user_id, type, reference_id, item_id, data) VALUES (${session.id}, 'upload_error', 0, ${repostSum}, ${db.json({ jobId, url, msg: 'This file already exists' })})`;
return;
}
}
@@ -637,7 +639,9 @@ export default router => {
const phashMatch = await queue.checkrepostphash(phash);
if (phashMatch) {
await fs.unlink(source).catch(() => {});
- await db`INSERT INTO notifications (user_id, type, reference_id, item_id, data) VALUES (${session.id}, 'upload_error', 0, ${phashMatch}, ${db.json({ jobId, url, msg: 'Visual duplicate detected (PHash)' })})`;
+ setProgress(jobId, { stage: 'error', done: true, error: 'This file is a visual duplicate', itemId: phashMatch });
+ cleanupJob(jobId);
+ await db`INSERT INTO notifications (user_id, type, reference_id, item_id, data) VALUES (${session.id}, 'upload_error', 0, ${phashMatch}, ${db.json({ jobId, url, msg: 'This file is a visual duplicate' })})`;
return;
}
}