diff --git a/templates/MetaLab-2026/html/posts/show.html b/templates/MetaLab-2026/html/posts/show.html index 8686b21..c906e74 100644 --- a/templates/MetaLab-2026/html/posts/show.html +++ b/templates/MetaLab-2026/html/posts/show.html @@ -149,931 +149,10 @@ - - - - - - - - - - - + + + + + diff --git a/templates/MetaLab-2026/static/js/post-comments.js b/templates/MetaLab-2026/static/js/post-comments.js new file mode 100644 index 0000000..5a3ba7e --- /dev/null +++ b/templates/MetaLab-2026/static/js/post-comments.js @@ -0,0 +1,456 @@ +// 评论系统 +// 依赖: utils.js (escapeHtml, formatTime, showToast, api), common.js (getCSRFToken) +(function() { + var commentsSection = document.getElementById('comments'); + if (!commentsSection) return; + + var postId = commentsSection.closest('.container').querySelector('#postReactions').dataset.postId; + var commentsList = document.getElementById('commentsList'); + var commentsTotal = document.getElementById('commentsTotal'); + var loadMoreBtn = document.getElementById('commentsLoadMore'); + var commentInput = document.getElementById('commentInput'); + var commentSubmitBtn = document.getElementById('commentSubmitBtn'); + var mentionDropdown = document.getElementById('mentionDropdown'); + var currentPage = 1; + var totalPages = 1; + + function buildCommentCard(c) { + var time = c.created_at ? formatTime(c.created_at) : ''; + var author = c.author_name ? escapeHtml(c.author_name) : '用户已注销'; + var body = c.body || ''; + + body = escapeHtml(body); + body = body.replace(/@(\S{1,16})/g, '@$1'); + + var html = '
' + + '
' + + '' + author + '' + + '' + time + '' + + '
' + + '
' + body + '
' + + '
'; + + if (isLoggedIn()) { + html += ''; + } + + html += '
' + + '
'; + + if (c.replies_count > 0) { + html += ''; + } + + html += '' + + '
'; + return html; + } + + function buildReplyCard(r) { + var time = r.created_at ? formatTime(r.created_at) : ''; + var author = r.author_name ? escapeHtml(r.author_name) : '用户已注销'; + var body = r.body || ''; + body = escapeHtml(body); + body = body.replace(/@(\S{1,16})/g, '@$1'); + + var prefix = ''; + if (r.reply_to_name) { + prefix = '回复 ' + escapeHtml(r.reply_to_name) + ':'; + } + + return '
' + + '
' + + '' + author + '' + + '' + time + '' + + '
' + + '
' + prefix + body + '
' + + '
'; + } + + function isLoggedIn() { + return !!document.getElementById('commentInput'); + } + + // ====== 加载根评论 ====== + function loadComments(page) { + var url = '/api/posts/' + postId + '/comments?page=' + page + '&page_size=20'; + fetch(url).then(function(r) { return r.json(); }) + .then(function(d) { + if (!d.success) return; + totalPages = d.data.total_pages || 1; + commentsTotal.textContent = '(' + d.data.total + ')'; + + var items = d.data.items || []; + for (var i = 0; i < items.length; i++) { + commentsList.insertAdjacentHTML('beforeend', buildCommentCard(items[i])); + } + + if (page < totalPages) { + loadMoreBtn.style.display = 'block'; + loadMoreBtn.textContent = '加载更多'; + } else { + loadMoreBtn.style.display = 'none'; + } + bindEvents(); + }) + .catch(function() { + commentsList.innerHTML = '
评论加载失败
'; + }); + } + + // ====== 绑定事件 ====== + function bindEvents() { + var replyBtns = commentsList.querySelectorAll('.comment-reply-btn'); + for (var i = 0; i < replyBtns.length; i++) { + if (replyBtns[i]._bound) continue; + replyBtns[i]._bound = true; + bindReplyBtn(replyBtns[i]); + } + + var toggleBtns = commentsList.querySelectorAll('.comment-toggle-replies'); + for (var j = 0; j < toggleBtns.length; j++) { + if (toggleBtns[j]._bound) continue; + toggleBtns[j]._bound = true; + bindToggleBtn(toggleBtns[j]); + } + + var submitBtns = commentsList.querySelectorAll('.comment-reply-submit'); + for (var k = 0; k < submitBtns.length; k++) { + if (submitBtns[k]._bound) continue; + submitBtns[k]._bound = true; + bindReplySubmit(submitBtns[k]); + } + + var replyInputs = commentsList.querySelectorAll('.comment-reply-input'); + for (var m = 0; m < replyInputs.length; m++) { + if (replyInputs[m]._mentionBound) continue; + replyInputs[m]._mentionBound = true; + bindMention(replyInputs[m]); + } + } + + function bindReplyBtn(btn) { + btn.addEventListener('click', function() { + var id = btn.dataset.id; + var form = document.getElementById('replyForm-' + id); + if (form) { + form.style.display = form.style.display === 'none' ? 'flex' : 'none'; + if (form.style.display !== 'none') { + document.getElementById('replyInput-' + id).focus(); + } + } + }); + } + + function bindToggleBtn(btn) { + btn.addEventListener('click', function() { + var id = btn.dataset.id; + var loaded = btn.dataset.loaded === 'true'; + if (loaded) { + var replies = document.getElementById('replies-' + id); + if (replies) { + replies.style.display = replies.style.display === 'none' ? 'block' : 'none'; + btn.textContent = replies.style.display === 'none' ? '共 ' + (btn.dataset.count || replies.children.length) + ' 条回复' : '收起回复'; + } + return; + } + btn.dataset.loaded = 'true'; + btn.textContent = '加载中...'; + fetch('/api/posts/' + postId + '/comments/' + id + '/replies') + .then(function(r) { return r.json(); }) + .then(function(d) { + if (!d.success) return; + var replies = d.data.replies || []; + var container = document.getElementById('replies-' + id); + if (!container) return; + var html = ''; + for (var i = 0; i < replies.length; i++) { + html += buildReplyCard(replies[i]); + } + container.innerHTML = html; + btn.dataset.count = replies.length; + btn.textContent = '收起回复'; + }) + .catch(function() { + btn.textContent = '加载失败'; + }); + }); + } + + function bindReplySubmit(btn) { + btn.addEventListener('click', function() { + var id = btn.dataset.id; + var input = document.getElementById('replyInput-' + id); + if (!input) return; + var body = input.value.trim(); + if (!body) { alert('请输入回复内容'); return; } + + btn.disabled = true; + api('POST', '/api/comments/' + id + '/reply', { body: body }) + .then(function(d) { + btn.disabled = false; + if (d.success) { + input.value = ''; + document.getElementById('replyForm-' + id).style.display = 'none'; + var toggleBtn = document.getElementById('toggle-' + id); + if (toggleBtn) { + toggleBtn.dataset.loaded = 'false'; + if (toggleBtn.dataset.count) { + toggleBtn.dataset.count = parseInt(toggleBtn.dataset.count) + 1; + toggleBtn.textContent = '共 ' + toggleBtn.dataset.count + ' 条回复'; + } + toggleBtn.click(); + } else { + var replies = document.getElementById('replies-' + id); + if (replies) { + fetch('/api/posts/' + postId + '/comments/' + id + '/replies') + .then(function(r) { return r.json(); }) + .then(function(d2) { + if (!d2.success) return; + var items = d2.data.replies || []; + var html = ''; + for (var i = 0; i < items.length; i++) { + html += buildReplyCard(items[i]); + } + replies.innerHTML = html; + }); + } + } + updateTotalCount(1); + } else { + alert(d.message || '回复失败'); + } + }) + .catch(function() { + btn.disabled = false; + alert('请求失败'); + }); + }); + } + + // ====== @提及自动补全 ====== + var mentionTarget = null; + var mentionPos = 0; + var searchTimer = null; + + function bindMention(input) { + input.addEventListener('input', function() { + var val = input.value; + var cursor = input.selectionStart; + var atPos = val.lastIndexOf('@', cursor - 1); + if (atPos === -1 || (atPos > 0 && val[atPos - 1] !== ' ' && val[atPos - 1] !== '\n')) { + hideMention(); + return; + } + var query = val.substring(atPos + 1, cursor); + if (query.length === 0 || query.includes(' ')) { + hideMention(); + return; + } + + mentionTarget = input; + mentionPos = atPos; + + clearTimeout(searchTimer); + searchTimer = setTimeout(function() { + fetch('/api/users/search?q=' + encodeURIComponent(query)) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.success && d.data && d.data.length > 0) { + showMention(input, d.data, query); + } else { + hideMention(); + } + }) + .catch(function() { hideMention(); }); + }, 200); + }); + + input.addEventListener('blur', function() { + setTimeout(hideMention, 150); + }); + } + + function showMention(input, users, query) { + var rect = input.getBoundingClientRect(); + var dropdown = mentionDropdown; + dropdown.innerHTML = ''; + dropdown.style.display = 'block'; + dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px'; + dropdown.style.left = (rect.left + window.scrollX) + 'px'; + dropdown.style.width = Math.max(rect.width, 200) + 'px'; + + for (var i = 0; i < users.length; i++) { + (function(u) { + var item = document.createElement('div'); + item.className = 'mention-item'; + item.innerHTML = '' + escapeHtml(u.username) + '' + escapeHtml(u.uid) + ''; + item.addEventListener('mousedown', function(e) { + e.preventDefault(); + selectMention(u); + }); + dropdown.appendChild(item); + })(users[i]); + } + } + + function hideMention() { + mentionDropdown.style.display = 'none'; + mentionDropdown.innerHTML = ''; + mentionTarget = null; + } + + function selectMention(user) { + if (!mentionTarget) return; + var val = mentionTarget.value; + var before = val.substring(0, mentionPos); + var after = val.substring(mentionTarget.selectionStart); + var insert = '@' + user.username + ' '; + mentionTarget.value = before + insert + after; + mentionTarget.focus(); + var newPos = mentionPos + insert.length; + mentionTarget.setSelectionRange(newPos, newPos); + hideMention(); + } + + // ====== 发表评论 ====== + if (commentSubmitBtn) { + commentSubmitBtn.addEventListener('click', function() { + var body = commentInput.value.trim(); + if (!body) { alert('请输入评论内容'); return; } + + commentSubmitBtn.disabled = true; + api('POST', '/api/posts/' + postId + '/comments', { body: body }) + .then(function(d) { + commentSubmitBtn.disabled = false; + if (d.success) { + commentInput.value = ''; + commentsList.innerHTML = ''; + currentPage = 1; + loadComments(currentPage); + updateTotalCount(1); + } else { + alert(d.message || '发表失败'); + } + }) + .catch(function() { + commentSubmitBtn.disabled = false; + alert('请求失败'); + }); + }); + + bindMention(commentInput); + } + + // ====== 加载更多 ====== + loadMoreBtn.addEventListener('click', function() { + currentPage++; + loadComments(currentPage); + }); + + function updateTotalCount(delta) { + var el = commentsTotal; + var current = parseInt(el.textContent.replace(/[()]/g, '')) || 0; + el.textContent = '(' + (current + delta) + ')'; + } + + // ====== 图片上传 ====== + var imageUploadInput = document.getElementById('commentImageInput'); + var uploadLabel = document.getElementById('commentUploadLabel'); + var uploadPreview = document.getElementById('commentUploadPreview'); + + if (imageUploadInput && uploadLabel) { + uploadLabel.addEventListener('click', function() { + imageUploadInput.click(); + }); + + imageUploadInput.addEventListener('change', function() { + var file = imageUploadInput.files[0]; + if (!file) return; + uploadLabel.style.pointerEvents = 'none'; + uploadLabel.style.opacity = '0.5'; + uploadLabel.textContent = '上传中...'; + + var formData = new FormData(); + formData.append('file', file); + + fetch('/api/comments/upload-image', { + method: 'POST', + headers: { 'X-CSRF-Token': getCSRFToken() }, + body: formData + }) + .then(function(r) { return r.json(); }) + .then(function(d) { + uploadLabel.style.pointerEvents = 'auto'; + uploadLabel.style.opacity = '1'; + uploadLabel.innerHTML = ' 图片'; + + if (d.success && d.data && d.data.url) { + var imgUrl = '\n![](' + d.data.url + ')\n'; + var input = commentInput; + var start = input.selectionStart; + var end = input.selectionEnd; + input.value = input.value.substring(0, start) + imgUrl + input.value.substring(end); + input.focus(); + input.selectionStart = input.selectionEnd = start + imgUrl.length; + } else { + alert(d.message || '上传失败'); + } + }) + .catch(function() { + uploadLabel.style.pointerEvents = 'auto'; + uploadLabel.style.opacity = '1'; + uploadLabel.innerHTML = ' 图片'; + alert('上传失败'); + }); + + imageUploadInput.value = ''; + }); + } + + // ====== 评论高亮 ====== + function highlightComment(commentId) { + var card = commentsList.querySelector('.comment-card[data-id="' + commentId + '"]') || + commentsList.querySelector('.comment-reply-card[data-id="' + commentId + '"]'); + if (card) { + commentsList.insertBefore(card, commentsList.firstChild); + card.scrollIntoView({ behavior: 'smooth', block: 'start' }); + card.style.background = '#fff8e1'; + card.style.transition = 'background 1.5s'; + setTimeout(function() { card.style.background = ''; }, 2000); + return true; + } + return false; + } + + var hash = window.location.hash; + var commentMatch = hash.match(/^#comment-(\d+)$/); + if (commentMatch) { + var targetCommentId = commentMatch[1]; + var checkInterval = setInterval(function() { + if (highlightComment(targetCommentId)) { + clearInterval(checkInterval); + } + }, 300); + setTimeout(function() { + clearInterval(checkInterval); + if (!commentsList.querySelector('.comment-card[data-id="' + targetCommentId + '"]') && + !commentsList.querySelector('.comment-reply-card[data-id="' + targetCommentId + '"]')) { + showToast('该评论已不存在'); + } + }, 3000); + } else if (hash === '#comments') { + setTimeout(function() { + var section = document.getElementById('comments'); + if (section) { + section.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, 400); + } + + // ====== 初始化 ====== + loadComments(currentPage); +})(); diff --git a/templates/MetaLab-2026/static/js/post-energize.js b/templates/MetaLab-2026/static/js/post-energize.js new file mode 100644 index 0000000..5ea167b --- /dev/null +++ b/templates/MetaLab-2026/static/js/post-energize.js @@ -0,0 +1,76 @@ +// 赋能按钮 + 弹窗 +(function() { + var btn = document.getElementById('energizeBtn'); + var overlay = document.getElementById('energizeOverlay'); + var closeBtn = document.getElementById('energizeClose'); + var capHint = document.getElementById('energizeCapHint'); + var lightBtn = document.getElementById('energizeLight'); + var heavyBtn = document.getElementById('energizeHeavy'); + if (!btn || !overlay) return; + + var postId = btn.dataset.id; + + function showOverlay() { + overlay.style.display = 'flex'; + } + function hideOverlay() { + overlay.style.display = 'none'; + capHint.style.display = 'none'; + lightBtn.disabled = false; + heavyBtn.disabled = false; + } + + btn.addEventListener('click', function() { + fetch('/api/energy/info') + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.success && d.data && d.data.daily_exp_cap_reached) { + capHint.style.display = 'block'; + } + showOverlay(); + }) + .catch(function() { showOverlay(); }); + }); + + closeBtn.addEventListener('click', hideOverlay); + overlay.addEventListener('click', function(e) { + if (e.target === overlay) hideOverlay(); + }); + + function doEnergize(amount) { + lightBtn.disabled = true; + heavyBtn.disabled = true; + + fetch('/api/energy/energize', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': getCSRFToken() + }, + body: JSON.stringify({ post_id: parseInt(postId), amount: amount }) + }) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.success) { + alert(d.data.message || '赋能成功!'); + hideOverlay(); + } else { + alert(d.message || '赋能失败'); + lightBtn.disabled = false; + heavyBtn.disabled = false; + } + }) + .catch(function() { + alert('请求失败'); + lightBtn.disabled = false; + heavyBtn.disabled = false; + }); + } + + lightBtn.addEventListener('click', function() { + doEnergize(parseInt(lightBtn.dataset.amount)); + }); + heavyBtn.addEventListener('click', function() { + doEnergize(parseInt(heavyBtn.dataset.amount)); + }); +})(); diff --git a/templates/MetaLab-2026/static/js/post-favorite.js b/templates/MetaLab-2026/static/js/post-favorite.js new file mode 100644 index 0000000..ea5b053 --- /dev/null +++ b/templates/MetaLab-2026/static/js/post-favorite.js @@ -0,0 +1,218 @@ +// 收藏按钮(弹窗选择收藏夹) +(function() { + var favBtn = document.getElementById('favBtn'); + var favCountEl = document.getElementById('favCount'); + var container = document.getElementById('postReactions'); + var overlay = document.getElementById('favOverlay'); + var closeBtn = document.getElementById('favClose'); + var folderListEl = document.getElementById('favFolderCheckList'); + var newInput = document.getElementById('favNewInput'); + var newBtn = document.getElementById('favNewConfirm'); + if (!favBtn || !container) return; + + var postId = container.dataset.postId; + var isFavorited = false; + var currentFolderId = null; + var loading = false; + + function updateBtn(favorited) { + isFavorited = favorited; + if (favorited) { + favBtn.classList.add('active'); + } else { + favBtn.classList.remove('active'); + currentFolderId = null; + } + } + + function loadStatus() { + return fetch('/api/posts/' + postId + '/folder-status') + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.success && d.data && d.data.favorited) { + currentFolderId = d.data.folder_id; + updateBtn(true); + } + }) + .catch(function() {}); + } + + function renderFolderList(folders) { + if (!folders || folders.length === 0) { + folderListEl.innerHTML = '
暂无收藏夹,请在下方创建
'; + return; + } + var html = ''; + folders.forEach(function(f) { + var isActive = currentFolderId === f.id; + html += '
' + + '' + escapeHtml(f.name) + (f.is_default ? ' (默认)' : '') + '' + + '' + (f.item_count || 0) + ' 篇' + + '
'; + }); + folderListEl.innerHTML = html; + } + + function showModal() { + if (!overlay) { handleNoModal(); return; } + loading = true; + overlay.style.display = 'flex'; + folderListEl.innerHTML = '
加载中...
'; + + api('GET', '/api/folders') + .then(function(d) { + var folders = (d.success && d.data && d.data.folders) ? d.data.folders : []; + renderFolderList(folders); + loading = false; + }) + .catch(function() { + folderListEl.innerHTML = '
加载失败,请重试
'; + loading = false; + }); + } + + function hideModal() { + if (overlay) overlay.style.display = 'none'; + loading = false; + } + + function handleNoModal() { + if (!getCSRFToken()) { + window.location.href = '/auth/login?redirect=/posts/' + postId; + return; + } + favBtn.disabled = true; + api('POST', '/api/posts/' + postId + '/favorite') + .then(function(d) { + favBtn.disabled = false; + if (d.success) { + var f = d.data && d.data.favorited; + updateBtn(f); + if (favCountEl && d.data && typeof d.data.favorites_count === 'number') { + favCountEl.textContent = d.data.favorites_count; + } else if (favCountEl) { + var cur = parseInt(favCountEl.textContent) || 0; + favCountEl.textContent = f ? cur + 1 : Math.max(0, cur - 1); + } + } + }) + .catch(function() { favBtn.disabled = false; }); + } + + function addToFolder(folderId) { + if (loading) return; + loading = true; + api('POST', '/api/folders/' + folderId + '/posts', { post_id: parseInt(postId) }) + .then(function(d) { + loading = false; + if (d.success) { + currentFolderId = folderId; + updateBtn(true); + if (favCountEl) favCountEl.textContent = (parseInt(favCountEl.textContent) || 0) + 1; + hideModal(); + } else { + alert(d.message || '操作失败'); + } + }) + .catch(function() { loading = false; }); + } + + function removeFromFolder(folderId) { + if (loading) return; + if (!confirm('确定取消收藏吗?')) return; + loading = true; + api('DELETE', '/api/folders/' + folderId + '/posts/' + postId) + .then(function(d) { + loading = false; + if (d.success) { + currentFolderId = null; + updateBtn(false); + if (favCountEl) favCountEl.textContent = Math.max(0, (parseInt(favCountEl.textContent) || 0) - 1); + hideModal(); + } else { + alert(d.message || '操作失败'); + } + }) + .catch(function() { loading = false; }); + } + + function createAndAdd(name) { + if (loading) return; + loading = true; + newBtn.disabled = true; + newBtn.textContent = '创建中...'; + api('POST', '/api/folders', { name: name, description: '', is_public: false }) + .then(function(d) { + if (!d.success) { + loading = false; + newBtn.disabled = false; + newBtn.textContent = '创建并收藏'; + alert(d.message || '创建失败'); + return; + } + var newId = d.data && d.data.id; + return api('POST', '/api/folders/' + newId + '/posts', { post_id: parseInt(postId) }) + .then(function(d2) { + loading = false; + newBtn.disabled = false; + newBtn.textContent = '创建并收藏'; + if (d2.success) { + currentFolderId = newId; + updateBtn(true); + if (favCountEl) favCountEl.textContent = (parseInt(favCountEl.textContent) || 0) + 1; + hideModal(); + } else { + alert(d2.message || '操作失败'); + } + }); + }) + .catch(function() { + loading = false; + newBtn.disabled = false; + newBtn.textContent = '创建并收藏'; + }); + } + + // 事件:点击收藏按钮 → 打开弹窗 + favBtn.addEventListener('click', function() { + if (!getCSRFToken()) { window.location.href = '/auth/login?redirect=/posts/' + postId; return; } + showModal(); + }); + + // 事件:关闭弹窗 + if (closeBtn) closeBtn.addEventListener('click', hideModal); + if (overlay) overlay.addEventListener('click', function(e) { + if (e.target === overlay) hideModal(); + }); + + // 事件:点击收藏夹选项 + if (folderListEl) { + folderListEl.addEventListener('click', function(e) { + var option = e.target.closest('.fav-folder-option'); + if (!option) return; + var folderId = parseInt(option.getAttribute('data-id')); + if (isNaN(folderId)) return; + if (currentFolderId === folderId) { + removeFromFolder(folderId); + } else { + addToFolder(folderId); + } + }); + } + + // 事件:新建收藏夹 + if (newBtn && newInput) { + newBtn.addEventListener('click', function() { + var name = newInput.value.trim(); + if (!name) { alert('请输入收藏夹名称'); return; } + if (name.length > 50) { alert('收藏夹名称不能超过 50 个字符'); return; } + createAndAdd(name); + }); + newInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') newBtn.click(); + }); + } + + // 初始化 + loadStatus(); +})(); diff --git a/templates/MetaLab-2026/static/js/post-reactions.js b/templates/MetaLab-2026/static/js/post-reactions.js new file mode 100644 index 0000000..a5b6917 --- /dev/null +++ b/templates/MetaLab-2026/static/js/post-reactions.js @@ -0,0 +1,71 @@ +// 赞/踩系统 +(function() { + var container = document.getElementById('postReactions'); + var likeBtn = document.getElementById('likeBtn'); + var dislikeBtn = document.getElementById('dislikeBtn'); + var likesCountEl = document.getElementById('likesCount'); + if (!container || !likeBtn || !dislikeBtn) return; + + var postId = container.dataset.postId; + + // 页面加载时获取当前反应状态 + fetch('/api/posts/' + postId + '/reaction') + .then(function(r) { return r.json(); }) + .then(function(d) { + if (!d.success || !d.data) return; + updateUI(d.data.reaction, d.data.likes_count); + }) + .catch(function() {}); + + function updateUI(reaction, count) { + likeBtn.classList.remove('active'); + dislikeBtn.classList.remove('active'); + if (reaction === 'liked') likeBtn.classList.add('active'); + if (reaction === 'disliked') dislikeBtn.classList.add('active'); + if (typeof count === 'number') likesCountEl.textContent = count; + } + + likeBtn.addEventListener('click', function() { + likeBtn.disabled = true; + fetch('/api/posts/' + postId + '/like', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCSRFToken() } + }) + .then(function(r) { + if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; } + return r.json(); + }) + .then(function(d) { + likeBtn.disabled = false; + if (!d) return; + if (d.success) { + updateUI(d.data.liked ? 'liked' : 'none', d.data.likes_count); + } else { + alert(d.message || '操作失败'); + } + }) + .catch(function() { likeBtn.disabled = false; }); + }); + + dislikeBtn.addEventListener('click', function() { + dislikeBtn.disabled = true; + fetch('/api/posts/' + postId + '/dislike', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCSRFToken() } + }) + .then(function(r) { + if (r.status === 401) { window.location.href = '/auth/login?redirect=/posts/' + postId; return null; } + return r.json(); + }) + .then(function(d) { + dislikeBtn.disabled = false; + if (!d) return; + if (d.success) { + updateUI(d.data.disliked ? 'disliked' : 'none'); + } else { + alert(d.message || '操作失败'); + } + }) + .catch(function() { dislikeBtn.disabled = false; }); + }); +})(); diff --git a/templates/MetaLab-2026/static/js/post-view.js b/templates/MetaLab-2026/static/js/post-view.js new file mode 100644 index 0000000..edbc1c0 --- /dev/null +++ b/templates/MetaLab-2026/static/js/post-view.js @@ -0,0 +1,48 @@ +// Vditor Markdown 渲染 + 外部链接处理 + 提交审核按钮 +(function() { + // ====== Vditor 渲染 ====== + var el = document.getElementById('postContent'); + var mdEl = document.getElementById('postMdContent'); + if (el && mdEl) { + var md = mdEl.value; + Vditor.preview(el, md, { + cdn: '/static/vditor', + theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' }, + hljs: { style: 'github-dark', enable: true }, + after: function() { + var host = window.location.host; + var links = el.querySelectorAll('a[href^="http"]'); + for (var i = 0; i < links.length; i++) { + var a = links[i]; + if (a.host && a.host !== host) { + a.setAttribute('rel', 'nofollow noopener noreferrer'); + a.setAttribute('target', '_blank'); + } + } + } + }); + // shortcode 占位 div 替换为对应卡片 UI + renderShortcodes(el); + } + + // ====== 提交审核按钮 ====== + var submitBtn = document.getElementById('submitAuditBtn'); + if (submitBtn) { + submitBtn.addEventListener('click', function() { + if (!confirm('确认提交审核?审核通过后将公开发布。')) return; + fetch('/api/posts/' + submitBtn.dataset.id + '/submit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': getCSRFToken() + } + }) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.success) { window.location.reload(); } + else { alert(d.message || '提交失败'); } + }) + .catch(function() { alert('请求失败'); }); + }); + } +})();