Allow to mark files in a PR as viewed (#19007)

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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 492 additions and 44 deletions

View file

@ -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');
}

View file

@ -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);
});
}
}

View file

@ -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');

View file

@ -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');
});

View file

@ -70,6 +70,7 @@ import {
initRepoSettingsCollaboration,
initRepoSettingSearchTeamBox,
} from './features/repo-settings.js';
import {initViewedCheckboxListenerFor} from './features/pull-view-file.js';
import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.js';
import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.js';
import {initRepoRelease, initRepoReleaseEditor} from './features/repo-release.js';
@ -178,6 +179,6 @@ $(document).ready(() => {
initUserAuthWebAuthn();
initUserAuthWebAuthnRegister();
initUserSettings();
initViewedCheckboxListenerFor();
checkAppUrl();
});