diff --git a/public/s/css/upload.css b/public/s/css/upload.css index 6e70605..2f7ba49 100644 --- a/public/s/css/upload.css +++ b/public/s/css/upload.css @@ -1166,8 +1166,53 @@ user-select: text !important; } +.item-comment-container { + position: relative; + width: 100%; + margin-top: 10px; + display: flex; + flex-direction: column; +} + .item-comment-input { min-height: 60px; + width: 100%; + box-sizing: border-box; +} + +/* Actions bar sits below the textarea, left-aligned */ +.item-comment-actions { + display: flex; + align-items: center; + gap: 6px; + margin-top: 4px; +} + +.item-emoji-trigger { + background: none; + border: 1px solid rgba(255, 255, 255, 0.12); + color: rgba(255, 255, 255, 0.5); + font-size: 1rem; + cursor: pointer; + padding: 2px 7px; + line-height: 1.4; + border-radius: 4px; + transition: color 0.15s, background 0.15s, border-color 0.15s; +} + +.item-emoji-trigger:hover { + color: var(--accent); + border-color: var(--accent); + background: rgba(255, 255, 255, 0.04); +} + +/* Picker pops up above the actions row */ +.item-comment-container .item-emoji-picker, +.item-comment-container .emoji-picker { + position: relative; + bottom: calc(100% + 4px); + left: 0; + z-index: 10000; } /* Media column wrapper for URL items in shitpost mode: diff --git a/public/s/js/upload.js b/public/s/js/upload.js index 1fd804a..5e798b8 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -76,6 +76,63 @@ window.initUploadForm = (selector) => { let autoTags = []; // Track tags suggested from metadata let selectedFiles = []; // Array of files for shitpost_mode let activeMode = 'file'; // 'file' or 'url' + // Shared emoji cache for per-item pickers (fetched once, reused by all items) + let _emojiCache = null; + let _emojiCachePromise = null; + const getEmojis = () => { + if (_emojiCache) return Promise.resolve(_emojiCache); + if (_emojiCachePromise) return _emojiCachePromise; + _emojiCachePromise = fetch('/api/v2/emojis') + .then(r => r.json()) + .then(data => { + if (data.success && data.emojis) { + _emojiCache = {}; + data.emojis.forEach(e => { _emojiCache[e.name] = e.url; }); + } + return _emojiCache || {}; + }) + .catch(() => ({})); + return _emojiCachePromise; + }; + const setupItemEmojiPicker = (textarea, triggerBtn) => { + // Always use standalone picker for per-item comments + let picker = null; + triggerBtn.addEventListener('click', async (e) => { + e.preventDefault(); + e.stopPropagation(); + if (picker) { + picker.style.display = picker.style.display === 'none' ? '' : 'none'; + return; + } + const emojis = await getEmojis(); + if (!emojis || !Object.keys(emojis).length) return; + picker = document.createElement('div'); + picker.className = 'emoji-picker item-emoji-picker'; + Object.keys(emojis).forEach(name => { + const img = document.createElement('img'); + img.src = emojis[name]; + img.title = `:${name}:`; + img.loading = 'lazy'; + img.onerror = () => { img.style.display = 'none'; }; + img.onclick = (ev) => { + ev.stopPropagation(); + textarea.value += ` :${name}: `; + textarea.dispatchEvent(new Event('input')); + textarea.focus(); + }; + picker.appendChild(img); + }); + const container = triggerBtn.closest('.item-comment-container'); + container.appendChild(picker); + const closeHandler = (ev) => { + if (!picker.contains(ev.target) && ev.target !== triggerBtn) { + picker.style.display = 'none'; + document.removeEventListener('click', closeHandler); + } + }; + setTimeout(() => document.addEventListener('click', closeHandler), 0); + }); + }; // If shitpost mode is active, the global rating is hidden and per-item ratings are used. // We must remove the 'required' attribute from global radios to prevent browser from blocking submission. @@ -812,7 +869,12 @@ window.initUploadForm = (selector) => { const commentPlaceholder = window.f0ckI18n?.upload_comment_placeholder || 'Comment (optional)...'; commentUI = ` - +