118 lines
4.9 KiB
JavaScript
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;
|