101 lines
4.0 KiB
JavaScript
101 lines
4.0 KiB
JavaScript
function createReviewController({ fetchJson }) {
|
||
function endpoint(item) {
|
||
const [owner, repo] = String(item.repository || '').split('/');
|
||
if (!owner || !repo || !Number.isInteger(Number(item.number))) {
|
||
throw new Error('This review link is invalid.');
|
||
}
|
||
return 'api/v1/repos/' + encodeURIComponent(owner) + '/' + encodeURIComponent(repo) +
|
||
'/pulls/' + Number(item.number) + '/review';
|
||
}
|
||
|
||
async function load(item) {
|
||
return fetchJson(endpoint(item), { headers: { Accept: 'application/json' } });
|
||
}
|
||
|
||
return { load };
|
||
}
|
||
|
||
function diffLineClass(line) {
|
||
if (line.startsWith('@@')) return 'hunk';
|
||
if (line.startsWith('+')) return 'added';
|
||
if (line.startsWith('-')) return 'removed';
|
||
return 'context';
|
||
}
|
||
|
||
function renderDiffFile(file, index, escapeHtml) {
|
||
const panelId = 'review-diff-' + index;
|
||
let preview;
|
||
if (file.diff_available) {
|
||
const lines = (file.diff_lines || []).map(line =>
|
||
'<span class="review-diff-line ' + diffLineClass(String(line)) + '">' +
|
||
escapeHtml(String(line)) + '</span>'
|
||
).join('');
|
||
preview = '<pre class="review-diff" id="' + panelId + '" hidden>' + lines +
|
||
(file.diff_truncated ? '<span class="review-diff-note">Preview truncated · open in Gitea for the full diff.</span>' : '') +
|
||
'</pre>';
|
||
} else {
|
||
const message = file.diff_binary ? 'Binary file · preview unavailable.' : 'Diff preview unavailable.';
|
||
preview = '<div class="review-diff-empty" id="' + panelId + '" hidden>' + message +
|
||
(file.diff_truncated ? ' The response was truncated.' : '') + '</div>';
|
||
}
|
||
const filename = escapeHtml(file.filename || 'Unknown file');
|
||
return '<div class="review-file" data-review-filename="' + filename + '"><button class="review-file-toggle" aria-expanded="false" aria-controls="' + panelId + '">' +
|
||
'<strong>' + filename + '</strong><span class="small">' +
|
||
escapeHtml(file.status || 'changed') + ' · +' + Number(file.additions || 0) + ' / −' +
|
||
Number(file.deletions || 0) + '</span></button>' + preview +
|
||
'<button class="review-mark" data-review-filename="' + filename + '" aria-pressed="false">Mark reviewed</button></div>';
|
||
}
|
||
|
||
function toggleDiff(button, panel) {
|
||
const expanded = button.getAttribute('aria-expanded') === 'true';
|
||
button.setAttribute('aria-expanded', String(!expanded));
|
||
panel.hidden = expanded;
|
||
}
|
||
|
||
function createProgress({ storage, repository, number, headSha, files }) {
|
||
const filenames = (files || []).map(file => file && file.filename).filter(Boolean);
|
||
const key = 'stackchain.review-progress.v1:' + repository + '#' + number + '@' + headSha;
|
||
const headKey = 'stackchain.review-progress.v1:' + repository + '#' + number + ':head';
|
||
let reviewed = [];
|
||
let newHead = false;
|
||
try {
|
||
const previousHead = storage.getItem(headKey);
|
||
newHead = Boolean(previousHead && previousHead !== headSha);
|
||
storage.setItem(headKey, headSha);
|
||
const saved = JSON.parse(storage.getItem(key) || '[]');
|
||
if (Array.isArray(saved)) reviewed = filenames.filter(filename => saved.includes(filename));
|
||
} catch (error) {
|
||
reviewed = [];
|
||
}
|
||
|
||
function snapshot() {
|
||
const pending = filenames.find(filename => !reviewed.includes(filename)) || null;
|
||
return {
|
||
reviewed: [...reviewed],
|
||
reviewedCount: reviewed.length,
|
||
total: filenames.length,
|
||
nextFilename: pending,
|
||
...(newHead ? { newHead: true } : {}),
|
||
};
|
||
}
|
||
|
||
function markReviewed(filename) {
|
||
if (filenames.includes(filename) && !reviewed.includes(filename)) {
|
||
reviewed.push(filename);
|
||
reviewed = filenames.filter(item => reviewed.includes(item));
|
||
try { storage.setItem(key, JSON.stringify(reviewed)); } catch (error) { /* local progress remains usable */ }
|
||
}
|
||
return snapshot();
|
||
}
|
||
|
||
return { snapshot, markReviewed };
|
||
}
|
||
|
||
createReviewController.renderDiffFile = renderDiffFile;
|
||
createReviewController.toggleDiff = toggleDiff;
|
||
createReviewController.createProgress = createProgress;
|
||
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = createReviewController;
|
||
}
|