(function (root, factory) { // 家族圈动态模块同时支持浏览器页面和 Node 单元测试。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.FeedPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.FeedPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; function confirmAction(message) { if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') { return root.ProfileUI.confirmAction(message); } return Promise.resolve(!root['confirm'] || root['confirm'](message)); } var MediaDisplay = root.MediaDisplay || (typeof require === 'function' ? require('./media-display.js') : null); var documentRef = root.document; var PAGE_SIZE = 20; var currentFeedPage = 1; var currentCommentPages = {}; var currentReplyPages = {}; var pendingActions = {}; var viewerContext = { userId: '', canEditContent: false, canManage: false }; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function shouldRedirectToLogin(api, error) { var status = error && (error.status || error.code); return !api || !api.getToken || !api.getToken() || Number(status) === 401; } function redirectUnauthorized(api, error) { if (!shouldRedirectToLogin(api, error)) return false; if (api && api.clearToken) api.clearToken(); if (!root.location) return true; if (typeof root.location.replace === 'function') { root.location.replace('login.html'); return true; } root.location.href = 'login.html'; return true; } function query(selector, rootNode) { return documentRef ? (rootNode || documentRef).querySelector(selector) : null; } function queryAll(selector, rootNode) { return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : []; } function showMessage(message) { if (root.layui && root.layui.layer) { root.layui.layer.msg(message); return; } if (root.alert) root.alert(message); } function getApiStateType(error) { return Number(error && (error.status || error.code)) === 403 ? 'forbidden' : 'error'; } function setFeedFormStatus(message, type) { var target = query('[data-feed-form-status]'); if (!target) return; if (!message) { target.innerHTML = ''; return; } if (root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(target, type || 'error', message); return; } target.textContent = message; } function setFeedContainerState(selector, type, message) { var target = query(selector); if (!target) return; if (root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(target, type, message); return; } target.innerHTML = '
' + escapeHtml(message) + '
'; } function setFeedFormEnabled(enabled) { var form = query('[data-feed-form]'); if (form) { queryAll('input, textarea, select, button', form).forEach(function (field) { field.disabled = !enabled; }); } queryAll('[data-feed-editor-action]').forEach(function (action) { action.hidden = !enabled; action.disabled = !enabled; }); } function setFeedSubmitPending(pending) { var selectors = '[data-feed-form] [type="submit"], [type="submit"][form="feed-edit-form"]'; queryAll(selectors).forEach(function (button) { if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent; button.disabled = Boolean(pending); button.textContent = pending ? '正在保存…' : button.dataset.defaultLabel; button.setAttribute('aria-busy', pending ? 'true' : 'false'); }); } function setFeedCommentFormStatus(message, type) { var target = query('[data-feed-comment-form-status]'); if (!target) return; if (!message) { target.innerHTML = ''; return; } if (root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(target, type || 'error', message); return; } target.textContent = message; } function setFeedCommentFormEnabled(enabled) { var form = query('[data-feed-comment-form]'); if (!form) return; queryAll('input, textarea, button', form).forEach(function (field) { field.disabled = !enabled; }); } function setFeedCommentSubmitPending(pending) { queryAll('[data-feed-comment-form] [type="submit"]').forEach(function (button) { if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent; button.textContent = pending ? '正在发布…' : button.dataset.defaultLabel; button.setAttribute('aria-busy', pending ? 'true' : 'false'); }); } function setFeedCommentTarget(parentCommentId) { var form = query('[data-feed-comment-form]'); var target = query('[data-feed-comment-form-target]'); var parent = parentCommentId ? toId(parentCommentId) : ''; if (!form) return; if (query('[name="parentCommentId"]', form)) query('[name="parentCommentId"]', form).value = parent; if (target) target.textContent = parent ? '正在回复这条评论' : '发表公开评论'; } function openFeedCommentForm(feedId, parentCommentId) { var genealogyId = getCurrentGenealogyId(); var form; var submitButton; if (!feedId || !genealogyId) return; if (!query('[data-feed-detail-page]')) { if (root.location) root.location.href = buildFeedUrl('detail', genealogyId, feedId); return; } if (getCurrentFeedId() !== toId(feedId)) return; form = query('[data-feed-comment-form]'); submitButton = form && query('[type="submit"]', form); if (!form || !submitButton || submitButton.disabled) return; setFeedCommentTarget(parentCommentId); setFeedCommentFormStatus(''); if (query('[name="commentContent"]', form) && query('[name="commentContent"]', form).focus) { query('[name="commentContent"]', form).focus(); } } function getQueryParam(search, name) { var params = new URLSearchParams(String(search || '').replace(/^\?/, '')); return params.get(name) || ''; } function getCurrentGenealogyId(search) { var page = query('[data-feed-page], [data-feed-edit-page], [data-feed-detail-page]'); var source = search === undefined && root.location ? root.location.search : search; var profileGenealogyId; if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) { profileGenealogyId = root.ProfileUI.getGenealogyId(); if (profileGenealogyId) return profileGenealogyId; } return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || ''; } function getCurrentFeedId(search) { var source = search === undefined && root.location ? root.location.search : search; return getQueryParam(source, 'feedId'); } function trimOrUndefined(value) { var text = String(value === undefined || value === null ? '' : value).trim(); return text || undefined; } function toIntegerOrUndefined(value) { var text = trimOrUndefined(value); var number; if (!text) return undefined; number = Number(text); return Number.isSafeInteger(number) && number >= 0 ? number : undefined; } function toId(value) { var text; if (value === undefined || value === null || value === '') return ''; text = String(value); return /^\d+$/.test(text) ? text : ''; } function setDefined(target, source, fields) { fields.forEach(function (field) { if (source[field] !== undefined && source[field] !== null) target[field] = source[field]; }); return target; } function buildPageQuery(pageNum) { var number = Number(pageNum); if (!Number.isSafeInteger(number) || number < 1) number = 1; return { pageNum: number, pageSize: PAGE_SIZE }; } function buildFeedUrl(kind, genealogyId, feedId) { var params = new URLSearchParams(); var page = 'profile-feed.html'; params.set('genealogyId', toId(genealogyId)); if (kind === 'detail' || kind === 'edit') { params.set('feedId', toId(feedId)); page = kind === 'detail' ? 'profile-feed-detail.html' : 'profile-feed-edit.html'; } return page + '?' + params.toString(); } function buildPostSaveUrl(genealogyId, feedId, status) { return String(status || '0') === '1' ? buildFeedUrl('list', genealogyId) : buildFeedUrl('detail', genealogyId, feedId); } function withActionLock(key, task) { var promise; if (pendingActions[key]) return pendingActions[key]; try { promise = Promise.resolve(task()); } catch (error) { promise = Promise.reject(error); } pendingActions[key] = promise.finally(function () { delete pendingActions[key]; }); return pendingActions[key]; } function buildFeedBody(values) { // FamilyFeedBody 只提交最新接口文档定义的字段。 var source = values || {}; var mediaOssIds = trimOrUndefined(source.mediaOssIds); var status = trimOrUndefined(source.status); var body = { feedType: trimOrUndefined(source.feedType) || 'text', feedContent: String(source.feedContent || '').trim() }; var sortOrder = toIntegerOrUndefined(source.sortOrder); if (status !== undefined && status !== '0' && status !== '1') { throw new Error('动态状态只能是 0 或 1'); } if (trimOrUndefined(source.sortOrder) !== undefined && sortOrder === undefined) { throw new Error('排序值必须是非负整数'); } if (mediaOssIds !== undefined) body.mediaOssIds = mediaOssIds; if (status !== undefined) body.status = status; if (sortOrder !== undefined) body.sortOrder = sortOrder; return body; } function buildCommentBody(values) { // FamilyFeedCommentBody 仅接受 commentContent 和可选的 parentCommentId。 var source = values || {}; var body = { commentContent: String(source.commentContent || '').trim() }; var parentCommentId = trimOrUndefined(source.parentCommentId); if (parentCommentId !== undefined) body.parentCommentId = parentCommentId; return body; } function getFeedId(item) { return toId(item && item.feedId); } function getCommentId(item) { return toId(item && item.commentId); } function normalizeFeed(item) { var feedId = getFeedId(item); var feedContent = item && item.feedContent; var feed; if (!feedId || feedContent === undefined || feedContent === null) return null; feed = setDefined({ feedId: feedId, feedContent: String(feedContent) }, item, [ 'genealogyNo', 'genealogyName', 'publisherNickName', 'publisherStatus', 'feedType', 'likedByMe', 'likeCount', 'commentCount', 'pinned', 'pinnedTime', 'sortOrder', 'status', 'remark', 'createTime', 'updateTime' ]); var mediaFiles = MediaDisplay ? MediaDisplay.normalizeFileList(item && item.mediaFiles) : []; if (mediaFiles.length) feed.mediaFiles = mediaFiles; if (toId(item.genealogyId)) feed.genealogyId = toId(item.genealogyId); if (toId(item.publisherUserId)) feed.publisherUserId = toId(item.publisherUserId); return feed; } function normalizeComment(item) { var commentId = getCommentId(item); var commentContent = item && item.commentContent; var userDeleted; if (!commentId || commentContent === undefined) return null; userDeleted = String(item && item.userDeleted || '') === '1'; var comment = { commentId: commentId, commentContent: commentContent === null ? '该评论已删除' : String(commentContent), appUserNickName: trimOrUndefined(item && item.appUserNickName), replyCount: item && item.replyCount, userDeleted: userDeleted || commentContent === null }; setDefined(comment, item, [ 'appUserAvatar', 'parentAppUserNickName', 'commentLevel', 'status', 'createTime' ]); ['genealogyId', 'feedId', 'parentCommentId', 'appUserId', 'parentAppUserId'].forEach(function (field) { if (toId(item && item[field])) comment[field] = toId(item[field]); }); return comment; } function normalizeList(data) { // AxiosRequestUtil 已解包 ListResult 与 PageResult,这里只接收对应的 data 或 rows。 if (Array.isArray(data)) return data; if (data && Array.isArray(data.rows)) return data.rows; return []; } function escapeHtml(value) { return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function renderFeedState(type, message) { if (root.ProfileUI && root.ProfileUI.renderApiState) { return root.ProfileUI.renderApiState(type, message); } return '
' + escapeHtml(message) + '
'; } function replaceInlineCommentList(feedId, content) { var row = query('[data-feed-id="' + feedId + '"]'); var previous = row && row.nextElementSibling; if (!row) return; if (previous && previous.getAttribute('data-feed-comment-list') === feedId) previous.remove(); row.insertAdjacentHTML('afterend', '
' + content + '
'); } function replaceReplyList(parentCommentId, content) { var row = query('[data-feed-comment-node="' + parentCommentId + '"]'); var previous = row && row.nextElementSibling; if (!row) return; if (previous && previous.getAttribute('data-feed-reply-list') === parentCommentId) previous.remove(); row.insertAdjacentHTML('afterend', content); } function setReplyState(parentCommentId, type, message) { replaceReplyList( parentCommentId, '
' + renderFeedState(type, message) + '
' ); } function getListUrl(genealogyId, feedId) { return buildFeedUrl(feedId ? 'edit' : 'list', genealogyId, feedId); } function renderFeedRow(feed, genealogyId) { var canDelete = viewerContext.userId && viewerContext.userId === feed.publisherUserId; var mediaCount = Array.isArray(feed.mediaFiles) ? feed.mediaFiles.length : 0; var title = feed.publisherNickName || feed.genealogyName || '家族动态'; var meta = []; var actions = ''; if (feed.pinned === true || String(feed.pinned) === '1') meta.push('置顶'); if (feed.createTime) meta.push(feed.createTime); if (mediaCount) meta.push('附件 ' + mediaCount); meta.push('点赞 ' + Number(feed.likeCount || 0)); meta.push('评论 ' + Number(feed.commentCount || 0)); actions += feed.likedByMe ? '' : ''; actions += '查看详情'; actions += ''; actions += ''; if (viewerContext.canEditContent) { actions += '编辑'; } if (canDelete) { actions += ''; } return '
' + '

' + escapeHtml(title) + '

' + escapeHtml(feed.feedContent) + '

' + (MediaDisplay ? MediaDisplay.renderGallery(feed.mediaFiles, { label: '动态附件', className: 'feed-media-gallery' }) : '') + '

' + escapeHtml(meta.join(' · ')) + '

' + '
' + actions + '
'; } function renderFeeds(data, genealogyId) { var container = query('[data-feed-list]'); var items = normalizeList(data).map(normalizeFeed); var invalidCount = items.filter(function (item) { return !item; }).length; var feeds = items.filter(Boolean); if (!container) return; if (invalidCount) { setFeedContainerState('[data-feed-list]', 'error', '动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。'); return; } if (!feeds.length) { setFeedContainerState('[data-feed-list]', 'empty', '暂无家族动态'); return; } container.innerHTML = feeds.map(function (feed) { return renderFeedRow(feed, genealogyId); }).join(''); } function getPageTotal(data) { var total = Number(data && data.total); return Number.isFinite(total) && total >= 0 ? total : 0; } function updateListPager(data) { var total = getPageTotal(data); var totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); var previous = query('[data-feed-page-prev]'); var next = query('[data-feed-page-next]'); var label = query('[data-feed-page-label]'); if (previous) previous.disabled = currentFeedPage <= 1; if (next) next.disabled = currentFeedPage >= totalPages; if (label) label.textContent = '第 ' + currentFeedPage + ' / ' + totalPages + ' 页,共 ' + total + ' 条'; } function hasReplies(comment) { var count = comment && comment.replyCount; return count !== undefined && count !== null && String(count) !== '' && String(count) !== '0'; } function renderCommentRow(feedId, comment) { var author = comment.appUserNickName ? '' + escapeHtml(comment.appUserNickName) + ' ' : ''; var actions = ''; var canDelete = viewerContext.userId && viewerContext.userId === comment.appUserId; if (!comment.userDeleted) { actions += ''; if (canDelete) { actions += ''; } } if (hasReplies(comment)) { actions += ''; } return '

' + author + escapeHtml(comment.commentContent) + '

' + (comment.createTime ? '

' + escapeHtml(comment.createTime) + '

' : '') + actions + '
'; } function normalizeComments(data) { var comments = normalizeList(data).map(normalizeComment); return { invalidCount: comments.filter(function (item) { return !item; }).length, comments: comments.filter(Boolean) }; } function renderComments(feedId, data) { var detailContainer = query('[data-feed-detail-comments]'); var row = query('[data-feed-id="' + feedId + '"]'); var result = normalizeComments(data); var list = result.comments; var content; if (!row && !detailContainer) return; if (result.invalidCount) { content = renderFeedState('error', '评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。'); if (detailContainer) setFeedContainerState('[data-feed-detail-comments]', 'error', '评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。'); else replaceInlineCommentList(feedId, content); return; } if (!list.length) { content = renderFeedState('empty', '暂无评论'); if (detailContainer) setFeedContainerState('[data-feed-detail-comments]', 'empty', '暂无评论'); else replaceInlineCommentList(feedId, content); return; } content = '
' + list.map(function (comment) { return renderCommentRow(feedId, comment); }).join('') + '
' + renderPageControls('comments', feedId, '', currentCommentPages[feedId] || 1, getPageTotal(data)); if (detailContainer) detailContainer.innerHTML = content; else replaceInlineCommentList(feedId, content); } function renderCommentReplies(feedId, parentCommentId, data) { var row = query('[data-feed-comment-node="' + parentCommentId + '"]'); var result = normalizeComments(data); if (!row) return; if (result.invalidCount) { setReplyState(parentCommentId, 'error', '回复响应缺少 commentId 或 commentContent,请联系后端补充 DTO。'); return; } if (!result.comments.length) { setReplyState(parentCommentId, 'empty', '暂无直接回复'); return; } replaceReplyList(parentCommentId, '
' + result.comments.map(function (comment) { return renderCommentRow(feedId, comment); }).join('') + renderPageControls('replies', feedId, parentCommentId, currentReplyPages[parentCommentId] || 1, getPageTotal(data)) + '
'); } function renderPageControls(kind, feedId, commentId, pageNum, total) { var totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); var prefix = kind === 'comments' ? 'data-feed-comments-' : 'data-feed-replies-'; var ids = ' data-feed-id="' + escapeHtml(feedId) + '"' + (commentId ? ' data-comment-id="' + escapeHtml(commentId) + '"' : ''); return '
' + '' + '第 ' + pageNum + ' / ' + totalPages + ' 页' + '' + '
'; } function getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { if (field.disabled) return; values[field.name] = field.value; }); return values; } function fillFeedForm(data) { var feed = normalizeFeed(data); if (!feed) throw new Error('动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。'); queryAll('[data-feed-form] [name]').forEach(function (field) { if (feed[field.name] !== undefined && feed[field.name] !== null) field.value = feed[field.name]; }); } function canEditFeeds(genealogy) { return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true)); } function setFeedContentEditingEnabled(enabled) { queryAll('[data-feed-create-link]').forEach(function (link) { link.hidden = !enabled; }); } function normalizeViewerContext(genealogy, profile) { return { userId: toId(profile && profile.userId), canEditContent: canEditFeeds(genealogy), canManage: Boolean(genealogy && genealogy.canManage === true) }; } async function loadViewerContext(api, genealogyId) { var genealogy; var profile; try { genealogy = await api.genealogyDetail(genealogyId); } catch (error) { genealogy = null; } try { profile = await api.currentProfile(); } catch (error) { profile = null; } viewerContext = normalizeViewerContext(genealogy, profile); setFeedContentEditingEnabled(viewerContext.canEditContent); return viewerContext; } function renderFeedDetail(feed, genealogyId) { var container = query('[data-feed-detail]'); var canDelete = viewerContext.userId && viewerContext.userId === feed.publisherUserId; var actions = ''; if (!container) return; actions += feed.likedByMe ? '' : ''; actions += ''; actions += ''; if (viewerContext.canEditContent) { actions += '编辑'; } if (canDelete) { actions += ''; } container.setAttribute('data-feed-id', feed.feedId); container.innerHTML = '

' + escapeHtml(feed.publisherNickName || feed.genealogyName || '家族动态') + '

' + '

' + escapeHtml(feed.feedContent) + '

' + (MediaDisplay ? MediaDisplay.renderGallery(feed.mediaFiles, { label: '动态附件', className: 'feed-media-gallery' }) : '') + '

' + escapeHtml([ feed.createTime || '', '点赞 ' + Number(feed.likeCount || 0), '评论 ' + Number(feed.commentCount || 0), String(feed.status) === '1' ? '已停用' : '正常' ].filter(Boolean).join(' · ')) + '

' + actions + '
'; } function syncGenealogyLinks(genealogyId) { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) { root.ProfileUI.syncGenealogyContextLinks(); } queryAll('[data-feed-create-link], [data-feed-list-link]').forEach(function (link) { link.href = link.hasAttribute('data-feed-create-link') ? buildFeedUrl('edit', genealogyId) : buildFeedUrl('list', genealogyId); }); } function requireGenealogyId() { var genealogyId = getCurrentGenealogyId(); var container; if (!genealogyId) { container = query('[data-feed-list]'); if (container) setFeedContainerState('[data-feed-list]', 'forbidden', '请从具体家谱进入家族圈动态。'); setFeedFormEnabled(false); setFeedFormStatus('请从具体家谱进入动态发布。', 'forbidden'); setFeedCommentFormEnabled(false); setFeedCommentFormStatus('请从具体家谱进入动态详情后发表评论。', 'forbidden'); showMessage('请从具体家谱进入家族圈动态'); } return genealogyId; } async function loadFeeds(pageNum) { var api = getApi(); var genealogyId; var data; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncGenealogyLinks(genealogyId); currentFeedPage = buildPageQuery(pageNum || getQueryParam(root.location && root.location.search, 'pageNum')).pageNum; setFeedContainerState('[data-feed-list]', 'loading', '正在加载家族动态…'); try { await loadViewerContext(api, genealogyId); data = await api.feedsPage(genealogyId, buildPageQuery(currentFeedPage)); renderFeeds(data, genealogyId); updateListPager(data); } catch (error) { if (redirectUnauthorized(api, error)) return; setFeedContainerState( '[data-feed-list]', getApiStateType(error), getApiStateType(error) === 'forbidden' ? '当前账号无权查看该家谱的动态。' : '家族动态加载失败,请稍后重试。' ); showMessage(error.message || '家族动态加载失败'); } } async function loadFeedForEdit() { var api = getApi(); var genealogyId; var feedId = getCurrentFeedId(); var isEditing = Boolean(feedId); var editorTitle = isEditing ? '编辑动态' : '发布动态'; if (root.document) root.document.title = editorTitle + ' - 个人中心 - 代代相传'; if (query('[data-feed-editor-kicker]')) query('[data-feed-editor-kicker]').textContent = editorTitle; if (query('[data-feed-editor-title]')) query('[data-feed-editor-title]').textContent = editorTitle; queryAll('[data-feed-editor-action]').forEach(function (button) { button.textContent = isEditing ? '保存修改' : '发布动态'; }); if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; syncGenealogyLinks(genealogyId); setFeedFormEnabled(false); setFeedFormStatus('正在读取家谱权限…', 'loading'); try { await loadViewerContext(api, genealogyId); if (!viewerContext.canEditContent) { setFeedFormStatus('当前账号无权发布或编辑该家谱的动态。', 'forbidden'); return; } setFeedFormEnabled(true); if (query('[data-feed-advanced]')) { query('[data-feed-advanced]').hidden = !viewerContext.canManage; queryAll('[name]', query('[data-feed-advanced]')).forEach(function (field) { field.disabled = !viewerContext.canManage; }); } if (!feedId) { setFeedFormStatus(''); return; } fillFeedForm(await api.feedDetail(genealogyId, feedId)); setFeedFormStatus(''); } catch (error) { if (redirectUnauthorized(api, error)) return; setFeedFormStatus( getApiStateType(error) === 'forbidden' ? '当前账号无权维护该家谱的动态。' : (error.message || '动态详情加载失败'), getApiStateType(error) ); } } async function loadFeedDetail() { var api = getApi(); var genealogyId; var feedId = getCurrentFeedId(); var feed; if (redirectUnauthorized(api)) return; setFeedCommentFormEnabled(false); setFeedCommentFormStatus('正在读取动态评论权限…', 'loading'); genealogyId = requireGenealogyId(); if (!genealogyId || !feedId) { setFeedContainerState('[data-feed-detail]', 'error', '动态链接缺少有效编号。'); setFeedCommentFormStatus('动态链接缺少有效编号。', 'error'); return; } syncGenealogyLinks(genealogyId); setFeedContainerState('[data-feed-detail]', 'loading', '正在加载动态详情…'); try { await loadViewerContext(api, genealogyId); feed = normalizeFeed(await api.feedDetail(genealogyId, feedId)); if (!feed) throw new Error('动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。'); renderFeedDetail(feed, genealogyId); setFeedCommentFormEnabled(true); setFeedCommentFormStatus(''); await showComments(feedId, currentCommentPages[feedId] || 1); } catch (error) { if (redirectUnauthorized(api, error)) return; setFeedContainerState( '[data-feed-detail]', getApiStateType(error), getApiStateType(error) === 'forbidden' ? '当前账号无权查看该动态。' : '动态详情加载失败,请返回列表重试。' ); setFeedCommentFormStatus( getApiStateType(error) === 'forbidden' ? '当前账号无权评论该动态。' : '动态详情加载失败,暂不能发表评论。', getApiStateType(error) ); showMessage(error.message || '动态详情加载失败'); } } async function submitFeed(form) { var api = getApi(); var genealogyId; var feedId = getCurrentFeedId(); var body; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; if (!viewerContext.canEditContent) { setFeedFormStatus('当前账号无权发布或编辑该家谱的动态。', 'forbidden'); return; } // 富文本编辑器在提交前同步回 textarea,保证发送的是用户当前输入。 if (root.AppRichEditor && root.AppRichEditor.syncAll) root.AppRichEditor.syncAll(); try { body = buildFeedBody(getFormValues(form)); } catch (error) { setFeedFormStatus(error.message || '动态表单校验失败', 'error'); return; } if (!body.feedContent) { setFeedFormStatus('请填写动态内容', 'error'); return; } return withActionLock('save:' + (feedId || 'new'), async function () { var saved; var savedFeedId; setFeedSubmitPending(true); setFeedFormStatus('正在保存动态…', 'loading'); try { if (feedId) { saved = await api.updateFeed(genealogyId, feedId, body); } else { saved = await api.createFeed(genealogyId, body); } savedFeedId = getFeedId(saved) || feedId; if (!savedFeedId) throw new Error('动态保存响应缺少 feedId'); if (String(body.status || '0') === '0') { await api.feedDetail(genealogyId, savedFeedId); } showMessage('动态已保存'); root.location.href = buildPostSaveUrl(genealogyId, savedFeedId, body.status); } catch (error) { if (redirectUnauthorized(api, error)) return; setFeedFormStatus( getApiStateType(error) === 'forbidden' ? '当前账号无权保存该家谱的动态。' : (error.message || '动态保存失败'), getApiStateType(error) ); } finally { setFeedSubmitPending(false); } }); } async function likeFeed(feedId, shouldLike) { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !feedId) return; return withActionLock((shouldLike ? 'like:' : 'unlike:') + feedId, async function () { try { if (shouldLike) { await api.likeFeed(genealogyId, feedId); } else { await api.unlikeFeed(genealogyId, feedId); } if (query('[data-feed-detail-page]')) await loadFeedDetail(); else await loadFeeds(currentFeedPage); } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(error.message || (shouldLike ? '点赞失败' : '取消点赞失败')); } }); } async function deleteFeed(feedId) { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !feedId) return; if (!await confirmAction('确认删除这条动态吗?')) return; return withActionLock('delete:' + feedId, async function () { try { await api.deleteFeed(genealogyId, feedId); if (query('[data-feed-detail-page]')) root.location.href = buildFeedUrl('list', genealogyId); else await loadFeeds(currentFeedPage); } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(error.message || '删除动态失败'); } }); } async function showComments(feedId, pageNum) { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !feedId) return; currentCommentPages[feedId] = buildPageQuery(pageNum).pageNum; if (query('[data-feed-detail-comments]')) { setFeedContainerState('[data-feed-detail-comments]', 'loading', '正在加载评论…'); } else { replaceInlineCommentList(feedId, renderFeedState('loading', '正在加载评论…')); } try { renderComments( feedId, await api.feedCommentsPage(genealogyId, feedId, buildPageQuery(currentCommentPages[feedId])) ); } catch (error) { if (redirectUnauthorized(api, error)) return; if (query('[data-feed-detail-comments]')) { setFeedContainerState( '[data-feed-detail-comments]', getApiStateType(error), getApiStateType(error) === 'forbidden' ? '当前账号无权查看评论。' : '评论加载失败,请稍后重试。' ); } else { replaceInlineCommentList( feedId, renderFeedState( getApiStateType(error), getApiStateType(error) === 'forbidden' ? '当前账号无权查看评论。' : '评论加载失败,请稍后重试。' ) ); } showMessage(error.message || '评论加载失败'); } } async function showCommentReplies(feedId, commentId, pageNum) { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !feedId || !commentId) return; currentReplyPages[commentId] = buildPageQuery(pageNum).pageNum; setReplyState(commentId, 'loading', '正在加载回复…'); try { renderCommentReplies( feedId, commentId, await api.feedCommentRepliesPage( genealogyId, feedId, commentId, buildPageQuery(currentReplyPages[commentId]) ) ); } catch (error) { if (redirectUnauthorized(api, error)) return; setReplyState( commentId, getApiStateType(error), getApiStateType(error) === 'forbidden' ? '当前账号无权查看回复。' : '回复加载失败,请稍后重试。' ); showMessage(error.message || '回复加载失败'); } } async function submitComment(form) { var api = getApi(); var genealogyId; var feedId = getCurrentFeedId(); var body; var values; var forbidden = false; if (redirectUnauthorized(api) || !form) return; genealogyId = requireGenealogyId(); if (!genealogyId || !feedId) return; values = getFormValues(form); body = buildCommentBody(values); if (!body.commentContent) { setFeedCommentFormStatus('请填写评论内容。', 'error'); return; } if (body.commentContent.length > 1000) { setFeedCommentFormStatus('评论不能超过 1000 字。', 'error'); return; } return withActionLock('comment:' + feedId + ':' + (body.parentCommentId || 'root'), async function () { setFeedCommentFormEnabled(false); setFeedCommentSubmitPending(true); setFeedCommentFormStatus('正在发布评论…', 'loading'); try { await api.createFeedComment(genealogyId, feedId, body); await showComments(feedId, currentCommentPages[feedId] || 1); if (body.parentCommentId) await showCommentReplies(feedId, body.parentCommentId, currentReplyPages[body.parentCommentId] || 1); form.reset(); setFeedCommentTarget(''); setFeedCommentFormStatus(''); var feed = normalizeFeed(await api.feedDetail(genealogyId, feedId)); if (feed) renderFeedDetail(feed, genealogyId); showMessage('评论已发布'); } catch (error) { if (redirectUnauthorized(api, error)) return; forbidden = getApiStateType(error) === 'forbidden'; setFeedCommentFormStatus( forbidden ? '当前账号无权评论该动态。' : (error.message || '评论发布失败,请稍后重试。'), getApiStateType(error) ); } finally { setFeedCommentSubmitPending(false); if (!forbidden) setFeedCommentFormEnabled(true); } }); } async function deleteComment(feedId, commentId) { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !feedId || !commentId) return; if (!await confirmAction('确认删除这条评论吗?')) return; return withActionLock('delete-comment:' + commentId, async function () { try { await api.deleteFeedComment(genealogyId, feedId, commentId); await showComments(feedId, currentCommentPages[feedId] || 1); if (query('[data-feed-detail-page]')) { var feed = normalizeFeed(await api.feedDetail(genealogyId, feedId)); if (feed) renderFeedDetail(feed, genealogyId); } else { await loadFeeds(currentFeedPage); } } catch (error) { if (redirectUnauthorized(api, error)) return; showMessage(error.message || '删除评论失败'); } }); } function bindActions() { if (!documentRef) return; documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-feed-form]'); if (form) { event.preventDefault(); submitFeed(form); return; } form = event.target.closest('[data-feed-comment-form]'); if (form) { event.preventDefault(); submitComment(form); } }); documentRef.addEventListener('click', function (event) { var target = event.target; var feedId; var commentId; var pageNum; if (target.closest('[data-feed-like]')) { event.preventDefault(); likeFeed(target.closest('[data-feed-like]').getAttribute('data-feed-like'), true); } if (target.closest('[data-feed-unlike]')) { event.preventDefault(); likeFeed(target.closest('[data-feed-unlike]').getAttribute('data-feed-unlike'), false); } if (target.closest('[data-feed-comments]')) { event.preventDefault(); feedId = target.closest('[data-feed-comments]').getAttribute('data-feed-comments'); showComments(feedId, 1); } if (target.closest('[data-feed-comment]')) { event.preventDefault(); feedId = target.closest('[data-feed-comment]').getAttribute('data-feed-comment'); openFeedCommentForm(feedId); } if (target.closest('[data-feed-comment-reply]')) { event.preventDefault(); commentId = target.closest('[data-feed-comment-reply]').getAttribute('data-feed-comment-reply'); feedId = target.closest('[data-feed-comment-reply]').getAttribute('data-feed-id'); openFeedCommentForm(feedId, commentId); } if (target.closest('[data-feed-comment-cancel]')) { event.preventDefault(); setFeedCommentTarget(''); setFeedCommentFormStatus(''); } if (target.closest('[data-feed-comment-replies]')) { event.preventDefault(); commentId = target.closest('[data-feed-comment-replies]').getAttribute('data-feed-comment-replies'); feedId = target.closest('[data-feed-comment-replies]').getAttribute('data-feed-id'); showCommentReplies(feedId, commentId, 1); } if (target.closest('[data-feed-delete]')) { event.preventDefault(); deleteFeed(target.closest('[data-feed-delete]').getAttribute('data-feed-delete')); } if (target.closest('[data-feed-comment-delete]')) { event.preventDefault(); commentId = target.closest('[data-feed-comment-delete]').getAttribute('data-feed-comment-delete'); feedId = target.closest('[data-feed-comment-delete]').getAttribute('data-feed-id'); deleteComment(feedId, commentId); } if (target.closest('[data-feed-page-prev]')) { event.preventDefault(); loadFeeds(Math.max(1, currentFeedPage - 1)); } if (target.closest('[data-feed-page-next]')) { event.preventDefault(); loadFeeds(currentFeedPage + 1); } if (target.closest('[data-feed-comments-prev], [data-feed-comments-next]')) { event.preventDefault(); feedId = target.closest('[data-feed-id]').getAttribute('data-feed-id'); pageNum = currentCommentPages[feedId] || 1; showComments(feedId, target.closest('[data-feed-comments-prev]') ? Math.max(1, pageNum - 1) : pageNum + 1); } if (target.closest('[data-feed-replies-prev], [data-feed-replies-next]')) { event.preventDefault(); feedId = target.closest('[data-feed-id]').getAttribute('data-feed-id'); commentId = target.closest('[data-comment-id]').getAttribute('data-comment-id'); pageNum = currentReplyPages[commentId] || 1; showCommentReplies(feedId, commentId, target.closest('[data-feed-replies-prev]') ? Math.max(1, pageNum - 1) : pageNum + 1); } if (target.closest('[data-feed-share]')) { event.preventDefault(); if (root.navigator && root.navigator.clipboard && root.location) { root.navigator.clipboard.writeText(root.location.href).then(function () { showMessage('分享链接已复制'); }).catch(function () { showMessage('复制失败,请从浏览器地址栏复制'); }); } else { showMessage('请从浏览器地址栏复制分享链接'); } } }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-feed-page]')) loadFeeds(); if (query('[data-feed-edit-page]')) loadFeedForEdit(); if (query('[data-feed-detail-page]')) loadFeedDetail(); } return { getCurrentGenealogyId: getCurrentGenealogyId, getCurrentFeedId: getCurrentFeedId, buildFeedBody: buildFeedBody, buildCommentBody: buildCommentBody, buildPageQuery: buildPageQuery, buildFeedUrl: buildFeedUrl, buildPostSaveUrl: buildPostSaveUrl, withActionLock: withActionLock, normalizeFeed: normalizeFeed, normalizeComment: normalizeComment, canEditFeeds: canEditFeeds, normalizeViewerContext: normalizeViewerContext, normalizeComments: normalizeComments, normalizeList: normalizeList, shouldRedirectToLogin: shouldRedirectToLogin, init: init }; });