/** * AKSRT Comment Tools * * Handles comment toolbar actions (emoji, code, bold, italic, quote, link), * Ctrl+Enter submit, and quick quote from selection. * * @package AKSRT */ (function () { 'use strict'; var COMMENT_TOOLS = { emojiPicker: null, quickReplyPanel: null, quickQuoteEnabled: false, quickReplyTemplates: [ '感谢分享,这篇内容对我很有帮助!', '已收藏,后面会按这个思路继续研究。', '这个细节讲得很清楚,学习了。', '我这边也复现成功了,补充一点经验:', '先留个脚印,等我测试后再回来反馈结果。' ], init: function () { this.bindToolbar(); this.bindCtrlEnter(); this.bindQuickQuote(); }, /** * Insert text at cursor position in textarea */ insertAtCursor: function (textarea, before, after) { if (!textarea) return; var start = textarea.selectionStart; var end = textarea.selectionEnd; var text = textarea.value; var selected = text.substring(start, end); var replacement = before + (selected || '') + (after || ''); textarea.value = text.substring(0, start) + replacement + text.substring(end); textarea.focus(); var newPos = start + before.length + selected.length; textarea.setSelectionRange(newPos, newPos); }, /** * Wrap selected text with markers */ wrapSelection: function (textarea, marker) { this.insertAtCursor(textarea, marker, marker); }, uploadImage: function (btn, textarea) { if (!window.aksrtData || !aksrtData.commentUploadEnabled || !aksrtData.commentUploadUrl) { return; } var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/jpeg,image/png,image/gif,image/webp'; input.style.display = 'none'; document.body.appendChild(input); var self = this; input.addEventListener('change', function () { var file = input.files && input.files[0]; if (!file) { input.remove(); return; } var maxSize = parseInt(aksrtData.commentUploadMaxSize || 0, 10) || (2 * 1024 * 1024); if (file.size > maxSize) { window.alert('图片大小不能超过 2MB'); input.remove(); return; } btn.disabled = true; var originalHtml = btn.innerHTML; btn.innerHTML = ''; var formData = new FormData(); formData.append('comment_image', file); fetch(aksrtData.commentUploadUrl, { method: 'POST', body: formData, credentials: 'same-origin', headers: { 'X-WP-Nonce': aksrtData.restNonce || '' } }) .then(function (response) { return response.json(); }) .then(function (payload) { if (!payload || payload.success !== true || !payload.url) { throw new Error((payload && payload.message) || '上传失败'); } self.insertAtCursor(textarea, '[img]' + payload.url + '[/img]', ''); }) .catch(function (error) { window.alert(error && error.message ? error.message : '上传失败'); }) .finally(function () { btn.disabled = false; btn.innerHTML = originalHtml; input.remove(); }); }); input.click(); }, /** * Bind toolbar button actions */ bindToolbar: function () { var self = this; document.addEventListener('click', function (e) { var btn = e.target.closest('[data-toolbar-action]'); if (!btn) return; var action = btn.getAttribute('data-toolbar-action'); var form = btn.closest('.comment-form'); var textarea = form ? form.querySelector('textarea') : document.getElementById('comment'); if (!textarea) return; switch (action) { case 'bold': self.wrapSelection(textarea, '**'); break; case 'italic': self.wrapSelection(textarea, '*'); break; case 'code': var selected = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); if (selected.indexOf('\n') >= 0) { self.insertAtCursor(textarea, '```\n', '\n```'); } else { self.wrapSelection(textarea, '`'); } break; case 'quote': self.insertAtCursor(textarea, '> ', '\n'); break; case 'link': self.askUrl(btn, '链接地址', function (url) { if (url) { var linkText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd) || '链接文字'; self.insertAtCursor(textarea, '[' + linkText + '](' + url + ')'); } }); break; case 'image': self.askUrl(btn, '图片地址', function (url) { if (url) { self.insertAtCursor(textarea, '[img]' + url + '[/img]'); } }); break; case 'upload-image': self.uploadImage(btn, textarea); break; case 'emoji': self.toggleEmojiPicker(btn, textarea); break; case 'quick-reply': self.toggleQuickReply(btn, textarea); break; } }); }, /** * Inline URL input — replaces native prompt() with a small input below the button. */ askUrl: function (anchor, placeholder, callback) { var existing = document.querySelector('.aksrt-inline-url-input'); if (existing) existing.remove(); var input = document.createElement('input'); input.type = 'url'; input.className = 'aksrt-inline-url-input'; input.placeholder = placeholder || 'https://'; input.value = 'https://'; input.style.cssText = 'display:block;width:100%;max-width:320px;margin:4px 0;padding:6px 10px;border:1px solid #dce3ec;border-radius:8px;font-size:13px;'; var parent = anchor.parentElement; parent.insertBefore(input, anchor.nextSibling); input.focus(); input.select(); function done() { var val = input.value.trim(); input.remove(); callback(val); } input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); done(); } if (e.key === 'Escape') { input.remove(); callback(''); } }); input.addEventListener('blur', function () { done(); }); }, /** * Simple emoji picker */ toggleEmojiPicker: function (btn, textarea) { var self = this; this.removeQuickReplyPanel(); if (this.emojiPicker) { this.emojiPicker.remove(); this.emojiPicker = null; return; } var emojis = [ { key: 'grinning', emoji: '😀' }, { key: 'joy', emoji: '😂' }, { key: 'rofl', emoji: '🤣' }, { key: 'blush', emoji: '😊' }, { key: 'heart-eyes', emoji: '😍' }, { key: 'smiling-hearts', emoji: '🥰' }, { key: 'sunglasses', emoji: '😎' }, { key: 'thinking', emoji: '🤔' }, { key: 'cry', emoji: '😢' }, { key: 'sob', emoji: '😭' }, { key: 'angry', emoji: '😡' }, { key: 'pleading', emoji: '🥺' }, { key: 'scream', emoji: '😱' }, { key: 'rolling-eyes', emoji: '🙄' }, { key: 'sleeping', emoji: '😴' }, { key: 'nauseated', emoji: '🤮' }, { key: 'thumbs-up', emoji: '👍' }, { key: 'thumbs-down', emoji: '👎' }, { key: 'clap', emoji: '👏' }, { key: 'pray', emoji: '🙏' }, { key: 'muscle', emoji: '💪' }, { key: 'heart', emoji: '❤️' }, { key: 'fire', emoji: '🔥' }, { key: 'star', emoji: '⭐' }, { key: 'tada', emoji: '🎉' }, { key: 'confetti', emoji: '🎊' }, { key: 'check', emoji: '✅' }, { key: 'cross', emoji: '❌' }, { key: 'warning', emoji: '⚠️' }, { key: 'hundred', emoji: '💯' }, { key: 'ok', emoji: '🆗' }, { key: 'new', emoji: '🆕' } ]; var picker = document.createElement('div'); picker.className = 'aksrt-emoji-picker'; picker.style.cssText = 'position:absolute;bottom:100%;left:0;background:#fff;border:1px solid #e8e8e8;border-radius:8px;padding:8px;display:grid;grid-template-columns:repeat(8,1fr);gap:4px;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,0.1);'; emojis.forEach(function (entry) { var item = document.createElement('button'); item.type = 'button'; item.style.cssText = 'border:none;background:none;cursor:pointer;font-size:18px;padding:4px;border-radius:4px;transition:background 0.15s;'; item.textContent = entry.emoji; item.setAttribute('title', entry.key); item.addEventListener('mouseenter', function () { item.style.background = '#f5f5f5'; }); item.addEventListener('mouseleave', function () { item.style.background = 'none'; }); item.addEventListener('click', function (e) { e.preventDefault(); self.insertAtCursor(textarea, '[g=' + entry.key + ']', ''); picker.remove(); self.emojiPicker = null; }); picker.appendChild(item); }); btn.style.position = 'relative'; btn.appendChild(picker); this.emojiPicker = picker; // Close on outside click var closeHandler = function (e) { if (!picker.contains(e.target) && e.target !== btn) { picker.remove(); self.emojiPicker = null; document.removeEventListener('click', closeHandler); } }; setTimeout(function () { document.addEventListener('click', closeHandler); }, 0); }, removeQuickReplyPanel: function () { if (this.quickReplyPanel) { this.quickReplyPanel.remove(); this.quickReplyPanel = null; } }, toggleQuickReply: function (btn, textarea) { var self = this; if (this.quickReplyPanel) { this.removeQuickReplyPanel(); return; } if (this.emojiPicker) { this.emojiPicker.remove(); this.emojiPicker = null; } var panel = document.createElement('div'); panel.className = 'comment-quick-reply'; var title = document.createElement('div'); title.className = 'comment-quick-reply__title'; title.textContent = '快捷回复'; panel.appendChild(title); var templates = document.createElement('div'); templates.className = 'comment-quick-reply__templates'; this.quickReplyTemplates.forEach(function (text) { var item = document.createElement('button'); item.type = 'button'; item.className = 'comment-quick-reply__template'; item.textContent = text; item.addEventListener('click', function (e) { e.preventDefault(); var needsLineBreak = textarea.value && !/\n\s*$/.test(textarea.value); var prefix = needsLineBreak ? '\n' : ''; self.insertAtCursor(textarea, prefix + text, ''); self.removeQuickReplyPanel(); }); templates.appendChild(item); }); panel.appendChild(templates); var toolbar = btn.closest('.comment-toolbar'); if (toolbar && toolbar.parentNode) { toolbar.parentNode.insertBefore(panel, toolbar.nextSibling); } else { btn.parentNode.appendChild(panel); } this.quickReplyPanel = panel; var closeHandler = function (e) { if (!panel.contains(e.target) && e.target !== btn) { self.removeQuickReplyPanel(); document.removeEventListener('click', closeHandler); } }; setTimeout(function () { document.addEventListener('click', closeHandler); }, 0); }, /** * Ctrl+Enter to submit comment */ bindCtrlEnter: function () { document.addEventListener('keydown', function (e) { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { var textarea = e.target; if (textarea.tagName !== 'TEXTAREA') return; var form = textarea.closest('form'); if (!form) return; var submitBtn = form.querySelector('[type="submit"]'); if (submitBtn) { submitBtn.click(); } } }); }, /** * Quick quote: select text on page, show "quote in comment" button */ bindQuickQuote: function () { if (!this.quickQuoteEnabled) return; var self = this; var quoteBtn = null; document.addEventListener('mouseup', function () { setTimeout(function () { if (quoteBtn) { quoteBtn.remove(); quoteBtn = null; } var selection = window.getSelection(); var text = selection.toString().trim(); if (text.length < 5 || text.length > 500) return; var range = selection.getRangeAt(0); var rect = range.getBoundingClientRect(); quoteBtn = document.createElement('button'); quoteBtn.type = 'button'; quoteBtn.textContent = '引用评论'; quoteBtn.style.cssText = 'position:fixed;top:' + (rect.top - 36) + 'px;left:' + (rect.left + rect.width / 2) + 'px;transform:translateX(-50%);padding:4px 12px;background:#1890ff;color:#fff;border:none;border-radius:4px;font-size:12px;cursor:pointer;z-index:9999;box-shadow:0 2px 8px rgba(0,0,0,0.15);'; quoteBtn.addEventListener('click', function () { var textarea = document.getElementById('comment'); if (textarea) { var quoted = text.split('\n').map(function (line) { return '> ' + line; }).join('\n') + '\n\n'; self.insertAtCursor(textarea, quoted, ''); textarea.scrollIntoView({ behavior: 'smooth', block: 'center' }); textarea.focus(); } quoteBtn.remove(); quoteBtn = null; }); document.body.appendChild(quoteBtn); }, 10); }); }, }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { COMMENT_TOOLS.init(); }); } else { COMMENT_TOOLS.init(); } })();