stackchain-dashboard/frontend/conversation.js
timmy 7662cca9e6
All checks were successful
CI / lint (pull_request) Successful in 3m36s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 6m19s
CI / release-candidate (pull_request) Has been skipped
feat: act on Search and Following comments (Closes #1388)
2026-08-25 09:11:14 +00:00

118 lines
4.9 KiB
JavaScript

function createConversationPager({ loadPage }) {
let state = { comments: [], page: 1, older_page: null, total: 0 };
let olderRequest = null;
const validComments = comments => Array.isArray(comments)
? comments.filter(comment => comment && Number.isInteger(comment.id))
: [];
const unique = comments => {
const seen = new Set();
return comments.filter(comment => {
if (seen.has(comment.id)) return false;
seen.add(comment.id);
return true;
}).sort((left, right) => {
const leftTime = Date.parse(left.created_at || '');
const rightTime = Date.parse(right.created_at || '');
if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
return leftTime - rightTime;
}
return left.id - right.id;
});
};
const snapshot = () => ({ ...state, comments: state.comments.map(comment => ({ ...comment })) });
return {
reset(page) {
const comments = unique(validComments(page?.comments));
state = {
comments,
page: Number.isInteger(page?.page) ? page.page : 1,
older_page: Number.isInteger(page?.older_page) ? page.older_page : null,
total: Number.isInteger(page?.total) ? Math.max(page.total, comments.length) : comments.length,
};
olderRequest = null;
return snapshot();
},
snapshot,
loadOlder() {
if (olderRequest) return olderRequest;
if (!Number.isInteger(state.older_page)) return Promise.resolve(snapshot());
const requestedPage = state.older_page;
olderRequest = Promise.resolve(loadPage(requestedPage)).then(page => {
state = {
comments: unique(validComments(page?.comments).concat(state.comments)),
page: Number.isInteger(page?.page) ? page.page : requestedPage,
older_page: Number.isInteger(page?.older_page) ? page.older_page : null,
total: Number.isInteger(page?.total) ? Math.max(page.total, state.comments.length) : state.total,
};
return snapshot();
}).finally(() => { olderRequest = null; });
return olderRequest;
},
append(comment) {
if (!comment || !Number.isInteger(comment.id)) return snapshot();
if (!state.comments.some(existing => existing.id === comment.id)) {
state = {
...state,
comments: state.comments.concat(comment),
total: Math.max(state.total + 1, state.comments.length + 1),
};
}
return snapshot();
},
replace(comment) {
if (!comment || !Number.isInteger(comment.id)) return snapshot();
state = {
...state,
comments: state.comments.map(existing => existing.id === comment.id ? { ...comment } : existing),
};
return snapshot();
},
remove(commentId) {
const comments = state.comments.filter(comment => comment.id !== commentId);
if (comments.length !== state.comments.length) {
state = { ...state, comments, total: Math.max(0, state.total - 1) };
}
return snapshot();
},
};
}
function renderConversationComment(comment, controller, escapeHtml, formatTime, renderMarkdown) {
const actions = controller?.actionHtml?.(comment) || '';
return '<div class="issue-comment" data-comment-id="' + Number(comment.id || 0) + '"><div class="small">' +
escapeHtml(comment.author || 'Unknown author') +
(comment.created_at ? ' · ' + escapeHtml(formatTime(comment.created_at)) : '') +
'</div>' + actions + '<div class="issue-sheet-content markdown-content">' +
renderMarkdown(comment.body || 'No comment body provided.') + '</div></div>';
}
function createConversationRenderers({qs,renderComment,updateReadPosition,getSelectedUpdate}) {
function status(kind, comments, total) {
qs('#' + kind + '-conversation-status').textContent = comments.length ?
comments.length + ' of ' + Math.max(total || 0, comments.length) + ' messages loaded.' : 'No comments yet.';
}
function paint(kind, state, controller) {
const comments = state?.comments || [];
qs('#' + kind + '-comments').innerHTML = comments.length ? comments.map(comment =>
kind === 'pull' ? '<div class="pull-comment-card">' + renderComment(comment,controller) + '</div>' :
renderComment(comment,controller)).join('') : '<div class="muted">No comments yet.</div>';
qs('#load-older-' + kind + '-comments').hidden = !Number.isInteger(state?.older_page);
status(kind,comments,state?.total);
if (kind === 'update') {
const newest = qs('#update-comments .issue-comment:last-child');
updateReadPosition.ready(String(getSelectedUpdate()?.notification_id || ''),newest);
}
}
return {
paintIssueConversation:(state,controller)=>paint('issue',state,controller),
paintPullConversation:(state,controller)=>paint('pull',state,controller),
paintUpdateConversation:(state,controller)=>paint('update',state,controller),
};
}
if (typeof module !== 'undefined' && module.exports) module.exports = createConversationPager;