From 977d76c74f6a5437654e38c871c670b048258f90 Mon Sep 17 00:00:00 2001 From: jwbth Date: Tue, 28 May 2024 16:37:42 +0400 Subject: [PATCH] Scroll to the root comment of thread on middle click on thread line Change-Id: I9b52d8042556ffde31ef7ffbc922f7494e13b625 --- src/Comment.js | 25 ++++++++++++++++--------- src/Thread.js | 22 ++++++++++++++++++++-- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/Comment.js b/src/Comment.js index 91dc5504..edbdc52f 100644 --- a/src/Comment.js +++ b/src/Comment.js @@ -2338,7 +2338,7 @@ class Comment extends CommentSkeleton { } /** - * Scroll to the comment if it is not in the viewport. + * Scroll to the comment if it is not in the viewport. See also {@link Comment#scrollTo}. * * @param {'top'|'center'|'bottom'} alignment Where should the element be positioned relative to * the viewport. @@ -2348,7 +2348,8 @@ class Comment extends CommentSkeleton { } /** - * Scroll to the comment and (by default) flash it as a target. + * Scroll to the comment and (by default) flash it as a target. See also + * {@link Comment#scrollIntoView}. * * @param {object} [options] * @param {boolean} [options.smooth=true] Use a smooth animation. @@ -2358,6 +2359,8 @@ class Comment extends CommentSkeleton { * @param {boolean} [options.pushState=false] Whether to push a state to the history with the * comment ID as a fragment. * @param {Function} [options.callback] Callback to run after the animation has completed. + * @param {'top'|'center'|'bottom'} [options.alignment] Where should the element be positioned + * relative to the viewport. */ scrollTo({ smooth = true, @@ -2365,6 +2368,7 @@ class Comment extends CommentSkeleton { flash = true, pushState = false, callback, + alignment, } = {}) { if (expandThreads) { this.expandAllThreadsDownTo(); @@ -2377,7 +2381,7 @@ class Comment extends CommentSkeleton { } if (this.isCollapsed) { - this.getVisibleExpandNote().cdScrollTo('top', smooth, callback); + this.getVisibleExpandNote().cdScrollTo(alignment || 'top', smooth, callback); const notification = mw.notification.notify( wrapHtml(cd.sParse('navpanel-firstunseen-hidden'), { callbacks: { @@ -2398,13 +2402,16 @@ class Comment extends CommentSkeleton { } else { const offset = this.getOffset({ considerFloating: true }); (this.editForm?.$element || this.$elements).cdScrollIntoView( + alignment || ( - this.isOpeningSection || - this.editForm || - (offset && offset.bottom !== offset.bottomForVisibility) - ) ? - 'top' : - 'center', + ( + this.isOpeningSection || + this.editForm || + (offset && offset.bottom !== offset.bottomForVisibility) + ) ? + 'top' : + 'center' + ), smooth, callback ); diff --git a/src/Thread.js b/src/Thread.js index 3c9574a6..4b7fef4f 100644 --- a/src/Thread.js +++ b/src/Thread.js @@ -260,12 +260,29 @@ class Thread { this.clickArea?.classList.remove('cd-thread-clickArea-hovered'); } + /** + * Handle the `mousedown` event on the click area. + * + * @param {Event} e + * @private + */ + handleClickAreaMouseDown(e) { + if (!this.clickArea.classList.contains('cd-thread-clickArea-hovered')) return; + + // Middle button + if (!this.rootComment.isCollapsed && e.button === 1) { + e.preventDefault(); + this.handleClickAreaUnhover(); + this.rootComment.scrollTo({ alignment: 'top' }); + } + } + /** * Handle the `click` event on the click area. * * @private */ - handleToggleClick() { + handleClickAreaClick() { if (!this.clickArea.classList.contains('cd-thread-clickArea-hovered')) return; this.toggle(); @@ -292,7 +309,8 @@ class Thread { this.clickArea.onmouseenter = this.handleClickAreaHover.bind(this); this.clickArea.onmouseleave = this.handleClickAreaUnhover.bind(this); - this.clickArea.onclick = this.handleToggleClick.bind(this); + this.clickArea.onmousedown = this.handleClickAreaMouseDown.bind(this); + this.clickArea.onclick = this.handleClickAreaClick.bind(this); /** * Thread line.