summaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
authordelvh <leon@kske.dev>2022-05-07 20:28:10 +0200
committerGitHub <noreply@github.com>2022-05-07 20:28:10 +0200
commit5ca224a789394d00cc1efb5afcae7b87aa7d1e49 (patch)
treed88bddc7a08449a2c8f2b4e5ae47d3ef067a1cc0 /web_src
parent59b30f060a840cde305952ef7bc344fa4101c0d5 (diff)
downloadgitea-5ca224a789394d00cc1efb5afcae7b87aa7d1e49.tar.gz
gitea-5ca224a789394d00cc1efb5afcae7b87aa7d1e49.zip
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.
Diffstat (limited to 'web_src')
-rw-r--r--web_src/js/features/file-fold.js18
-rw-r--r--web_src/js/features/pull-view-file.js71
-rw-r--r--web_src/js/features/repo-code.js6
-rw-r--r--web_src/js/features/repo-diff.js12
-rw-r--r--web_src/js/index.js3
-rw-r--r--web_src/less/_review.less18
6 files changed, 121 insertions, 7 deletions
diff --git a/web_src/js/features/file-fold.js b/web_src/js/features/file-fold.js
new file mode 100644
index 0000000000..5e714a1de8
--- /dev/null
+++ b/web_src/js/features/file-fold.js
@@ -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');
+}
+
diff --git a/web_src/js/features/pull-view-file.js b/web_src/js/features/pull-view-file.js
new file mode 100644
index 0000000000..57e9d5e047
--- /dev/null
+++ b/web_src/js/features/pull-view-file.js
@@ -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);
+ });
+ }
+}
diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js
index d7b4baac83..8562ba0072 100644
--- a/web_src/js/features/repo-code.js
+++ b/web_src/js/features/repo-code.js
@@ -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');
diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js
index 013b8d3fa0..8403a2fd42 100644
--- a/web_src/js/features/repo-diff.js
+++ b/web_src/js/features/repo-diff.js
@@ -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');
});
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 5b95a0d8ef..4343c2d965 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -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();
});
diff --git a/web_src/less/_review.less b/web_src/less/_review.less
index 8bc41b9736..1bcb3663c2 100644
--- a/web_src/less/_review.less
+++ b/web_src/less/_review.less
@@ -262,3 +262,21 @@ a.blob-excerpt:hover {
scroll-margin-top: 130px;
}
}
+
+.changed-since-last-review {
+ margin: 0 5px;
+ padding: 0 3px;
+ border: 2px var(--color-primary-light-3) solid;
+ background-color: var(--color-primary-alpha-30);
+ border-radius: 7px;
+}
+
+.viewed-file-form {
+ margin: 0 3px;
+ padding: 0 3px;
+ border-radius: 3px;
+}
+
+.viewed-file-checked-form {
+ background-color: var(--color-primary-light-4);
+}