1223 lines
45 KiB
JavaScript
1223 lines
45 KiB
JavaScript
(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();
|
|
root.NavigationUtil.open('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 = '<div class="api-state api-state--' + type + '" data-api-state="' + type + '">' +
|
|
escapeHtml(message) + '</div>';
|
|
}
|
|
|
|
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]')) {
|
|
root.NavigationUtil.open(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 (source.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, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function renderFeedState(type, message) {
|
|
if (root.ProfileUI && root.ProfileUI.renderApiState) {
|
|
return root.ProfileUI.renderApiState(type, message);
|
|
}
|
|
return '<div class="api-state api-state--' + escapeHtml(type) + '" data-api-state="' +
|
|
escapeHtml(type) + '">' + escapeHtml(message) + '</div>';
|
|
}
|
|
|
|
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', '<div data-feed-comment-list="' + escapeHtml(feedId) + '">' + content + '</div>');
|
|
}
|
|
|
|
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,
|
|
'<div data-feed-reply-list="' + escapeHtml(parentCommentId) + '">' + renderFeedState(type, message) + '</div>'
|
|
);
|
|
}
|
|
|
|
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
|
|
? '<button class="pill" type="button" data-feed-unlike="' + escapeHtml(feed.feedId) + '">取消点赞</button>'
|
|
: '<button class="pill" type="button" data-feed-like="' + escapeHtml(feed.feedId) + '">点赞</button>';
|
|
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('detail', genealogyId, feed.feedId)) + '">查看详情</a>';
|
|
actions += '<button class="pill" type="button" data-feed-comments="' + escapeHtml(feed.feedId) + '">查看评论</button>';
|
|
actions += '<button class="pill" type="button" data-feed-comment="' + escapeHtml(feed.feedId) + '">发表评论</button>';
|
|
if (viewerContext.canEditContent) {
|
|
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('edit', genealogyId, feed.feedId)) + '">编辑</a>';
|
|
}
|
|
if (canDelete) {
|
|
actions += '<button class="pill is-danger" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>';
|
|
}
|
|
|
|
return '<article class="module-row feed-row" data-feed-id="' + escapeHtml(feed.feedId) + '">' +
|
|
'<div><h3>' + escapeHtml(title) + '</h3><p>' + escapeHtml(feed.feedContent) + '</p>' +
|
|
(MediaDisplay ? MediaDisplay.renderGallery(feed.mediaFiles, { label: '动态附件', className: 'feed-media-gallery' }) : '') +
|
|
'<p class="module-meta">' + escapeHtml(meta.join(' · ')) + '</p></div>' +
|
|
'<div class="row-actions">' + actions + '</div></article>';
|
|
}
|
|
|
|
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 ? '<strong>' + escapeHtml(comment.appUserNickName) + '</strong> ' : '';
|
|
var actions = '';
|
|
var canDelete = viewerContext.userId && viewerContext.userId === comment.appUserId;
|
|
|
|
if (!comment.userDeleted) {
|
|
actions += '<button class="pill" type="button" data-feed-comment-reply="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">回复</button>';
|
|
if (canDelete) {
|
|
actions += '<button class="pill is-danger" type="button" data-feed-comment-delete="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">删除评论</button>';
|
|
}
|
|
}
|
|
if (hasReplies(comment)) {
|
|
actions += '<button class="pill" type="button" data-feed-comment-replies="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">查看回复</button>';
|
|
}
|
|
|
|
return '<div class="module-row" data-feed-comment-node="' + escapeHtml(comment.commentId) + '"><p>' + author + escapeHtml(comment.commentContent) + '</p>' +
|
|
(comment.createTime ? '<p class="module-meta">' + escapeHtml(comment.createTime) + '</p>' : '') + actions + '</div>';
|
|
}
|
|
|
|
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 = '<div class="module-list">' + list.map(function (comment) {
|
|
return renderCommentRow(feedId, comment);
|
|
}).join('') + '</div>' + 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, '<div class="module-list" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">' + result.comments.map(function (comment) {
|
|
return renderCommentRow(feedId, comment);
|
|
}).join('') + renderPageControls('replies', feedId, parentCommentId, currentReplyPages[parentCommentId] || 1, getPageTotal(data)) + '</div>');
|
|
}
|
|
|
|
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 '<div class="row-actions">' +
|
|
'<button class="pill" type="button" ' + prefix + 'prev' + ids + (pageNum <= 1 ? ' disabled' : '') + '>上一页</button>' +
|
|
'<span>第 ' + pageNum + ' / ' + totalPages + ' 页</span>' +
|
|
'<button class="pill" type="button" ' + prefix + 'next' + ids + (pageNum >= totalPages ? ' disabled' : '') + '>下一页</button>' +
|
|
'</div>';
|
|
}
|
|
|
|
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);
|
|
var contentField;
|
|
var contentEditor;
|
|
|
|
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];
|
|
});
|
|
contentField = query('[data-feed-form] [name="feedContent"]');
|
|
if (contentField && root.AppRichEditor && root.AppRichEditor.init) {
|
|
contentEditor = root.AppRichEditor.init(contentField);
|
|
if (contentEditor && contentEditor.editor && contentEditor.editor.setHtml) {
|
|
contentEditor.editor.setHtml(feed.feedContent);
|
|
contentEditor.sync();
|
|
}
|
|
}
|
|
root.AttachmentEditor.setFiles('#feedMedia', data.mediaFiles || []);
|
|
}
|
|
|
|
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
|
|
? '<button class="pill" type="button" data-feed-unlike="' + escapeHtml(feed.feedId) + '">取消点赞</button>'
|
|
: '<button class="pill" type="button" data-feed-like="' + escapeHtml(feed.feedId) + '">点赞</button>';
|
|
actions += '<button class="pill" type="button" data-feed-comment="' + escapeHtml(feed.feedId) + '">发表评论</button>';
|
|
actions += '<button class="pill" type="button" data-feed-share>复制分享链接</button>';
|
|
if (viewerContext.canEditContent) {
|
|
actions += '<a class="pill" href="' + escapeHtml(buildFeedUrl('edit', genealogyId, feed.feedId)) + '">编辑</a>';
|
|
}
|
|
if (canDelete) {
|
|
actions += '<button class="pill is-danger" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>';
|
|
}
|
|
container.setAttribute('data-feed-id', feed.feedId);
|
|
container.innerHTML = '<h2>' + escapeHtml(feed.publisherNickName || feed.genealogyName || '家族动态') + '</h2>' +
|
|
'<p>' + escapeHtml(feed.feedContent) + '</p>' +
|
|
(MediaDisplay ? MediaDisplay.renderGallery(feed.mediaFiles, { label: '动态附件', className: 'feed-media-gallery' }) : '') +
|
|
'<p class="module-meta">' +
|
|
escapeHtml([
|
|
feed.createTime || '',
|
|
'点赞 ' + Number(feed.likeCount || 0),
|
|
'评论 ' + Number(feed.commentCount || 0),
|
|
String(feed.status) === '1' ? '已停用' : '正常'
|
|
].filter(Boolean).join(' · ')) +
|
|
'</p><div class="row-actions">' + actions + '</div>';
|
|
}
|
|
|
|
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;
|
|
setFeedFormEnabled(false);
|
|
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.NavigationUtil.open(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.NavigationUtil.open(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
|
|
};
|
|
});
|