2014-08-07 11:38:34 +00:00
|
|
|
/**
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
* Collapsible tabs for Vector
|
2014-08-07 11:38:34 +00:00
|
|
|
*/
|
2020-04-10 15:24:13 +00:00
|
|
|
function init() {
|
2023-04-11 17:22:58 +00:00
|
|
|
const cactionsId = 'p-cactions',
|
2020-09-23 22:29:30 +00:00
|
|
|
$cactions = $( '#' + cactionsId ),
|
2020-04-10 15:24:13 +00:00
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
2023-04-11 17:22:58 +00:00
|
|
|
$tabContainer = $( '#p-views ul' );
|
|
|
|
let initialCactionsWidth = function () {
|
|
|
|
// HACK: This depends on a discouraged feature of jQuery width().
|
|
|
|
// The #p-cactions element is generally hidden by default, but
|
|
|
|
// the consumers of this function need to know the width that the
|
|
|
|
// "More" menu would consume if it were visible. This means it
|
|
|
|
// must not return 0 if hidden, but rather virtually render it
|
|
|
|
// and compute its width, then hide it again. jQuery width() does
|
|
|
|
// all that for us.
|
|
|
|
const width = $cactions.width() || 0;
|
2016-08-26 03:56:34 +00:00
|
|
|
initialCactionsWidth = function () {
|
|
|
|
return width;
|
|
|
|
};
|
2023-04-11 17:22:58 +00:00
|
|
|
return width;
|
|
|
|
};
|
2016-08-26 03:56:34 +00:00
|
|
|
|
2014-08-07 11:38:34 +00:00
|
|
|
// Bind callback functions to animate our drop down menu in and out
|
|
|
|
// and then call the collapsibleTabs function on the menu
|
|
|
|
$tabContainer
|
2016-11-22 02:08:39 +00:00
|
|
|
.on( 'beforeTabCollapse', function () {
|
2023-04-11 17:22:58 +00:00
|
|
|
let expandedWidth;
|
2014-08-07 11:38:34 +00:00
|
|
|
// If the dropdown was hidden, show it
|
2020-10-23 16:57:49 +00:00
|
|
|
if ( !mw.util.isPortletVisible( cactionsId ) ) {
|
2020-09-23 22:29:30 +00:00
|
|
|
mw.util.showPortlet( cactionsId );
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
// Now that it is visible, force-render it virtually
|
|
|
|
// to get its expanded width, then shrink it 1px before we
|
|
|
|
// yield from JS (which means the expansion won't be visible).
|
|
|
|
// Then animate from the 1px to the expanded width.
|
|
|
|
expandedWidth = $cactions.width();
|
2019-03-13 23:00:40 +00:00
|
|
|
// eslint-disable-next-line no-jquery/no-animate
|
2022-01-05 20:49:19 +00:00
|
|
|
$cactions
|
2017-08-25 15:21:21 +00:00
|
|
|
.css( 'width', '1px' )
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
.animate( { width: expandedWidth }, 'normal' );
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
} )
|
2016-11-22 02:08:39 +00:00
|
|
|
.on( 'beforeTabExpand', function () {
|
2014-08-07 11:38:34 +00:00
|
|
|
// If we're removing the last child node right now, hide the dropdown
|
|
|
|
if ( $cactions.find( 'li' ).length === 1 ) {
|
2019-03-13 23:00:40 +00:00
|
|
|
// eslint-disable-next-line no-jquery/no-animate
|
2022-01-05 20:49:19 +00:00
|
|
|
$cactions.animate( { width: '1px' }, 'normal', function () {
|
2020-09-23 22:29:30 +00:00
|
|
|
$( this ).attr( 'style', '' );
|
|
|
|
mw.util.hidePortlet( cactionsId );
|
2015-02-14 20:44:54 +00:00
|
|
|
} );
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
} )
|
|
|
|
.collapsibleTabs( {
|
|
|
|
expandCondition: function ( eleWidth ) {
|
2018-09-09 17:05:47 +00:00
|
|
|
// This looks a bit awkward because we're doing expensive queries as late
|
|
|
|
// as possible.
|
2023-04-11 17:22:58 +00:00
|
|
|
const distance = $.collapsibleTabs.calculateTabDistance();
|
2014-08-07 11:38:34 +00:00
|
|
|
// If there are at least eleWidth + 1 pixels of free space, expand.
|
|
|
|
// We add 1 because .width() will truncate fractional values but .offset() will not.
|
|
|
|
if ( distance >= eleWidth + 1 ) {
|
|
|
|
return true;
|
|
|
|
} else {
|
2018-09-09 17:05:47 +00:00
|
|
|
// Maybe we can still expand? Account for the width of the "Actions" dropdown
|
|
|
|
// if the expansion would hide it.
|
2014-08-07 11:38:34 +00:00
|
|
|
if ( $cactions.find( 'li' ).length === 1 ) {
|
2016-08-26 03:56:34 +00:00
|
|
|
return distance >= eleWidth + 1 - initialCactionsWidth();
|
2014-08-07 11:38:34 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
collapseCondition: function () {
|
2023-04-11 17:22:58 +00:00
|
|
|
let collapsibleWidth = 0,
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
doCollapse = false;
|
2017-11-30 17:22:19 +00:00
|
|
|
|
2018-09-09 17:05:47 +00:00
|
|
|
// This looks a bit awkward because we're doing expensive queries as late
|
|
|
|
// as possible.
|
|
|
|
// TODO: The dropdown itself should probably "fold" to just the down-arrow
|
|
|
|
// (hiding the text) if it can't fit on the line?
|
2014-08-07 11:38:34 +00:00
|
|
|
|
2017-11-30 17:22:19 +00:00
|
|
|
// Never collapse if there is no overlap.
|
|
|
|
if ( $.collapsibleTabs.calculateTabDistance() >= 0 ) {
|
2014-08-07 11:38:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-11-30 17:22:19 +00:00
|
|
|
|
|
|
|
// Always collapse if the "More" button is already shown.
|
2020-09-23 22:29:30 +00:00
|
|
|
if ( mw.util.isPortletVisible( cactionsId ) ) {
|
2017-11-30 17:22:19 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
// If we reach here, this means:
|
|
|
|
// 1. #p-cactions is currently empty and invisible (e.g. when logged out),
|
|
|
|
// 2. and, there is at least one li.collapsible link in #p-views, as asserted
|
|
|
|
// by handleResize() before calling here. Such link exists e.g. as
|
|
|
|
// "View history" on articles, but generally not on special pages.
|
|
|
|
// 3. and, the left-navigation and right-navigation are overlapping
|
|
|
|
// each other, e.g. when making the window very narrow, or if a gadget
|
|
|
|
// added a lot of tabs.
|
2020-03-09 18:31:36 +00:00
|
|
|
$tabContainer.children( 'li.collapsible' ).each( function ( _index, element ) {
|
|
|
|
collapsibleWidth += $( element ).width() || 0;
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
if ( collapsibleWidth > initialCactionsWidth() ) {
|
|
|
|
// We've found one or more collapsible links that are wider
|
|
|
|
// than the "More" menu would be if it were made visible,
|
|
|
|
// which means it is worth doing a collapsing.
|
|
|
|
doCollapse = true;
|
|
|
|
// Stop this possibly expensive loop the moment the condition is met once.
|
|
|
|
return false;
|
|
|
|
}
|
2020-03-09 18:31:36 +00:00
|
|
|
return;
|
2017-11-30 17:22:19 +00:00
|
|
|
} );
|
vector.js: Remove eager calculation of p-cactions width on page load
With these optimisations applied, there is no longer any need for
the width value during most page views, and even for most resize
operations. As such, eagerly computing it ahead of time, even from an
idle callback, no longer makes sense.
It is still memoised because it's worth avoiding a recalc during
some of the code paths that need it. Also because this way the
logic stays compatible. I don't know for sure if all involved logic
would be able to handle the value changing over time.
Optimisations:
* Where possible, don't depend on jQuery width() working on invisible
elements. Specifically, don't depend on it giving you the width of the
element *as if* it were visible when it is invisible. When logged-out and,
when navigating special pages (e.g. Special:Blankpage), the #p-cactions
element is an emptyPortlet with `display: none;` set.
The animation logic was depending on initialCactionsWidth() providing
the width the element would have if it were visible. This is because
jQuery width(), will actually change 'display: none' to 'display: block',
force render virtually, compute the width, and then change back.
Instead of depending on this discouraged feature [1], move this calculation
to the code dealing with the animation and calculating it there, ourselves,
right after making it visible for real (but before shrinking it for the
hidden-to-visible growing expansion animation).
* Document our reliance on this discouraged feature for the remaining two
callers in expandCondition() and collapseCondition(). Both of those
need the initial width as-if visible and generally are not in charge
direclty of making it visible (so there's no better place to measure
it), and are in fact almost always called when the element is invisible,
thus pretty much exclusively depending on it, not even as an edge case.
* In collapseCondition(), optimise the logic to remember whether
collapsing is needed. This way, it won't need to call initialCactionsWidth()
if the loop iterated zero times (turns out to be impossible, but not
obvious from the code).
Follows-up 46f1d41 and 9b2bcbb.
Change-Id: I6f3a5c937eb17d194a7e00ab273768a5f2cb7cd2
2019-10-02 02:18:14 +00:00
|
|
|
return doCollapse;
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
} );
|
2020-04-10 15:24:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Object.freeze( { init: init } );
|