(function () {
var root = document.querySelector("[data-play-composer]");
if (!root) return;
var commentsRoot = document.querySelector("[data-play-comments]") || root.closest(".play-comments");
var input = root.querySelector("[data-comment-input]");
var countEl = root.querySelector("[data-comment-count]");
var sendBtn = root.querySelector("[data-comment-send]");
var listEl = document.querySelector("[data-comment-list]");
var totalEl = document.querySelector("[data-comment-total-label]");
var moreBtn = document.querySelector("[data-comment-more]");
var sending = false;
var inlineSending = false;
var loadingMore = false;
var MAX_LEN = 200;
var PAGE_SIZE = 4;
var replyTarget = null;
function toast(message, type) {
if (typeof window.showToast === "function") {
window.showToast(message, type);
return;
}
if (window.AuthAPI && typeof window.AuthAPI.toast === "function") {
window.AuthAPI.toast(message, type || "info");
return;
}
}
function toInt(v, fallback) {
var n = parseInt(v, 10);
return isNaN(n) ? fallback || 0 : n;
}
function escapeHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function getPageSize() {
return Math.max(
1,
toInt(commentsRoot && commentsRoot.getAttribute("data-comment-size"), PAGE_SIZE)
);
}
function getPage() {
return Math.max(
1,
toInt(commentsRoot && commentsRoot.getAttribute("data-comment-page"), 1)
);
}
function getLoaded() {
if (!listEl) return 0;
return listEl.querySelectorAll(".play-comment:not(.play-comment--empty)").length;
}
function setPagerState(page, loaded, total) {
if (!commentsRoot) return;
if (page != null) commentsRoot.setAttribute("data-comment-page", String(page));
if (loaded != null) commentsRoot.setAttribute("data-comment-loaded", String(loaded));
if (total != null) commentsRoot.setAttribute("data-comment-total", String(total));
syncMoreButton(total != null ? total : toInt(commentsRoot.getAttribute("data-comment-total"), 0));
}
function syncMoreButton(total) {
if (!moreBtn) return;
var loaded = getLoaded();
var hasMore = total > loaded;
if (hasMore) {
moreBtn.hidden = false;
moreBtn.removeAttribute("hidden");
} else {
moreBtn.hidden = true;
moreBtn.setAttribute("hidden", "");
}
}
function syncCount() {
if (!input || !countEl) return;
var len = Array.from(input.value || "").length;
countEl.textContent = len + " / " + MAX_LEN;
}
function formatTime(raw) {
var s = String(raw || "").trim();
if (!s) return "刚刚";
var d = new Date(s);
if (isNaN(d.getTime())) return s;
return (
d.getFullYear() +
"/" +
(d.getMonth() + 1) +
"/" +
d.getDate() +
" " +
String(d.getHours()).padStart(2, "0") +
":" +
String(d.getMinutes()).padStart(2, "0")
);
}
function renderCommentItem(item) {
item = item || {};
var avatar = String(item.userAvatar || "").trim();
var name = escapeHtml(item.userName || "匿名用户");
var text = escapeHtml(item.text || "");
var time = escapeHtml(formatTime(item.createdAt));
var like = toInt(item.countLike, 0);
var liked = Number(item.isLike) === 1;
var avatarHtml = avatar
? ''
: '';
var children = Array.isArray(item.children) ? item.children : [];
var replies = children
.map(function (child) {
var cName = escapeHtml(child.userName || "用户");
var replyTo = child.replyUserName
? " 回复 " + escapeHtml(child.replyUserName)
: "";
return (
'
"
);
})
.join("");
var commentId = escapeHtml(item.id || "");
var rawName = String(item.userName || "匿名用户");
return (
'"
);
}
function getLoadedIds() {
var ids = Object.create(null);
if (!listEl) return ids;
listEl.querySelectorAll(".play-comment[data-comment-id]").forEach(function (el) {
var id = String(el.getAttribute("data-comment-id") || "").trim();
if (id) ids[id] = true;
});
return ids;
}
function updateTotal(n) {
if (!totalEl) return;
totalEl.textContent = n > 0 ? n + " 条" : "暂无评论";
}
function renderList(list, totalCount) {
if (!listEl) return;
list = Array.isArray(list) ? list : [];
clearReplyTarget();
if (!list.length) {
listEl.innerHTML =
'';
} else {
listEl.innerHTML = list.map(renderCommentItem).join("");
if (typeof window.initLazyImages === "function") {
window.initLazyImages(listEl);
}
}
var total = typeof totalCount === "number" ? totalCount : toInt(commentsRoot && commentsRoot.getAttribute("data-comment-total"), 0);
if (typeof totalCount === "number") {
updateTotal(totalCount);
}
setPagerState(1, list.length, total);
}
// 下一页追加到现有列表底部,不替换已展示评论。
function appendList(list, totalCount) {
if (!listEl) return;
list = Array.isArray(list) ? list : [];
var total =
typeof totalCount === "number"
? totalCount
: toInt(commentsRoot && commentsRoot.getAttribute("data-comment-total"), 0);
if (typeof totalCount === "number") {
updateTotal(totalCount);
}
if (!list.length) {
setPagerState(getPage(), getLoaded(), total);
return;
}
var empty = listEl.querySelector(".play-comment--empty");
if (empty) empty.remove();
var existing = getLoadedIds();
var fresh = list.filter(function (item) {
var id = String((item && item.id) || "").trim();
return id && !existing[id];
});
if (!fresh.length) {
setPagerState(getPage(), getLoaded(), total);
return;
}
var wrap = document.createElement("ul");
wrap.innerHTML = fresh.map(renderCommentItem).join("");
var nodes = [];
while (wrap.firstChild) {
nodes.push(wrap.firstChild);
listEl.appendChild(wrap.firstChild);
}
if (typeof window.initLazyImages === "function") {
nodes.forEach(function (node) {
if (node && node.nodeType === 1) window.initLazyImages(node);
});
window.initLazyImages(listEl);
}
setPagerState(getPage(), getLoaded(), total);
}
function fetchCommentPage(pageNum) {
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
return Promise.reject(new Error("评论服务未就绪"));
}
var objectId = toInt(root.getAttribute("data-object-id"), 0);
var objectType = toInt(root.getAttribute("data-object-type"), 10);
if (objectId <= 0) return Promise.reject(new Error("缺少评论对象"));
var pageSize = getPageSize();
return window.HttpRequest.get("/api/comment/list", {
params: {
objectId: objectId,
objectType: objectType,
pageNum: pageNum,
pageSize: pageSize,
sortOrder: 1,
},
}).then(function (res) {
if (!res || Number(res.code) !== 200 || !res.data) {
throw new Error((res && (res.tip || res.msg)) || "加载失败");
}
return res.data;
});
}
function refreshList() {
return fetchCommentPage(1)
.then(function (data) {
renderList(data.list || [], toInt(data.totalCount, 0));
})
.catch(function () {});
}
function loadMoreComments() {
if (loadingMore || !moreBtn) return;
var nextPage = getPage() + 1;
loadingMore = true;
moreBtn.disabled = true;
fetchCommentPage(nextPage)
.then(function (data) {
var list = data.list || [];
var total = toInt(data.totalCount, 0);
if (commentsRoot) {
commentsRoot.setAttribute("data-comment-page", String(nextPage));
}
// 只追加,绝不 renderList 整表替换
appendList(list, total);
})
.catch(function (err) {
toast((err && err.message) || "加载失败,请稍后重试", "error");
})
.finally(function () {
loadingMore = false;
if (moreBtn) moreBtn.disabled = false;
});
}
function removeInlineComposer() {
if (!listEl) return;
listEl.querySelectorAll("[data-comment-inline-composer]").forEach(function (el) {
el.remove();
});
}
function clearReplyTarget() {
replyTarget = null;
removeInlineComposer();
}
function applyLikeUI(btn, isLike, countLike) {
if (!btn) return;
var liked = Number(isLike) === 1;
btn.classList.toggle("is-active", liked);
btn.setAttribute("data-liked", liked ? "1" : "2");
if (countLike != null) {
btn.setAttribute("data-like-raw", String(countLike));
var countEl = btn.querySelector("[data-like-count]");
if (countEl) countEl.textContent = String(countLike);
}
}
function toggleCommentLike(btn) {
if (!btn || btn.getAttribute("data-liking") === "1") return;
if (root.getAttribute("data-logged-in") !== "1") {
var loginBtn = document.querySelector("[data-login-open]");
if (loginBtn) loginBtn.click();
return;
}
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
toast("点赞服务未就绪", "error");
return;
}
var commentId = toInt(btn.getAttribute("data-comment-like"), 0);
if (commentId <= 0) return;
var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1;
var nextLike = !likedNow;
var prevCount = toInt(btn.getAttribute("data-like-raw"), 0);
var nextCount = Math.max(0, prevCount + (nextLike ? 1 : -1));
btn.setAttribute("data-liking", "1");
btn.disabled = true;
applyLikeUI(btn, nextLike ? 1 : 2, nextCount);
var payload = { commentId: commentId, isLike: nextLike };
window.HttpRequest.post("/api/comment/like/set", payload)
.then(function (res) {
if (!res || Number(res.code) !== 200) {
throw new Error((res && (res.tip || res.msg)) || "操作失败");
}
var data = res.data || {};
applyLikeUI(
btn,
data.isLike != null ? data.isLike : nextLike ? 1 : 2,
data.countLike != null ? data.countLike : nextCount
);
})
.catch(function (err) {
applyLikeUI(btn, likedNow ? 1 : 2, prevCount);
toast((err && err.message) || "操作失败,请稍后重试", "error");
})
.finally(function () {
btn.removeAttribute("data-liking");
btn.disabled = false;
});
}
function openInlineComposer(commentEl, target) {
if (!commentEl || !target) return;
removeInlineComposer();
replyTarget = target;
var body = commentEl.querySelector(".play-comment__body") || commentEl;
var actions = body.querySelector(".play-comment__actions");
var box = document.createElement("div");
box.className = "play-comment__inline";
box.setAttribute("data-comment-inline-composer", "");
var name = target.replyName || "用户";
box.innerHTML =
'" +
'";
if (actions && actions.parentNode === body) {
if (actions.nextSibling) body.insertBefore(box, actions.nextSibling);
else body.appendChild(box);
} else {
body.appendChild(box);
}
var inlineInput = box.querySelector("[data-comment-inline-input]");
if (inlineInput) inlineInput.focus();
}
function setSending(on) {
sending = !!on;
if (sendBtn) sendBtn.disabled = !!on;
if (input) input.disabled = !!on;
}
function submitTopComment() {
if (sending) return;
if (root.getAttribute("data-logged-in") !== "1") {
var loginBtn = document.querySelector("[data-login-open]");
if (loginBtn) loginBtn.click();
return;
}
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
toast("评论服务未就绪", "error");
return;
}
var text = String((input && input.value) || "").trim();
if (!text) {
toast("请输入评论内容", "error");
return;
}
if (Array.from(text).length > MAX_LEN) {
toast("评论最多 200 字", "error");
return;
}
var objectId = toInt(root.getAttribute("data-object-id"), 0);
var objectType = toInt(root.getAttribute("data-object-type"), 10);
if (objectId <= 0) {
toast("缺少评论对象", "error");
return;
}
var payload = {
objectId: objectId,
objectType: objectType,
text: text,
parentsId: 0,
replyCommentId: 0,
};
setSending(true);
window.HttpRequest.post("/api/comment/send", payload)
.then(function (res) {
if (!res || Number(res.code) !== 200) {
throw new Error((res && (res.tip || res.msg)) || "发表失败");
}
if (input) input.value = "";
syncCount();
clearReplyTarget();
toast("发布成功,评论审核后才会展示", "success");
return refreshList();
})
.catch(function (err) {
toast((err && err.message) || "发表失败,请稍后重试", "error");
})
.finally(function () {
setSending(false);
});
}
function sendInlineReply(box) {
if (!box || inlineSending || !replyTarget) return;
if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") {
toast("评论服务未就绪", "error");
return;
}
var inlineInput = box.querySelector("[data-comment-inline-input]");
var btn = box.querySelector("[data-comment-inline-send]");
if (!inlineInput) return;
var text = String(inlineInput.value || "").trim();
if (!text) {
toast("请输入评论内容", "error");
return;
}
if (Array.from(text).length > MAX_LEN) {
toast("评论最多 200 字", "error");
return;
}
var objectId = toInt(root.getAttribute("data-object-id"), 0);
var objectType = toInt(root.getAttribute("data-object-type"), 10);
if (objectId <= 0) {
toast("缺少评论对象", "error");
return;
}
inlineSending = true;
if (btn) btn.disabled = true;
inlineInput.disabled = true;
var payload = {
objectId: objectId,
objectType: objectType,
text: text,
parentsId: toInt(replyTarget.parentsId, 0),
replyCommentId: toInt(
replyTarget.replyCommentId || replyTarget.parentsId,
0
),
};
window.HttpRequest.post("/api/comment/send", payload)
.then(function (res) {
if (!res || Number(res.code) !== 200) {
throw new Error((res && (res.tip || res.msg)) || "发表失败");
}
clearReplyTarget();
toast("发布成功,评论审核后才会展示", "success");
return refreshList();
})
.catch(function (err) {
toast((err && err.message) || "发表失败,请稍后重试", "error");
})
.finally(function () {
inlineSending = false;
if (btn) btn.disabled = false;
if (inlineInput) inlineInput.disabled = false;
});
}
if (input) {
input.addEventListener("input", syncCount);
input.addEventListener("keydown", function (event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submitTopComment();
}
});
syncCount();
}
if (sendBtn) {
sendBtn.addEventListener("click", submitTopComment);
}
if (moreBtn) {
moreBtn.addEventListener("click", function (event) {
event.preventDefault();
loadMoreComments();
});
}
if (listEl) {
listEl.addEventListener("click", function (event) {
var likeBtn = window.domClosest(event.target, "[data-comment-like]");
if (likeBtn && listEl.contains(likeBtn)) {
event.preventDefault();
toggleCommentLike(likeBtn);
return;
}
var cancelBtn = window.domClosest(event.target, "[data-comment-reply-cancel]");
if (cancelBtn && listEl.contains(cancelBtn)) {
clearReplyTarget();
return;
}
var sendInlineBtn = window.domClosest(event.target, "[data-comment-inline-send]");
if (sendInlineBtn && listEl.contains(sendInlineBtn)) {
var box = sendInlineBtn.closest("[data-comment-inline-composer]");
sendInlineReply(box);
return;
}
var replyBtn = window.domClosest(event.target, "[data-comment-reply]");
if (!replyBtn || !listEl.contains(replyBtn)) return;
if (root.getAttribute("data-logged-in") !== "1") {
var loginBtn = document.querySelector("[data-login-open]");
if (loginBtn) loginBtn.click();
return;
}
var parentsId = toInt(replyBtn.getAttribute("data-parents-id"), 0);
var commentId = toInt(replyBtn.getAttribute("data-comment-id"), parentsId);
if (parentsId <= 0) return;
var commentEl = replyBtn.closest(".play-comment");
openInlineComposer(commentEl, {
parentsId: parentsId,
replyCommentId: commentId,
replyName: replyBtn.getAttribute("data-reply-name") || "用户",
});
});
listEl.addEventListener("keydown", function (event) {
if (event.key !== "Enter" || event.shiftKey) return;
var inlineInput = window.domClosest(event.target, "[data-comment-inline-input]");
if (!inlineInput || !listEl.contains(inlineInput)) return;
event.preventDefault();
var box = inlineInput.closest("[data-comment-inline-composer]");
sendInlineReply(box);
});
}
})();