diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 725cf6c..4f11e80 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -2447,6 +2447,38 @@ body.layout-legacy #comments-container.faded-out { 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 { display: flex; flex-wrap: wrap; diff --git a/public/s/js/comments.js b/public/s/js/comments.js index c6c9309..b91f6be 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -2326,10 +2326,15 @@ class CommentSystem { submitBtn.classList.add('uploading'); } - // Insert placeholder at cursor position - const cursorPos = textarea.selectionStart; - const before = textarea.value.substring(0, cursorPos); - const after = textarea.value.substring(textarea.selectionEnd); + // Use saved cursor position (set when attach btn was clicked, before file picker + // dismissed the keyboard on mobile causing selectionStart to become 0). + const savedPos = wrap._savedCursorPos ?? textarea.selectionStart; + 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 sep = before.length > 0 && !/\s$/.test(before) ? ' ' : ''; textarea.value = before + sep + placeholder + after; @@ -2358,13 +2363,14 @@ class CommentSystem { const json = await res.json(); if (json.success && json.files && json.files.length > 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); // Update preview with actual thumbnail if (previewItem) { previewItem.classList.remove('cf-uploading'); - previewItem.dataset.url = url; + previewItem.dataset.url = rawUrl; previewItem.dataset.fileId = fileData.id; previewItem.dataset.dest = fileData.dest; previewItem.dataset.mime = fileData.mime; @@ -2373,12 +2379,12 @@ class CommentSystem { if (fileData.mime.startsWith('image/')) { const img = document.createElement('img'); - img.src = url; + img.src = rawUrl; img.loading = 'lazy'; previewItem.appendChild(img); } else if (fileData.mime.startsWith('video/')) { const vid = document.createElement('video'); - vid.src = url; + vid.src = rawUrl; vid.muted = true; vid.preload = 'metadata'; previewItem.appendChild(vid); @@ -2393,10 +2399,18 @@ class CommentSystem { nameEl.textContent = file.name; previewItem.appendChild(nameEl); + const spoilerBtn = document.createElement('button'); + spoilerBtn.className = 'cf-spoiler-btn'; + spoilerBtn.title = 'Toggle spoiler'; + spoilerBtn.innerHTML = 'S'; + spoilerBtn.type = 'button'; + previewItem.appendChild(spoilerBtn); + const removeBtn = document.createElement('button'); removeBtn.className = 'cf-remove-btn'; removeBtn.title = removeLabel; removeBtn.innerHTML = ''; + removeBtn.type = 'button'; previewItem.appendChild(removeBtn); } } else { @@ -2452,22 +2466,61 @@ class CommentSystem { // Attach file button if (target.matches('.comment-attach-btn') || target.closest('.comment-attach-btn')) { 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'); if (fileInput) fileInput.click(); 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 if (target.matches('.cf-remove-btn') || target.closest('.cf-remove-btn')) { const previewItem = target.closest('.cf-preview-item'); if (previewItem) { - const url = previewItem.dataset.url; - if (url) { + const rawUrl = previewItem.dataset.url; + if (rawUrl) { const wrap = previewItem.closest('.comment-input'); const textarea = wrap?.querySelector('textarea'); if (textarea) { - // Remove the URL and any surrounding newline - textarea.value = textarea.value.replace(new RegExp('\\n?' + url.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') + '\\n?'), '\n').replace(/^\n|\n$/g, ''); + // Build patterns for both plain URL and spoiler-wrapped URL + 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();