attachment fix

This commit is contained in:
2026-05-27 19:12:35 +02:00
parent 514dae7906
commit 61754e058f
2 changed files with 97 additions and 12 deletions

View File

@@ -2447,6 +2447,38 @@ body.layout-legacy #comments-container.faded-out {
font-size: 14px; font-size: 14px;
} }
.cf-spoiler-btn {
background: none;
border: none;
color: #888;
cursor: pointer;
padding: 2px 4px;
font-size: 12px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 2px;
transition: color 0.15s;
}
.cf-spoiler-btn:hover,
.cf-is-spoiler .cf-spoiler-btn {
color: #f0a500;
}
.cf-is-spoiler {
border-color: rgba(240, 165, 0, 0.4);
background: rgba(240, 165, 0, 0.06);
}
.spoiler-attach-badge {
font-size: 9px;
font-weight: 800;
line-height: 1;
color: #f0a500;
letter-spacing: 0.03em;
}
.comment-file-preview { .comment-file-preview {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -2326,10 +2326,15 @@ class CommentSystem {
submitBtn.classList.add('uploading'); submitBtn.classList.add('uploading');
} }
// Insert placeholder at cursor position // Use saved cursor position (set when attach btn was clicked, before file picker
const cursorPos = textarea.selectionStart; // dismissed the keyboard on mobile causing selectionStart to become 0).
const before = textarea.value.substring(0, cursorPos); const savedPos = wrap._savedCursorPos ?? textarea.selectionStart;
const after = textarea.value.substring(textarea.selectionEnd); const savedEnd = wrap._savedCursorEnd ?? textarea.selectionEnd;
wrap._savedCursorPos = null;
wrap._savedCursorEnd = null;
const before = textarea.value.substring(0, savedPos);
const after = textarea.value.substring(savedEnd);
const placeholder = `[${uploadingText} ${file.name}]`; const placeholder = `[${uploadingText} ${file.name}]`;
const sep = before.length > 0 && !/\s$/.test(before) ? ' ' : ''; const sep = before.length > 0 && !/\s$/.test(before) ? ' ' : '';
textarea.value = before + sep + placeholder + after; textarea.value = before + sep + placeholder + after;
@@ -2358,13 +2363,14 @@ class CommentSystem {
const json = await res.json(); const json = await res.json();
if (json.success && json.files && json.files.length > 0) { if (json.success && json.files && json.files.length > 0) {
const fileData = json.files[0]; const fileData = json.files[0];
const url = `/c/${fileData.dest}${fileData.converted_gif ? '#gif' : ''}`; const rawUrl = `/c/${fileData.dest}${fileData.converted_gif ? '#gif' : ''}`;
const url = rawUrl;
textarea.value = textarea.value.replace(placeholder, url); textarea.value = textarea.value.replace(placeholder, url);
// Update preview with actual thumbnail // Update preview with actual thumbnail
if (previewItem) { if (previewItem) {
previewItem.classList.remove('cf-uploading'); previewItem.classList.remove('cf-uploading');
previewItem.dataset.url = url; previewItem.dataset.url = rawUrl;
previewItem.dataset.fileId = fileData.id; previewItem.dataset.fileId = fileData.id;
previewItem.dataset.dest = fileData.dest; previewItem.dataset.dest = fileData.dest;
previewItem.dataset.mime = fileData.mime; previewItem.dataset.mime = fileData.mime;
@@ -2373,12 +2379,12 @@ class CommentSystem {
if (fileData.mime.startsWith('image/')) { if (fileData.mime.startsWith('image/')) {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = url; img.src = rawUrl;
img.loading = 'lazy'; img.loading = 'lazy';
previewItem.appendChild(img); previewItem.appendChild(img);
} else if (fileData.mime.startsWith('video/')) { } else if (fileData.mime.startsWith('video/')) {
const vid = document.createElement('video'); const vid = document.createElement('video');
vid.src = url; vid.src = rawUrl;
vid.muted = true; vid.muted = true;
vid.preload = 'metadata'; vid.preload = 'metadata';
previewItem.appendChild(vid); previewItem.appendChild(vid);
@@ -2393,10 +2399,18 @@ class CommentSystem {
nameEl.textContent = file.name; nameEl.textContent = file.name;
previewItem.appendChild(nameEl); previewItem.appendChild(nameEl);
const spoilerBtn = document.createElement('button');
spoilerBtn.className = 'cf-spoiler-btn';
spoilerBtn.title = 'Toggle spoiler';
spoilerBtn.innerHTML = '<i class="fa-solid fa-paperclip"></i><span class="spoiler-attach-badge">S</span>';
spoilerBtn.type = 'button';
previewItem.appendChild(spoilerBtn);
const removeBtn = document.createElement('button'); const removeBtn = document.createElement('button');
removeBtn.className = 'cf-remove-btn'; removeBtn.className = 'cf-remove-btn';
removeBtn.title = removeLabel; removeBtn.title = removeLabel;
removeBtn.innerHTML = '<i class="fa-solid fa-xmark"></i>'; removeBtn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
removeBtn.type = 'button';
previewItem.appendChild(removeBtn); previewItem.appendChild(removeBtn);
} }
} else { } else {
@@ -2452,22 +2466,61 @@ class CommentSystem {
// Attach file button // Attach file button
if (target.matches('.comment-attach-btn') || target.closest('.comment-attach-btn')) { if (target.matches('.comment-attach-btn') || target.closest('.comment-attach-btn')) {
const wrap = target.closest('.comment-input'); const wrap = target.closest('.comment-input');
const textarea = wrap?.querySelector('textarea');
if (textarea) {
wrap._savedCursorPos = textarea.selectionStart;
wrap._savedCursorEnd = textarea.selectionEnd;
}
const fileInput = wrap?.querySelector('.comment-file-input'); const fileInput = wrap?.querySelector('.comment-file-input');
if (fileInput) fileInput.click(); if (fileInput) fileInput.click();
return; return;
} }
// Attach file as spoiler: toggle [spoiler] wrapping on a preview item
const spoilerToggle = target.closest('.cf-spoiler-btn');
if (spoilerToggle) {
const previewItem = spoilerToggle.closest('.cf-preview-item');
if (previewItem) {
const rawUrl = previewItem.dataset.url;
const wrap = previewItem.closest('.comment-input');
const textarea = wrap?.querySelector('textarea');
if (textarea && rawUrl) {
const escapedUrl = rawUrl.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&');
const spoilerPattern = new RegExp('\\[spoiler\\]' + escapedUrl + '\\[\/spoiler\\]', 'i');
if (spoilerPattern.test(textarea.value)) {
// Unwrap
textarea.value = textarea.value.replace(spoilerPattern, rawUrl);
previewItem.classList.remove('cf-is-spoiler');
spoilerToggle.title = 'Toggle spoiler';
} else {
// Wrap
textarea.value = textarea.value.replace(new RegExp(escapedUrl), `[spoiler]${rawUrl}[/spoiler]`);
previewItem.classList.add('cf-is-spoiler');
spoilerToggle.title = 'Remove spoiler';
}
}
}
return;
}
// Remove file preview + strip URL from textarea // Remove file preview + strip URL from textarea
if (target.matches('.cf-remove-btn') || target.closest('.cf-remove-btn')) { if (target.matches('.cf-remove-btn') || target.closest('.cf-remove-btn')) {
const previewItem = target.closest('.cf-preview-item'); const previewItem = target.closest('.cf-preview-item');
if (previewItem) { if (previewItem) {
const url = previewItem.dataset.url; const rawUrl = previewItem.dataset.url;
if (url) { if (rawUrl) {
const wrap = previewItem.closest('.comment-input'); const wrap = previewItem.closest('.comment-input');
const textarea = wrap?.querySelector('textarea'); const textarea = wrap?.querySelector('textarea');
if (textarea) { if (textarea) {
// Remove the URL and any surrounding newline // Build patterns for both plain URL and spoiler-wrapped URL
textarea.value = textarea.value.replace(new RegExp('\\n?' + url.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') + '\\n?'), '\n').replace(/^\n|\n$/g, ''); const escapedUrl = rawUrl.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&');
const spoilerPattern = new RegExp('\\n?\\[spoiler\\]' + escapedUrl + '\\[\\/spoiler\\]\\n?', 'i');
const plainPattern = new RegExp('\\n?' + escapedUrl + '\\n?');
if (spoilerPattern.test(textarea.value)) {
textarea.value = textarea.value.replace(spoilerPattern, '\n').replace(/^\n|\n$/g, '');
} else {
textarea.value = textarea.value.replace(plainPattern, '\n').replace(/^\n|\n$/g, '');
}
} }
} }
previewItem.remove(); previewItem.remove();