Allow to mark files in a PR as viewed ()

Users can now mark files in PRs as viewed, resulting in them not being shown again by default when they reopen the PR again.
This commit is contained in:
delvh
2022-05-07 20:28:10 +02:00
committed by GitHub
parent 59b30f060a
commit 5ca224a789
16 changed files with 492 additions and 44 deletions

@ -0,0 +1,18 @@
import {svg} from '../svg.js';
// Hides the file if newFold is true, and shows it otherwise. The actual hiding is performed using CSS.
//
// The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class.
// The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class.
//
export function setFileFolding(fileContentBox, foldArrow, newFold) {
foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18);
fileContentBox.setAttribute('data-folded', newFold);
}
// Like `setFileFolding`, except that it automatically inverts the current file folding state.
export function invertFileFolding(fileContentBox, foldArrow) {
setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true');
}

@ -0,0 +1,71 @@
import {setFileFolding} from './file-fold.js';
const {csrfToken, pageData} = window.config;
const prReview = pageData.prReview || {};
const viewedStyleClass = 'viewed-file-checked-form';
const viewedCheckboxSelector = '.viewed-file-form'; // Selector under which all "Viewed" checkbox forms can be found
// Refreshes the summary of viewed files if present
// The data used will be window.config.pageData.prReview.numberOf{Viewed}Files
function refreshViewedFilesSummary() {
const viewedFilesMeter = document.getElementById('viewed-files-summary');
viewedFilesMeter?.setAttribute('value', prReview.numberOfViewedFiles);
const summaryLabel = document.getElementById('viewed-files-summary-label');
if (summaryLabel) summaryLabel.innerHTML = summaryLabel.getAttribute('data-text-changed-template')
.replace('%[1]d', prReview.numberOfViewedFiles)
.replace('%[2]d', prReview.numberOfFiles);
}
// Explicitly recounts how many files the user has currently reviewed by counting the number of checked "viewed" checkboxes
// Additionally, the viewed files summary will be updated if it exists
export function countAndUpdateViewedFiles() {
// The number of files is constant, but the number of viewed files can change because files can be loaded dynamically
prReview.numberOfViewedFiles = document.querySelectorAll(`${viewedCheckboxSelector} > input[type=checkbox][checked]`).length;
refreshViewedFilesSummary();
}
// Initializes a listener for all children of the given html element
// (for example 'document' in the most basic case)
// to watch for changes of viewed-file checkboxes
export function initViewedCheckboxListenerFor() {
for (const form of document.querySelectorAll(`${viewedCheckboxSelector}:not([data-has-viewed-checkbox-listener="true"])`)) {
// To prevent double addition of listeners
form.setAttribute('data-has-viewed-checkbox-listener', true);
// The checkbox consists of a div containing the real checkbox with its label and the CSRF token,
// hence the actual checkbox first has to be found
const checkbox = form.querySelector('input[type=checkbox]');
checkbox.addEventListener('change', function() {
// Mark the file as viewed visually - will especially change the background
if (this.checked) {
form.classList.add(viewedStyleClass);
prReview.numberOfViewedFiles++;
} else {
form.classList.remove(viewedStyleClass);
prReview.numberOfViewedFiles--;
}
// Update viewed-files summary and remove "has changed" label if present
refreshViewedFilesSummary();
const hasChangedLabel = form.parentNode.querySelector('.changed-since-last-review');
hasChangedLabel?.parentNode.removeChild(hasChangedLabel);
// Unfortunately, actual forms cause too many problems, hence another approach is needed
const files = {};
files[checkbox.getAttribute('name')] = this.checked;
const data = {files};
const headCommitSHA = form.getAttribute('data-headcommit');
if (headCommitSHA) data.headCommitSHA = headCommitSHA;
fetch(form.getAttribute('data-link'), {
method: 'POST',
headers: {'X-Csrf-Token': csrfToken},
body: JSON.stringify(data),
});
// Fold the file accordingly
const parentBox = form.closest('.diff-file-header');
setFileFolding(parentBox.closest('.file-content'), parentBox.querySelector('.fold-file'), this.checked);
});
}
}

@ -1,5 +1,6 @@
import $ from 'jquery';
import {svg} from '../svg.js';
import {invertFileFolding} from './file-fold.js';
function changeHash(hash) {
if (window.history.pushState) {
@ -148,10 +149,7 @@ export function initRepoCodeView() {
}).trigger('hashchange');
}
$(document).on('click', '.fold-file', ({currentTarget}) => {
const box = currentTarget.closest('.file-content');
const folded = box.getAttribute('data-folded') !== 'true';
currentTarget.innerHTML = svg(`octicon-chevron-${folded ? 'right' : 'down'}`, 18);
box.setAttribute('data-folded', String(folded));
invertFileFolding(currentTarget.closest('.file-content'), currentTarget);
});
$(document).on('click', '.blob-excerpt', async ({currentTarget}) => {
const url = currentTarget.getAttribute('data-url');

@ -2,6 +2,7 @@ import $ from 'jquery';
import {initCompReactionSelector} from './comp/ReactionSelector.js';
import {initRepoIssueContentHistory} from './repo-issue-content.js';
import {validateTextareaNonEmpty} from './comp/EasyMDE.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles} from './pull-view-file.js';
const {csrfToken} = window.config;
@ -104,6 +105,13 @@ export function initRepoDiffConversationNav() {
});
}
// Will be called when the show more (files) button has been pressed
function onShowMoreFiles() {
initRepoIssueContentHistory();
initViewedCheckboxListenerFor();
countAndUpdateViewedFiles();
}
export function initRepoDiffShowMore() {
$('#diff-files, #diff-file-boxes').on('click', '#diff-show-more-files, #diff-show-more-files-stats', (e) => {
e.preventDefault();
@ -125,7 +133,7 @@ export function initRepoDiffShowMore() {
$('#diff-too-many-files-stats').remove();
$('#diff-files').append($(resp).find('#diff-files li'));
$('#diff-incomplete').replaceWith($(resp).find('#diff-file-boxes').children());
initRepoIssueContentHistory();
onShowMoreFiles();
}).fail(() => {
$('#diff-show-more-files, #diff-show-more-files-stats').removeClass('disabled');
});
@ -151,7 +159,7 @@ export function initRepoDiffShowMore() {
}
$target.parent().replaceWith($(resp).find('#diff-file-boxes .diff-file-body .file-body').children());
initRepoIssueContentHistory();
onShowMoreFiles();
}).fail(() => {
$target.removeClass('disabled');
});