From 75293088c53f213f76f6ee079981ad4ecbc766e8 Mon Sep 17 00:00:00 2001 From: lfs Date: Sun, 24 Mar 2013 20:10:35 -0300 Subject: [PATCH] Vector: Add the collapsibleTabs script from the Vector extension * Move jquery.collapsibleTabs from the Vector extension to the Vector skin (see 097f387362) * Register the module and its dependencies * Merge the init script into the existing vector.js Bug: 46513 Change-Id: I7bd77e6e4c384c9e2033f9d05ffcae86b0bb7a69 --- RELEASE-NOTES-1.22 | 1 + resources/Resources.php | 7 +- skins/vector/collapsibleTabs.js | 210 ++++++++++++++++++++++++++++++++ skins/vector/vector.js | 28 +++++ 4 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 skins/vector/collapsibleTabs.js diff --git a/RELEASE-NOTES-1.22 b/RELEASE-NOTES-1.22 index e98ed8ec0e..bd7066c009 100644 --- a/RELEASE-NOTES-1.22 +++ b/RELEASE-NOTES-1.22 @@ -111,6 +111,7 @@ production. ** editmyuserjs controls whether a user may edit their own JS subpages. * Add new hook AbortTalkPageEmailNotification, this will be used to determine whether to send the regular talk page email notification +* (bug 46513) Vector: Add the collapsibleTabs script from the Vector extension. === Bug fixes in 1.22 === * Disable Special:PasswordReset when $wgEnableEmail is false. Previously one diff --git a/resources/Resources.php b/resources/Resources.php index 347884a48d..4e85c1ed57 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -99,7 +99,12 @@ return array( 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), 'skins.vector.js' => array( - 'scripts' => 'vector/vector.js', + 'scripts' => array( + 'vector/vector.js', + 'vector/collapsibleTabs.js', + ), + 'position' => 'top', + 'dependencies' => 'jquery.delayedBind', 'remoteBasePath' => $GLOBALS['wgStylePath'], 'localBasePath' => $GLOBALS['wgStyleDirectory'], ), diff --git a/skins/vector/collapsibleTabs.js b/skins/vector/collapsibleTabs.js new file mode 100644 index 0000000000..ad77c36168 --- /dev/null +++ b/skins/vector/collapsibleTabs.js @@ -0,0 +1,210 @@ +/** + * Collapsible tabs jQuery Plugin + */ +( function ( $ ) { + var rtl = $( 'body' ).is( '.rtl' ); + $.fn.collapsibleTabs = function ( options ) { + // return if the function is called on an empty jquery object + if ( !this.length ) { + return this; + } + // Merge options into the defaults + var $settings = $.extend( {}, $.collapsibleTabs.defaults, options ); + + this.each( function () { + var $el = $( this ); + // add the element to our array of collapsible managers + $.collapsibleTabs.instances = ( $.collapsibleTabs.instances.length === 0 ? + $el : $.collapsibleTabs.instances.add( $el ) ); + // attach the settings to the elements + $el.data( 'collapsibleTabsSettings', $settings ); + // attach data to our collapsible elements + $el.children( $settings.collapsible ).each( function () { + $.collapsibleTabs.addData( $( this ) ); + } ); + } ); + + // if we haven't already bound our resize hanlder, bind it now + if ( !$.collapsibleTabs.boundEvent ) { + $( window ) + .delayedBind( '500', 'resize', function ( ) { + $.collapsibleTabs.handleResize(); + } ); + } + // call our resize handler to setup the page + $.collapsibleTabs.handleResize(); + return this; + }; + /** + * Returns the amount of horizontal distance between the two tabs groups + * (#left-navigation and #right-navigation), in pixels. If negative, this + * means that the tabs overlap, and the value is the width of overlapping + * parts. + * + * Used in default expandCondition and collapseCondition. + * + * @return {Numeric} distance/overlap in pixels + */ + function calculateTabDistance() { + var $leftTab, $rightTab, leftEnd, rightStart; + + // In RTL, #right-navigation is actually on the left and vice versa. + // Hooray for descriptive naming. + if ( !rtl ) { + $leftTab = $( '#left-navigation' ); + $rightTab = $( '#right-navigation' ); + } else { + $leftTab = $( '#right-navigation' ); + $rightTab = $( '#left-navigation' ); + } + + leftEnd = $leftTab.offset().left + $leftTab.width(); + rightStart = $rightTab.offset().left; + + return rightStart - leftEnd; + } + $.collapsibleTabs = { + instances: [], + boundEvent: null, + defaults: { + expandedContainer: '#p-views ul', + collapsedContainer: '#p-cactions ul', + collapsible: 'li.collapsible', + shifting: false, + expandCondition: function ( eleWidth ) { + // If there's at least eleWidth pixels free space, expand. + return calculateTabDistance() >= eleWidth; + }, + collapseCondition: function () { + // If there's an overlap, collapse. + return calculateTabDistance() < 0; + } + }, + addData: function ( $collapsible ) { + var $settings = $collapsible.parent().data( 'collapsibleTabsSettings' ); + if ( $settings !== null ) { + $collapsible.data( 'collapsibleTabsSettings', { + expandedContainer: $settings.expandedContainer, + collapsedContainer: $settings.collapsedContainer, + expandedWidth: $collapsible.width(), + prevElement: $collapsible.prev() + } ); + } + }, + getSettings: function ( $collapsible ) { + var $settings = $collapsible.data( 'collapsibleTabsSettings' ); + if ( $settings === undefined ) { + $.collapsibleTabs.addData( $collapsible ); + $settings = $collapsible.data( 'collapsibleTabsSettings' ); + } + return $settings; + }, + /** + * @param {jQuery.Event} e + */ + handleResize: function () { + $.collapsibleTabs.instances.each( function () { + var $el = $( this ), + data = $.collapsibleTabs.getSettings( $el ); + + if ( data.shifting ) { + return; + } + + // if the two navigations are colliding + if ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) { + + $el.trigger( 'beforeTabCollapse' ); + // move the element to the dropdown menu + $.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible + ':last' ) ); + } + + // if there are still moveable items in the dropdown menu, + // and there is sufficient space to place them in the tab container + if ( $( data.collapsedContainer + ' ' + data.collapsible ).length > 0 && + data.expandCondition( $.collapsibleTabs.getSettings( $( data.collapsedContainer ).children( + data.collapsible + ':first' ) ).expandedWidth ) ) { + //move the element from the dropdown to the tab + $el.trigger( 'beforeTabExpand' ); + $.collapsibleTabs + .moveToExpanded( data.collapsedContainer + ' ' + data.collapsible + ':first' ); + } + }); + }, + moveToCollapsed: function ( ele ) { + var data, expContainerSettings, target, + $moving = $( ele ); + + data = $.collapsibleTabs.getSettings( $moving ); + if ( !data ) { + return; + } + expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) ); + if ( !expContainerSettings ) { + return; + } + expContainerSettings.shifting = true; + + // Remove the element from where it's at and put it in the dropdown menu + target = data.collapsedContainer; + $moving.css( 'position', 'relative' ) + .css( ( rtl ? 'left' : 'right' ), 0 ) + .animate( { width: '1px' }, 'normal', function () { + var data, expContainerSettings; + $( this ).hide(); + // add the placeholder + $( '' ).insertAfter( this ); + // XXX: 'data' is undefined here, should the 'data' from the outer scope have + // a different name? + $( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', data ); + $( this ).attr( 'style', 'display: list-item;' ); + data = $.collapsibleTabs.getSettings( $( ele ) ); + if ( data ) { + expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) ); + if ( expContainerSettings ) { + expContainerSettings.shifting = false; + $.collapsibleTabs.handleResize(); + } + } + } ); + }, + moveToExpanded: function ( ele ) { + var data, expContainerSettings, $target, expandedWidth, + $moving = $( ele ); + + data = $.collapsibleTabs.getSettings( $moving ); + if ( !data ) { + return; + } + expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) ); + if ( !expContainerSettings ) { + return; + } + expContainerSettings.shifting = true; + + // grab the next appearing placeholder so we can use it for replacing + $target = $( data.expandedContainer ).find( 'span.placeholder:first' ); + expandedWidth = data.expandedWidth; + $moving.css( 'position', 'relative' ).css( ( rtl ? 'right' : 'left' ), 0 ).css( 'width', '1px' ); + $target.replaceWith( + $moving + .detach() + .css( 'width', '1px' ) + .data( 'collapsibleTabsSettings', data ) + .animate( { width: expandedWidth + 'px' }, 'normal', function () { + $( this ).attr( 'style', 'display: block;' ); + var data, expContainerSettings; + data = $.collapsibleTabs.getSettings( $( this ) ); + if ( data ) { + expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) ); + if ( expContainerSettings ) { + expContainerSettings.shifting = false; + $.collapsibleTabs.handleResize(); + } + } + } ) + ); + } + }; + +}( jQuery ) ); diff --git a/skins/vector/vector.js b/skins/vector/vector.js index 4427d9a314..fb1f212345 100644 --- a/skins/vector/vector.js +++ b/skins/vector/vector.js @@ -18,4 +18,32 @@ jQuery( function ( $ ) { $el.removeClass( 'vectorMenuFocus' ); } ); } ); + + /** + * Collapsible tabs for Vector + */ + var $cactions = $( '#p-cactions' ); + + // Bind callback functions to animate our drop down menu in and out + // and then call the collapsibleTabs function on the menu + $( '#p-views ul' ) + .bind( 'beforeTabCollapse', function () { + // If the dropdown was hidden, show it + if ( $cactions.hasClass( 'emptyPortlet' ) ) { + $cactions + .removeClass( 'emptyPortlet' ) + .find( 'h3, h5' ) + .css( 'width', '1px' ).animate( { 'width': '24px' }, 390 ); + } + } ) + .bind( 'beforeTabExpand', function () { + // If we're removing the last child node right now, hide the dropdown + if ( $cactions.find( 'li' ).length === 1 ) { + $cactions.find( 'h3, h5' ).animate( { 'width': '1px' }, 390, function () { + $( this ).attr( 'style', '' ) + .parent().addClass( 'emptyPortlet' ); + }); + } + } ) + .collapsibleTabs(); } ); -- 2.20.1