diff --git a/frontend/conversation.js b/frontend/conversation.js new file mode 100644 index 0000000..f64af9f --- /dev/null +++ b/frontend/conversation.js @@ -0,0 +1,69 @@ +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(); + }, + }; +} + +if (typeof module !== 'undefined' && module.exports) module.exports = createConversationPager; diff --git a/frontend/index.html b/frontend/index.html index 65967cf..57b10e3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -212,6 +212,7 @@ textarea { resize: vertical; min-height: 120px; } .pull-sheet-header button, .pull-sheet-actions button, .pull-sheet-actions a, .pull-comment-composer button { min-height:44px; } .pull-sheet-content { overflow-wrap:anywhere; white-space:pre-wrap; } .pull-file, .pull-comment-card { margin:8px 0; padding:10px; border:1px solid #203a5c; border-radius:10px; } +.conversation-more { min-height:44px; width:100%; margin:8px 0; } .pull-file-toggle, .pull-review-file { min-height:44px; width:100%; } .pull-file-toggle { display:flex; justify-content:space-between; align-items:center; gap:8px; text-align:left; } .pull-review-file { margin-top:8px; } @@ -449,8 +450,10 @@ textarea { resize: vertical; min-height: 120px; }
-
Add comment
@@ -569,7 +572,9 @@ textarea { resize: vertical; min-height: 120px; }Changed files
Recent discussion
+Full conversation
+ +Add comment
@@ -657,6 +662,7 @@ textarea { resize: vertical; min-height: 120px; } + @@ -713,10 +719,12 @@ textarea { resize: vertical; min-height: 120px; } let updateTrigger = null; let selectedIssue = null; let selectedIssueDetail = null; + let issueConversation = null; let issueTrigger = null; let selectedPull = null; let pullTrigger = null; let selectedPullDetail = null; + let pullConversation = null; let pullReviewState = null; let creatingIssue = false; let findingWork = false; @@ -1271,13 +1279,32 @@ textarea { resize: vertical; min-height: 120px; } } function renderIssueComment(comment) { - return '