feat(comments): full 1:1 zibll comment kernel for posts, forum, shop

Extract shared comment helpers (content/item/form/geo/actions), port GIF
[g=] smilies + FixedInput/moveForm UX, wire close_comments/city/quick
settings, REST comment get, and align product review chrome with zibll.
This commit is contained in:
kate522 committed 2026-07-18 15:51:47 +08:00
1 parent 9edd39c33d
commit 276a2891ba
116 files changed
+2658 -1848

No files matched your search

+9 -75
View File
@@ -1,23 +1,15 @@
/**
* AKSRT Comment Actions client
*
* Single client for per-comment actions via REST aksrt/v1:
* like / edit / delete / moderate (approve|hold|spam) / pin
* REST aksrt/v1: like / delete / moderate / pin.
* Edit uses moveForm (#respond) via comment-move-form.js — not inline textarea.
* Submit owned by assets/js/core/comments.js (single submit contract).
*
* Comment SUBMISSION is owned by main.js initCommentSystem() (the native
* wp-comments-post.php flow, which runs captcha verification, rate limiting and
* the moderation pipeline, then re-renders the list through the canonical
* server callback). This file deliberately does NOT bind the submit form, so
* there is exactly one comment-submit contract.
*
* Markup contract (rendered by aksrt_comment_callback, identical on first
* paint, load-more and post-submit reload):
* Markup (aksrt_comment_callback):
* li.aksrt-comment[data-comment-id]
* .aksrt-comment__text (content)
* .aksrt-comment__actions
* [data-comment-action="like|edit|delete|approve|hold|spam|pin"]
*
* Depends on: aksrtData.restNonce (wp_rest) + aksrtData.siteUrl.
* .comment-header .aksrt-comment__like[data-comment-action=like]
* .comment-content#comment-content-{id}
* .comt-meta [data-comment-action=pin|approve|hold|spam|delete]
*
* @package AKSRT
*/
@@ -66,11 +58,10 @@
var commentId = getCommentId(commentEl);
if (!commentId) return;
// edit is handled by comment-move-form.js (moveForm into #respond).
switch (action) {
case 'like': handleLike(actionBtn, commentId); break;
case 'edit': handleEdit(commentEl); break;
case 'save-edit': saveEdit(commentEl, commentId, actionBtn); break;
case 'cancel-edit': cancelEdit(commentEl); break;
case 'edit': break;
case 'delete': handleDelete(commentEl, commentId); break;
case 'pin': handlePin(commentEl, actionBtn, commentId); break;
case 'approve':
@@ -104,63 +95,6 @@
.finally(function () { btn.dataset.busy = ''; });
}
function handleEdit(commentEl) {
var contentEl = commentEl.querySelector(CONTENT_SELECTOR);
if (!contentEl || commentEl.querySelector('.aksrt-comment-edit-form')) return;
var temp = document.createElement('div');
temp.innerHTML = contentEl.innerHTML;
var originalText = (temp.textContent || temp.innerText || '').trim();
var editForm = document.createElement('div');
editForm.className = 'aksrt-comment-edit-form';
editForm.innerHTML =
'<textarea class="aksrt-comment-edit-textarea"></textarea>' +
'<div class="aksrt-comment-actions">' +
'<button type="button" data-comment-action="save-edit" class="aksrt-comment-action-btn aksrt-comment-action-btn--success">保存</button>' +
'<button type="button" data-comment-action="cancel-edit" class="aksrt-comment-action-btn">取消</button>' +
'</div>';
editForm.querySelector('textarea').value = originalText;
contentEl.style.display = 'none';
contentEl.parentNode.insertBefore(editForm, contentEl.nextSibling);
editForm.querySelector('textarea').focus();
}
function cancelEdit(commentEl) {
var editForm = commentEl.querySelector('.aksrt-comment-edit-form');
if (!editForm) return;
var contentEl = commentEl.querySelector(CONTENT_SELECTOR);
if (contentEl) contentEl.style.display = '';
editForm.remove();
}
function saveEdit(commentEl, commentId, saveBtn) {
var editForm = commentEl.querySelector('.aksrt-comment-edit-form');
if (!editForm) return;
var textarea = editForm.querySelector('.aksrt-comment-edit-textarea');
var newContent = textarea ? textarea.value.trim() : '';
if (!newContent) return;
if (saveBtn) { saveBtn.disabled = true; saveBtn.textContent = '保存中...'; }
restPost('/comment/edit', { comment_id: commentId, content: newContent })
.then(function (result) {
if (result.success) {
var contentEl = commentEl.querySelector(CONTENT_SELECTOR);
if (contentEl) { contentEl.innerHTML = result.data.content; contentEl.style.display = ''; }
editForm.remove();
} else {
notify((result.data && result.data.message) || result.message || '编辑失败', 'error');
if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = '保存'; }
}
})
.catch(function () {
notify('网络错误', 'error');
if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = '保存'; }
});
}
function handleDelete(commentEl, commentId) {
var msg = (window.aksrt && aksrt.__) ? aksrt.__('comment_confirm_delete', '确定要删除这条评论吗?') : '确定要删除这条评论吗?';
var doDelete = function () {
+134 -415
View File
@@ -1,422 +1,141 @@
/**
* AKSRT Comment Tools
*
* Handles comment toolbar actions (emoji, code, bold, italic, quote, link),
* Ctrl+Enter submit, and quick quote from selection.
*
* @package AKSRT
* Comment expand tools — zibll-style shortcode inserts ([g=] [code] [img=] quick).
*/
(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 = '<i class="fas fa-spinner fa-spin"></i>';
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();
function notify(msg) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg, 'info');
}
}
function findTextarea(from) {
var form = from && from.closest ? from.closest('form') : null;
if (form) {
return form.querySelector('textarea.grin, textarea#comment, textarea[name="comment"]');
}
return document.querySelector('#comment, textarea.grin');
}
function insertAtCursor(textarea, value) {
if (!textarea) return;
var start = textarea.selectionStart || 0;
var end = textarea.selectionEnd || 0;
var val = textarea.value || '';
textarea.value = val.slice(0, start) + value + val.slice(end);
var pos = start + value.length;
textarea.selectionStart = textarea.selectionEnd = pos;
textarea.focus();
textarea.dispatchEvent(new Event('input'));
}
function restUpload(file) {
var d = window.aksrtData || {};
var url = d.commentUploadUrl || ((d.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, '') + '/comment/upload-image');
var fd = new FormData();
fd.append('image', file);
fd.append('file', file);
return fetch(url, {
method: 'POST',
headers: { 'X-WP-Nonce': d.restNonce || d.nonce || '' },
credentials: 'same-origin',
body: fd
}).then(function (r) { return r.json(); });
}
document.addEventListener('click', function (e) {
var sm = e.target && e.target.closest ? e.target.closest('[data-smilie], a.smilie-icon') : null;
if (sm) {
e.preventDefault();
var key = sm.getAttribute('data-smilie');
if (!key && sm.querySelector) {
var img = sm.querySelector('[data-smilie]');
if (img) key = img.getAttribute('data-smilie');
}
if (!key) return;
insertAtCursor(findTextarea(sm), '[g=' + key + ']');
var wrap = sm.closest('.dropup, .input-expand-wrap');
if (wrap) wrap.classList.remove('open');
return;
}
var codeBtn = e.target && e.target.closest ? e.target.closest('.input-code-submit') : null;
if (codeBtn) {
e.preventDefault();
var box = codeBtn.closest('.dropdown-code, .input-expand-code');
var ta = box ? box.querySelector('textarea') : null;
var code = ta ? ta.value : '';
if (!code || code.length < 2) {
notify('请输入代码');
return;
}
insertAtCursor(findTextarea(codeBtn), '[code]\n' + code + '\n[/code]\n');
if (ta) ta.value = '';
var w = codeBtn.closest('.dropup, .input-expand-wrap');
if (w) w.classList.remove('open');
return;
}
var imgBtn = e.target && e.target.closest ? e.target.closest('.input-image-submit') : null;
if (imgBtn) {
e.preventDefault();
var ibox = imgBtn.closest('.dropdown-image, .input-expand-image');
var input = ibox ? ibox.querySelector('.input-image-url') : null;
var url = input ? (input.value || '').trim() : '';
if (!/^https?:\/\//i.test(url)) {
notify('请输入有效图片地址');
return;
}
insertAtCursor(findTextarea(imgBtn), '[img=' + url + ']');
if (input) input.value = '';
var iw = imgBtn.closest('.dropup, .input-expand-wrap');
if (iw) iw.classList.remove('open');
return;
}
var upBtn = e.target && e.target.closest ? e.target.closest('.input-image-upload') : null;
if (upBtn) {
e.preventDefault();
var fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/jpeg,image/png,image/gif,image/webp';
fileInput.addEventListener('change', function () {
var file = fileInput.files && fileInput.files[0];
if (!file) return;
restUpload(file)
.then(function (json) {
var u = (json && (json.url || json.source_url || (json.data && (json.data.url || json.data.source_url)))) || '';
if (!u) {
notify((json && json.message) || '上传失败');
return;
}
insertAtCursor(findTextarea(upBtn), '[img=' + u + ']');
})
.catch(function () { notify('上传失败'); });
});
fileInput.click();
return;
}
var q = e.target && e.target.closest ? e.target.closest('.quick-reply-item') : null;
if (q) {
e.preventDefault();
var phrase = q.getAttribute('data-text') || q.textContent || '';
insertAtCursor(findTextarea(q), phrase);
var qw = q.closest('.dropup, .input-expand-wrap');
if (qw) qw.classList.remove('open');
}
});
document.addEventListener('keydown', function (e) {
if (!(e.ctrlKey || e.metaKey) || e.key !== 'Enter') return;
var ta = e.target;
if (!ta || ta.id !== 'comment') return;
var form = ta.closest('#commentform');
if (!form) return;
e.preventDefault();
var submit = form.querySelector('#submit, [type="submit"]');
if (submit) submit.click();
});
})();
+90
View File
@@ -0,0 +1,90 @@
/**
* Mobile FixedInput + autoheight for comment respond (zibll parity).
*/
(function () {
'use strict';
function $(sel, root) {
return (root || document).querySelector(sel);
}
function FixedInput(el, action) {
if (!el) return;
if (!el.dataset.fixedBound) {
el.addEventListener('click', function (e) {
if (e.target && e.target.closest && e.target.closest('.fixed-body')) {
FixedInput(el, 'hide');
}
});
el.dataset.fixedBound = '1';
}
if (action === 'show') {
el.classList.add('show');
window.setTimeout(function () {
var field = el.querySelector('textarea, input');
if (field) field.focus();
}, 100);
return;
}
if (action === 'hide') {
var cancel = el.querySelector('#cancel-comment-reply-link');
if (cancel) cancel.click();
el.querySelectorAll('input,textarea').forEach(function (n) {
try { n.blur(); } catch (e) {}
});
el.querySelectorAll('.dropup.open, .open').forEach(function (n) {
n.classList.remove('open');
});
el.classList.remove('show');
}
}
document.addEventListener('click', function (e) {
var trigger = e.target && e.target.closest ? e.target.closest('[fixed-input]') : null;
if (!trigger) return;
e.preventDefault();
var targetSel = trigger.getAttribute('fixed-input') || '#respond';
var panel = document.querySelector(targetSel);
var comments = document.getElementById('comments');
if (comments && comments.scrollIntoView) {
comments.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
FixedInput(panel, 'show');
});
// Autoheight textareas.
function bindAutoheight(ta) {
if (!ta || ta.dataset.autoheightBound) return;
ta.dataset.autoheightBound = '1';
var max = parseInt(ta.getAttribute('maxheight') || '188', 10) || 188;
var resize = function () {
ta.style.height = 'auto';
var h = Math.min(ta.scrollHeight, max);
ta.style.height = h + 'px';
ta.style.overflowY = ta.scrollHeight > max ? 'auto' : 'hidden';
};
ta.addEventListener('input', resize);
resize();
}
function scan() {
document.querySelectorAll('textarea[autoheight], textarea.grin').forEach(bindAutoheight);
}
document.addEventListener('DOMContentLoaded', scan);
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
window.AksrtFrontend.register('aksrt-comment-fixed-input', function (root) {
scan();
// Expose for submit hide.
root = root || document;
var respond = root.querySelector ? root.querySelector('#respond') : document.getElementById('respond');
if (respond) {
respond.aksrtFixedInput = function (action) {
FixedInput(respond, action);
};
}
});
}
window.AksrtFixedInput = FixedInput;
})();
+243
View File
@@ -0,0 +1,243 @@
/**
* Comment moveForm for reply/edit (zibll parity) — REST-backed edit fetch.
*/
(function () {
'use strict';
var respondId = 'respond';
var tempId = 'aksrt-wp-temp-form-div';
var editingId = 0;
function getRespond() {
return document.getElementById(respondId);
}
function restRoot() {
var d = window.aksrtData || {};
return (d.restUrl || d.rest_url || '/wp-json/aksrt/v1').replace(/\/$/, '');
}
function nonce() {
var d = window.aksrtData || {};
return d.restNonce || d.nonce || '';
}
function notify(msg, type) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg, type || 'info');
return;
}
if (window.notyf) {
try { window.notyf[type === 'error' ? 'error' : 'success'](msg); } catch (e) {}
}
}
function setActionText(text) {
var el = document.querySelector('#' + respondId + ' .action-text');
if (!el) return;
if (text) {
el.style.display = '';
el.textContent = text;
} else {
el.style.display = 'none';
el.textContent = '';
}
}
function ensureParentField(respond) {
var form = respond.querySelector('#commentform') || respond;
var parent = form.querySelector('[name="comment_parent"]');
if (!parent) {
parent = document.createElement('input');
parent.type = 'hidden';
parent.name = 'comment_parent';
parent.id = 'comment_parent';
parent.value = '0';
form.appendChild(parent);
}
return parent;
}
function ensureEditField(respond, commentId) {
var form = respond.querySelector('#commentform') || respond;
var existing = form.querySelector('[name="edit_comment_ID"]');
if (commentId) {
if (!existing) {
existing = document.createElement('input');
existing.type = 'hidden';
existing.name = 'edit_comment_ID';
existing.id = 'edit_comment_ID';
form.appendChild(existing);
}
existing.value = String(commentId);
// Hide parent while editing.
var parent = form.querySelector('[name="comment_parent"]');
if (parent) parent.name = 'comment_parent_bak';
} else if (existing) {
existing.remove();
var bak = form.querySelector('[name="comment_parent_bak"]');
if (bak) bak.name = 'comment_parent';
}
}
function moveForm(commId, parentId, isEdit) {
var respond = getRespond();
if (!respond) return false;
var comm = document.getElementById(commId);
var parent = ensureParentField(respond);
var cancel = document.getElementById('cancel-comment-reply-link');
var submit = respond.querySelector('#submit');
var textarea = respond.querySelector('#comment');
if (!document.getElementById(tempId)) {
var temp = document.createElement('div');
temp.id = tempId;
temp.style.display = 'none';
respond.parentNode.insertBefore(temp, respond);
}
if (comm && comm.parentNode) {
comm.parentNode.insertBefore(respond, comm.nextSibling);
}
parent.value = isEdit ? '0' : String(parentId || '0');
editingId = isEdit ? parseInt(parentId, 10) || 0 : 0;
if (cancel) {
cancel.style.display = '';
cancel.onclick = function () {
var t = document.getElementById(tempId);
var r = getRespond();
if (t && r && t.parentNode) {
t.parentNode.insertBefore(r, t);
t.parentNode.removeChild(t);
}
ensureEditField(r, 0);
if (parent) parent.value = '0';
setActionText('');
if (textarea) textarea.value = '';
editingId = 0;
cancel.style.display = 'none';
if (window.AksrtFixedInput) window.AksrtFixedInput(r, 'hide');
else if (r) r.classList.remove('show');
return false;
};
}
if (isEdit && editingId) {
setActionText('编辑评论');
ensureEditField(respond, editingId);
if (textarea) {
textarea.disabled = true;
textarea.value = '加载中…';
}
if (submit) submit.disabled = true;
fetch(restRoot() + '/comment/get?comment_id=' + encodeURIComponent(String(editingId)), {
headers: { 'X-WP-Nonce': nonce() },
credentials: 'same-origin',
})
.then(function (r) { return r.json(); })
.then(function (json) {
var content = (json && (json.content || (json.data && json.data.content))) || '';
if (textarea) {
textarea.disabled = false;
textarea.value = content;
textarea.dispatchEvent(new Event('input'));
}
if (submit) submit.disabled = false;
})
.catch(function () {
notify('加载评论失败', 'error');
if (textarea) {
textarea.disabled = false;
textarea.value = '';
}
if (submit) submit.disabled = false;
});
} else {
setActionText('回复评论');
ensureEditField(respond, 0);
if (textarea) {
textarea.focus();
}
}
if (window.AksrtFixedInput) {
window.AksrtFixedInput(respond, 'show');
} else {
respond.classList.add('show');
}
if (comm && comm.scrollIntoView) {
comm.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
return false;
}
document.addEventListener('click', function (e) {
var reply = e.target && e.target.closest ? e.target.closest('.comment-reply-link') : null;
var edit = e.target && e.target.closest ? e.target.closest('.comment-edit-link, [data-comment-action="edit"]') : null;
if (reply) {
e.preventDefault();
var cid = reply.getAttribute('data-commentid') || reply.getAttribute('data-comment-id') || '';
var postid = reply.getAttribute('data-postid') || '';
if (!cid) {
var li = reply.closest('[data-comment-id], #comment-' + (cid || ''));
if (li && li.getAttribute('data-comment-id')) cid = li.getAttribute('data-comment-id');
}
if (!cid) {
var m = (reply.closest('li[id^="comment-"]') || {}).id || '';
var mm = /comment-(\d+)/.exec(m);
if (mm) cid = mm[1];
}
moveForm('div-comment-' + cid, cid, false);
return;
}
if (edit) {
e.preventDefault();
var eid = edit.getAttribute('data-commentid') || edit.getAttribute('data-comment-id') || '';
if (!eid) {
var row = edit.closest('[data-comment-id]');
if (row) eid = row.getAttribute('data-comment-id');
}
if (!eid) return;
moveForm('div-comment-' + eid, eid, true);
}
});
// Dropup toggles (guest / expand).
document.addEventListener('click', function (e) {
var t = e.target && e.target.closest ? e.target.closest('[data-toggle-class="open"]') : null;
if (!t) return;
var targetSel = t.getAttribute('data-target');
if (!targetSel) return;
e.preventDefault();
var wrap = t.closest('.dropup, .input-expand-wrap, #comment-user-info') || t.parentElement;
var panel = targetSel.charAt(0) === '#' || targetSel.charAt(0) === '.'
? (wrap && wrap.querySelector(targetSel)) || document.querySelector(targetSel)
: null;
if (wrap) wrap.classList.toggle('open');
if (panel && panel !== wrap) panel.classList.toggle('open');
});
// Close dropups on outside click.
document.addEventListener('click', function (e) {
if (e.target.closest && e.target.closest('.dropup, .input-expand-wrap, [data-toggle-class="open"]')) return;
document.querySelectorAll('.dropup.open, .input-expand-wrap.open, #comment-user-info.open').forEach(function (n) {
n.classList.remove('open');
});
});
// Scroll helper for @parent.
document.addEventListener('click', function (e) {
var a = e.target && e.target.closest ? e.target.closest('[data-scroll-to]') : null;
if (!a) return;
e.preventDefault();
var sel = a.getAttribute('data-scroll-to');
var el = sel ? document.querySelector(sel) : null;
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
window.aksrtCommentMoveForm = moveForm;
window.aksrtGetEditingCommentId = function () { return editingId; };
})();
+34 -1
View File
@@ -206,11 +206,17 @@
var formData = collectFormData();
var restRoot = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, '') : '';
var restBase = (restRoot || '') + '/wp-json/aksrt/v1/comment/submit';
var editId = form.querySelector('[name="edit_comment_ID"]');
var isEdit = editId && parseInt(editId.value, 10) > 0;
var restBase = (restRoot || '') + '/wp-json/aksrt/v1/comment/' + (isEdit ? 'edit' : 'submit');
// Try REST endpoint for local-insert HTML response
var restBody = {};
formData.forEach(function (v, k) { restBody[k] = v; });
if (isEdit) {
restBody.comment_id = parseInt(editId.value, 10);
restBody.content = restBody.comment || restBody.content || '';
}
fetch(restBase, {
method: 'POST',
@@ -223,6 +229,21 @@
})
.then(function (r) { return r.json(); })
.then(function (json) {
if (isEdit && json && json.success) {
var cid = restBody.comment_id;
var contentEl = document.querySelector('#comment-content-' + cid + ', [data-comment-id="' + cid + '"] .comment-content');
var rendered = (json.data && json.data.content) || json.content || '';
if (contentEl && rendered) contentEl.innerHTML = rendered;
var textarea = form.querySelector('#comment');
if (textarea) textarea.value = '';
if (editId) editId.remove();
restoreRespondPosition();
setLoading(false);
showToast(json.message || '评论已更新', 'success');
var respondEl = document.getElementById('respond');
if (respondEl && window.AksrtFixedInput) window.AksrtFixedInput(respondEl, 'hide');
return;
}
if (json && json.success && json.html) {
// Local insert: prepend new comment HTML
localInsertComment(json.html, json.is_reply, json.parent_id, json.needs_moderation);
@@ -252,6 +273,18 @@
// Move respond back to original position
restoreRespondPosition();
// Hide mobile FixedInput after successful submit.
var respondEl = document.getElementById('respond');
if (respondEl && window.AksrtFixedInput) {
window.AksrtFixedInput(respondEl, 'hide');
} else if (respondEl) {
respondEl.classList.remove('show');
}
// If editing via moveForm, clear edit field.
var editField = form.querySelector('[name="edit_comment_ID"]');
if (editField) editField.remove();
return;
}