MediaWiki:Gadget-switch-tabber.js:修订间差异
外观
隐藏当前平台无内容的切换标题 |
支持SwitchTabber子页面的同名锚点定位 |
||
| (未显示同一用户的11个中间版本) | |||
| 第7行: | 第7行: | ||
var TAB_CLASS = 'oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-optionWidget oo-ui-tabOptionWidget'; | var TAB_CLASS = 'oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-optionWidget oo-ui-tabOptionWidget'; | ||
var LABEL_CLASS = 'oo-ui-labelElement-label'; | var LABEL_CLASS = 'oo-ui-labelElement-label'; | ||
var TOC_CONTAINER_SELECTOR = '#toc, .toc, .vector-toc'; | |||
var activeSwitchTab = 1; | |||
var tocObserver; | |||
var PLATFORM_LABELS = { | var PLATFORM_LABELS = { | ||
mobile: { | mobile: { | ||
| 第17行: | 第20行: | ||
} | } | ||
}; | }; | ||
function attributeSelector( name, value ) { | |||
return '[' + name + '="' + String( value ).replace( /\\/g, '\\\\' ).replace( /"/g, '\\"' ) + '"]'; | |||
} | |||
function attrSelector( value ) { | function attrSelector( value ) { | ||
return ' | return attributeSelector( 'data-tabber-id', value ); | ||
} | |||
function idSelector( value ) { | |||
return attributeSelector( 'id', value ); | |||
} | } | ||
| 第40行: | 第51行: | ||
function getTabberElements( tabberId, type ) { | function getTabberElements( tabberId, type ) { | ||
return $( '.tabberex-' + type + attrSelector( tabberId ) ); | return $( '.tabberex-' + type + attrSelector( tabberId ) ); | ||
} | |||
function getSwitchTabIndexForElement( element ) { | |||
var tab = element && element.closest && element.closest( '.tabberex-tab' ); | |||
var body; | |||
var platform; | |||
var index; | |||
while ( tab ) { | |||
body = tab.parentNode; | |||
if ( body && body.matches( '.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) ) ) { | |||
platform = tab.getAttribute( 'data-switchtabber-platform' ); | |||
if ( platform === 'mobile' ) { | |||
return 1; | |||
} | |||
if ( platform === 'switch' ) { | |||
return 2; | |||
} | |||
index = toArray( body.children ).indexOf( tab ); | |||
return index === -1 ? null : index + 1; | |||
} | |||
tab = tab.parentElement && tab.parentElement.closest( '.tabberex-tab' ); | |||
} | |||
return null; | |||
} | } | ||
| 第69行: | 第104行: | ||
} | } | ||
function | function toArray( collection ) { | ||
var clone = heading.cloneNode( true ); | return Array.prototype.slice.call( collection ); | ||
} | |||
function removeElements( root, selector ) { | |||
toArray( root.querySelectorAll( selector ) ).forEach( function ( element ) { | |||
element.parentNode.removeChild( element ); | |||
} ); | |||
} | |||
function getHeadingState( heading, tabIndex ) { | |||
var headline = heading.querySelector( '.mw-headline' ); | |||
var clone = ( headline || heading ).cloneNode( true ); | |||
var containingTabIndex = getSwitchTabIndexForElement( heading ); | |||
var label; | |||
toArray( clone.querySelectorAll( '.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) ) ).forEach( function ( body ) { | |||
toArray( body.children ).forEach( function ( tab, index ) { | |||
if ( index !== tabIndex - 1 ) { | |||
tab.parentNode.removeChild( tab ); | |||
} | |||
} ); | |||
} ); | |||
removeElements( clone, '.mobileonly, .mw-editsection' ); | |||
label = clone.textContent.replace( /\s+/g, ' ' ).trim(); | |||
return { | |||
label: label, | |||
visible: label !== '' && ( containingTabIndex === null || containingTabIndex === tabIndex ) | |||
}; | |||
} | |||
function setSwitchElementVisible( element, visible ) { | |||
var display; | |||
var priority; | |||
if ( !visible && element.dataset.switchtabberDisplaySet !== '1' ) { | |||
element.dataset.switchtabberDisplay = element.style.getPropertyValue( 'display' ); | |||
function ( | element.dataset.switchtabberDisplayPriority = element.style.getPropertyPriority( 'display' ); | ||
element.dataset.switchtabberDisplaySet = '1'; | |||
} | |||
element.hidden = !visible; | |||
if ( !visible ) { | |||
} ); | element.style.setProperty( 'display', 'none', 'important' ); | ||
return; | |||
} | |||
if ( element.dataset.switchtabberDisplaySet !== '1' ) { | |||
return; | |||
} | |||
display = element.dataset.switchtabberDisplay; | |||
priority = element.dataset.switchtabberDisplayPriority; | |||
if ( display ) { | |||
element.style.setProperty( 'display', display, priority ); | |||
} else { | |||
element.style.removeProperty( 'display' ); | |||
} | |||
delete element.dataset.switchtabberDisplay; | |||
delete element.dataset.switchtabberDisplayPriority; | |||
delete element.dataset.switchtabberDisplaySet; | |||
} | |||
function getHeadingSectionNumber( heading ) { | |||
return toArray( | |||
document.querySelectorAll( '.mw-parser-output h2, .mw-parser-output h3, .mw-parser-output h4, .mw-parser-output h5, .mw-parser-output h6' ) | |||
).indexOf( heading ) + 1; | |||
} | |||
function getSectionEditControls( heading ) { | |||
var controls = toArray( heading.querySelectorAll( '.mw-editsection' ) ); | |||
var sectionNumber = getHeadingSectionNumber( heading ); | |||
if ( sectionNumber === 0 ) { | |||
return controls; | |||
} | |||
toArray( document.querySelectorAll( '.mw-editsection' ) ).forEach( function ( control ) { | |||
var link = control.querySelector( 'a[href*="section="]' ); | |||
var match = link && /(?:[?&]section=)(\d+)/.exec( link.getAttribute( 'href' ) || '' ); | |||
if ( match && Number( match[ 1 ] ) === sectionNumber && controls.indexOf( control ) === -1 ) { | |||
controls.push( control ); | |||
} | |||
} ); | |||
return controls; | |||
} | |||
function syncEditSectionControls( heading, label, visible ) { | |||
getSectionEditControls( heading ).forEach( function ( control ) { | |||
setSwitchElementVisible( control, visible ); | |||
toArray( control.querySelectorAll( 'a' ) ).forEach( function ( link ) { | |||
var href = link.getAttribute( 'href' ) || ''; | |||
var prefix = href.indexOf( 'veaction=edit' ) !== -1 ? '编辑章节:' : '编辑章节的源代码:'; | |||
link.setAttribute( 'title', prefix + label ); | |||
} ); | |||
} ); | |||
} | |||
function getElementByFragment( fragment ) { | |||
var decoded; | |||
var targets; | |||
var activeTarget; | |||
if ( !fragment ) { | |||
return null; | |||
} | |||
try { | |||
decoded = decodeURIComponent( fragment ).replace( / /g, '_' ); | |||
} catch ( e ) { | |||
return null; | |||
} | |||
targets = toArray( document.querySelectorAll( idSelector( decoded ) ) ).concat( | |||
toArray( document.querySelectorAll( attributeSelector( 'data-switchtabber-anchor', decoded ) ) ) | |||
).filter( function ( target, index, allTargets ) { | |||
return allTargets.indexOf( target ) === index; | |||
} ); | |||
activeTarget = targets.filter( function ( target ) { | |||
return getSwitchTabIndexForElement( target ) === activeSwitchTab; | |||
} )[ 0 ]; | |||
return activeTarget || targets[ 0 ] || null; | |||
} | |||
function getFragmentTarget( link ) { | |||
var href = link.getAttribute( 'href' ); | |||
var hashIndex; | |||
if ( !href || href.indexOf( '#' ) === -1 ) { | |||
return null; | |||
} | |||
hashIndex = href.indexOf( '#' ); | |||
return getElementByFragment( href.slice( hashIndex + 1 ) ); | |||
} | |||
function getStableHeadingAnchor( heading ) { | |||
var headline = heading.querySelector( '.mw-headline' ); | |||
var anchor = headline && headline.querySelector( '[id]' ); | |||
return anchor && anchor.id !== headline.id && | |||
document.querySelectorAll( idSelector( anchor.id ) ).length === 1 ? anchor.id : null; | |||
} | |||
function syncTocEntry( heading, state ) { | |||
var stableAnchor = getStableHeadingAnchor( heading ); | |||
toArray( document.querySelectorAll( '#toc a[href*="#"], .toc a[href*="#"], .vector-toc a[href*="#"]' ) ).forEach( function ( link ) { | |||
var target = getFragmentTarget( link ); | |||
var tocItem; | |||
var labelElement; | |||
if ( !target || target.closest( 'h1, h2, h3, h4, h5, h6' ) !== heading ) { | |||
return; | |||
} | |||
tocItem = link.closest( 'li' ); | |||
if ( tocItem ) { | |||
setSwitchElementVisible( tocItem, state.visible ); | |||
} | |||
if ( stableAnchor ) { | |||
link.setAttribute( 'href', '#' + stableAnchor ); | |||
} | |||
if ( !state.visible ) { | |||
return; | |||
} | |||
labelElement = link.querySelector( '.vector-toc-text span:not(.vector-toc-numb), .toctext, .toc-text' ); | |||
if ( labelElement && labelElement.textContent !== state.label ) { | |||
labelElement.textContent = state.label; | |||
} | } | ||
} ); | } ); | ||
} | |||
function syncSwitchHeading( heading, tabIndex ) { | |||
var state = getHeadingState( heading, tabIndex ); | |||
var wrapper = heading.closest( '.mw-heading' ) || heading; | |||
setSwitchElementVisible( wrapper, state.visible ); | |||
syncEditSectionControls( heading, state.label, state.visible ); | |||
syncTocEntry( heading, state ); | |||
} | |||
function syncSwitchHeadings( tabIndex ) { | |||
toArray( document.querySelectorAll( 'h1, h2, h3, h4, h5, h6' ) ).forEach( function ( heading ) { | |||
if ( | |||
heading.querySelector( '.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) ) || | |||
getSwitchTabIndexForElement( heading ) !== null | |||
) { | |||
syncSwitchHeading( heading, tabIndex ); | |||
} | |||
} ); | |||
} | } | ||
function | function mutationTouchesToc( mutation ) { | ||
var target = mutation.target; | |||
if ( target.nodeType === 1 && target.closest( TOC_CONTAINER_SELECTOR ) ) { | |||
return true; | |||
} | |||
return toArray( mutation.addedNodes ).some( function ( node ) { | |||
return node.nodeType === 1 && ( | |||
node.matches( TOC_CONTAINER_SELECTOR ) || | |||
!!node.querySelector( TOC_CONTAINER_SELECTOR ) | |||
); | |||
} ); | |||
} | |||
function observeTocChanges() { | |||
if ( !window.MutationObserver || tocObserver || !document.body ) { | |||
return; | |||
} | |||
tocObserver = new window.MutationObserver( function ( mutations ) { | |||
if ( toArray( mutations ).some( mutationTouchesToc ) ) { | |||
syncSwitchHeadings( activeSwitchTab ); | |||
} | } | ||
); | } ); | ||
tocObserver.observe( document.body, { | |||
childList: true, | |||
subtree: true | |||
} ); | |||
} | } | ||
| 第109行: | 第325行: | ||
tabberexShowTab( tabberId, index ); | tabberexShowTab( tabberId, index ); | ||
if ( tabberId === SWITCH_TABBER_ID ) { | if ( tabberId === SWITCH_TABBER_ID ) { | ||
activeSwitchTab = index; | |||
syncSwitchHeadings( index ); | |||
} | } | ||
} | } | ||
| 第183行: | 第400行: | ||
} | } | ||
function | function collectTabDetails( container, hideTabs ) { | ||
var details = []; | var details = []; | ||
var defaultIndex = 0; | var defaultIndex = 0; | ||
toArray( container.querySelectorAll( ':scope > .tabberex-tab' ) ).forEach( function ( tab, index ) { | |||
details.push( readTabDetails( tab, index ) ); | |||
if ( details[ index ].isDefault ) { | |||
if ( | |||
defaultIndex = index; | defaultIndex = index; | ||
} | |||
if ( hideTabs ) { | |||
tab.style.display = 'none'; | |||
} | } | ||
} ); | } ); | ||
return { | |||
details: details, | |||
defaultIndex: defaultIndex | |||
}; | |||
} | |||
function registerTabber( defaultTabs, tabberIds, tabberId, defaultIndex ) { | |||
if ( defaultTabs[ tabberId ] === undefined ) { | if ( defaultTabs[ tabberId ] === undefined ) { | ||
defaultTabs[ tabberId ] = defaultIndex; | defaultTabs[ tabberId ] = defaultIndex; | ||
} | |||
tabberIds.push( tabberId ); | |||
} | |||
function processHead( head, defaultTabs, tabberIds, tabberIndex ) { | |||
var tabberId = head.getAttribute( 'data-tabber-id' ) || String( tabberIndex ); | |||
var tabState; | |||
if ( head.dataset.tabberexProcessed || !head.querySelector( '.tabberex-tab' ) ) { | |||
return tabberIndex; | |||
} | } | ||
tabState = collectTabDetails( head, false ); | |||
head.dataset.tabberexProcessed = '1'; | |||
buildTabHead( head, tabberId, tabState.details ); | |||
registerTabber( defaultTabs, tabberIds, tabberId, tabState.defaultIndex ); | |||
return tabberIndex + 1; | return tabberIndex + 1; | ||
} | } | ||
| 第214行: | 第444行: | ||
var tabberId = tabber.getAttribute( 'data-tabber-id' ) || String( tabberIndex ); | var tabberId = tabber.getAttribute( 'data-tabber-id' ) || String( tabberIndex ); | ||
var head = document.createElement( 'div' ); | var head = document.createElement( 'div' ); | ||
var | var tabState; | ||
if ( tabber.dataset.tabberexProcessed ) { | if ( tabber.dataset.tabberexProcessed ) { | ||
| 第221行: | 第450行: | ||
} | } | ||
tabState = collectTabDetails( tabber, true ); | |||
tabber.dataset.tabberexProcessed = '1'; | tabber.dataset.tabberexProcessed = '1'; | ||
tabber.setAttribute( 'data-tabber-id', tabberId ); | tabber.setAttribute( 'data-tabber-id', tabberId ); | ||
tabber.classList.remove( 'tabberex' ); | tabber.classList.remove( 'tabberex' ); | ||
tabber.classList.add( 'tabberex-body' ); | tabber.classList.add( 'tabberex-body' ); | ||
buildTabHead( head, tabberId, details ); | buildTabHead( head, tabberId, tabState.details ); | ||
tabber.parentNode.insertBefore( head, tabber ); | tabber.parentNode.insertBefore( head, tabber ); | ||
registerTabber( defaultTabs, tabberIds, tabberId, tabState.defaultIndex ); | |||
return tabberIndex + 1; | return tabberIndex + 1; | ||
} | } | ||
| 第334行: | 第550行: | ||
tabberexSwitchTab( SWITCH_TABBER_ID, index ); | tabberexSwitchTab( SWITCH_TABBER_ID, index ); | ||
setSelectedSwitchTab( index ); | setSelectedSwitchTab( index ); | ||
} | |||
function activateSwitchTabForHash() { | |||
var fragment = window.location.hash.slice( 1 ); | |||
var target = getElementByFragment( fragment ); | |||
var tabIndex = getSwitchTabIndexForElement( target ); | |||
if ( !target ) { | |||
return; | |||
} | |||
if ( tabIndex !== null && tabIndex !== activeSwitchTab ) { | |||
switchtabberSwitchTab( tabIndex ); | |||
} | |||
window.setTimeout( function () { | |||
target.scrollIntoView(); | |||
}, 0 ); | |||
} | } | ||
| 第404行: | 第636行: | ||
applySwitchTabberIconMasks( $( document ) ); | applySwitchTabberIconMasks( $( document ) ); | ||
initialTab = getSelectedSwitchTab(); | initialTab = getSelectedSwitchTab(); | ||
observeTocChanges(); | |||
tabberexSwitchTab( SWITCH_TABBER_ID, initialTab ); | tabberexSwitchTab( SWITCH_TABBER_ID, initialTab ); | ||
activateSwitchTabForHash(); | |||
} | } | ||
| 第413行: | 第647行: | ||
window.switchtabber_switchTab = switchtabberSwitchTab; | window.switchtabber_switchTab = switchtabberSwitchTab; | ||
window.addEventListener( 'hashchange', activateSwitchTabForHash ); | |||
mw.hook( 'wikipage.content' ).add( processTabbers ); | mw.hook( 'wikipage.content' ).add( processTabbers ); | ||
mw.hook( 'wikipage.content' ).add( addSwitchTabber ); | mw.hook( 'wikipage.content' ).add( addSwitchTabber ); | ||
}( this, jQuery, mediaWiki ) ); | }( this, jQuery, mediaWiki ) ); | ||
2026年8月10日 (一) 01:34的最新版本
( function ( window, $, mw ) {
'use strict';
var SWITCH_TABBER_ID = 'SwitchTabber';
var COOKIE_NAME = 'SwitchTab';
var TABBER_HEAD_CLASS = 'tabberex-head oo-ui-widget oo-ui-widget-enabled oo-ui-selectWidget oo-ui-selectWidget-unpressed oo-ui-selectWidget-depressed oo-ui-tabSelectWidget';
var TAB_CLASS = 'oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-optionWidget oo-ui-tabOptionWidget';
var LABEL_CLASS = 'oo-ui-labelElement-label';
var TOC_CONTAINER_SELECTOR = '#toc, .toc, .vector-toc';
var activeSwitchTab = 1;
var tocObserver;
var PLATFORM_LABELS = {
mobile: {
hans: '移动版',
hant: '行動版'
},
switch: {
hans: 'Switch',
hant: 'Switch'
}
};
function attributeSelector( name, value ) {
return '[' + name + '="' + String( value ).replace( /\\/g, '\\\\' ).replace( /"/g, '\\"' ) + '"]';
}
function attrSelector( value ) {
return attributeSelector( 'data-tabber-id', value );
}
function idSelector( value ) {
return attributeSelector( 'id', value );
}
function isTraditionalVariant() {
var variant = String(
mw.config.get( 'wgUserVariant' ) ||
mw.config.get( 'wgUserLanguage' ) ||
mw.config.get( 'wgContentLanguage' ) ||
''
).toLowerCase();
return /(?:hant|tw|hk|mo)/.test( variant );
}
function getPlatformLabel( key ) {
var labels = PLATFORM_LABELS[ key ];
return isTraditionalVariant() ? labels.hant : labels.hans;
}
function getTabberElements( tabberId, type ) {
return $( '.tabberex-' + type + attrSelector( tabberId ) );
}
function getSwitchTabIndexForElement( element ) {
var tab = element && element.closest && element.closest( '.tabberex-tab' );
var body;
var platform;
var index;
while ( tab ) {
body = tab.parentNode;
if ( body && body.matches( '.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) ) ) {
platform = tab.getAttribute( 'data-switchtabber-platform' );
if ( platform === 'mobile' ) {
return 1;
}
if ( platform === 'switch' ) {
return 2;
}
index = toArray( body.children ).indexOf( tab );
return index === -1 ? null : index + 1;
}
tab = tab.parentElement && tab.parentElement.closest( '.tabberex-tab' );
}
return null;
}
function tabberexShowTab( tabberId, index ) {
getTabberElements( tabberId, 'head' ).children( ':nth-child(' + index + ')' )
.addClass( 'oo-ui-optionWidget-selected' )
.css( 'cursor', 'default' );
getTabberElements( tabberId, 'body' ).children( ':nth-child(' + index + ')' ).each( function () {
var displayValue = this.getAttribute( 'data-tab-display' ) || 'block';
this.style.display = displayValue;
} );
}
function tabberexHideTab( tabberId, index ) {
getTabberElements( tabberId, 'head' ).children( ':nth-child(' + index + ')' )
.removeClass( 'oo-ui-optionWidget-selected' )
.css( 'cursor', 'pointer' );
getTabberElements( tabberId, 'body' ).children( ':nth-child(' + index + ')' ).css( 'display', 'none' );
}
function tabberexHideAllTabs( tabberId ) {
getTabberElements( tabberId, 'head' ).children()
.removeClass( 'oo-ui-optionWidget-selected' )
.css( 'cursor', 'pointer' );
getTabberElements( tabberId, 'body' ).children().css( 'display', 'none' );
}
function toArray( collection ) {
return Array.prototype.slice.call( collection );
}
function removeElements( root, selector ) {
toArray( root.querySelectorAll( selector ) ).forEach( function ( element ) {
element.parentNode.removeChild( element );
} );
}
function getHeadingState( heading, tabIndex ) {
var headline = heading.querySelector( '.mw-headline' );
var clone = ( headline || heading ).cloneNode( true );
var containingTabIndex = getSwitchTabIndexForElement( heading );
var label;
toArray( clone.querySelectorAll( '.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) ) ).forEach( function ( body ) {
toArray( body.children ).forEach( function ( tab, index ) {
if ( index !== tabIndex - 1 ) {
tab.parentNode.removeChild( tab );
}
} );
} );
removeElements( clone, '.mobileonly, .mw-editsection' );
label = clone.textContent.replace( /\s+/g, ' ' ).trim();
return {
label: label,
visible: label !== '' && ( containingTabIndex === null || containingTabIndex === tabIndex )
};
}
function setSwitchElementVisible( element, visible ) {
var display;
var priority;
if ( !visible && element.dataset.switchtabberDisplaySet !== '1' ) {
element.dataset.switchtabberDisplay = element.style.getPropertyValue( 'display' );
element.dataset.switchtabberDisplayPriority = element.style.getPropertyPriority( 'display' );
element.dataset.switchtabberDisplaySet = '1';
}
element.hidden = !visible;
if ( !visible ) {
element.style.setProperty( 'display', 'none', 'important' );
return;
}
if ( element.dataset.switchtabberDisplaySet !== '1' ) {
return;
}
display = element.dataset.switchtabberDisplay;
priority = element.dataset.switchtabberDisplayPriority;
if ( display ) {
element.style.setProperty( 'display', display, priority );
} else {
element.style.removeProperty( 'display' );
}
delete element.dataset.switchtabberDisplay;
delete element.dataset.switchtabberDisplayPriority;
delete element.dataset.switchtabberDisplaySet;
}
function getHeadingSectionNumber( heading ) {
return toArray(
document.querySelectorAll( '.mw-parser-output h2, .mw-parser-output h3, .mw-parser-output h4, .mw-parser-output h5, .mw-parser-output h6' )
).indexOf( heading ) + 1;
}
function getSectionEditControls( heading ) {
var controls = toArray( heading.querySelectorAll( '.mw-editsection' ) );
var sectionNumber = getHeadingSectionNumber( heading );
if ( sectionNumber === 0 ) {
return controls;
}
toArray( document.querySelectorAll( '.mw-editsection' ) ).forEach( function ( control ) {
var link = control.querySelector( 'a[href*="section="]' );
var match = link && /(?:[?&]section=)(\d+)/.exec( link.getAttribute( 'href' ) || '' );
if ( match && Number( match[ 1 ] ) === sectionNumber && controls.indexOf( control ) === -1 ) {
controls.push( control );
}
} );
return controls;
}
function syncEditSectionControls( heading, label, visible ) {
getSectionEditControls( heading ).forEach( function ( control ) {
setSwitchElementVisible( control, visible );
toArray( control.querySelectorAll( 'a' ) ).forEach( function ( link ) {
var href = link.getAttribute( 'href' ) || '';
var prefix = href.indexOf( 'veaction=edit' ) !== -1 ? '编辑章节:' : '编辑章节的源代码:';
link.setAttribute( 'title', prefix + label );
} );
} );
}
function getElementByFragment( fragment ) {
var decoded;
var targets;
var activeTarget;
if ( !fragment ) {
return null;
}
try {
decoded = decodeURIComponent( fragment ).replace( / /g, '_' );
} catch ( e ) {
return null;
}
targets = toArray( document.querySelectorAll( idSelector( decoded ) ) ).concat(
toArray( document.querySelectorAll( attributeSelector( 'data-switchtabber-anchor', decoded ) ) )
).filter( function ( target, index, allTargets ) {
return allTargets.indexOf( target ) === index;
} );
activeTarget = targets.filter( function ( target ) {
return getSwitchTabIndexForElement( target ) === activeSwitchTab;
} )[ 0 ];
return activeTarget || targets[ 0 ] || null;
}
function getFragmentTarget( link ) {
var href = link.getAttribute( 'href' );
var hashIndex;
if ( !href || href.indexOf( '#' ) === -1 ) {
return null;
}
hashIndex = href.indexOf( '#' );
return getElementByFragment( href.slice( hashIndex + 1 ) );
}
function getStableHeadingAnchor( heading ) {
var headline = heading.querySelector( '.mw-headline' );
var anchor = headline && headline.querySelector( '[id]' );
return anchor && anchor.id !== headline.id &&
document.querySelectorAll( idSelector( anchor.id ) ).length === 1 ? anchor.id : null;
}
function syncTocEntry( heading, state ) {
var stableAnchor = getStableHeadingAnchor( heading );
toArray( document.querySelectorAll( '#toc a[href*="#"], .toc a[href*="#"], .vector-toc a[href*="#"]' ) ).forEach( function ( link ) {
var target = getFragmentTarget( link );
var tocItem;
var labelElement;
if ( !target || target.closest( 'h1, h2, h3, h4, h5, h6' ) !== heading ) {
return;
}
tocItem = link.closest( 'li' );
if ( tocItem ) {
setSwitchElementVisible( tocItem, state.visible );
}
if ( stableAnchor ) {
link.setAttribute( 'href', '#' + stableAnchor );
}
if ( !state.visible ) {
return;
}
labelElement = link.querySelector( '.vector-toc-text span:not(.vector-toc-numb), .toctext, .toc-text' );
if ( labelElement && labelElement.textContent !== state.label ) {
labelElement.textContent = state.label;
}
} );
}
function syncSwitchHeading( heading, tabIndex ) {
var state = getHeadingState( heading, tabIndex );
var wrapper = heading.closest( '.mw-heading' ) || heading;
setSwitchElementVisible( wrapper, state.visible );
syncEditSectionControls( heading, state.label, state.visible );
syncTocEntry( heading, state );
}
function syncSwitchHeadings( tabIndex ) {
toArray( document.querySelectorAll( 'h1, h2, h3, h4, h5, h6' ) ).forEach( function ( heading ) {
if (
heading.querySelector( '.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) ) ||
getSwitchTabIndexForElement( heading ) !== null
) {
syncSwitchHeading( heading, tabIndex );
}
} );
}
function mutationTouchesToc( mutation ) {
var target = mutation.target;
if ( target.nodeType === 1 && target.closest( TOC_CONTAINER_SELECTOR ) ) {
return true;
}
return toArray( mutation.addedNodes ).some( function ( node ) {
return node.nodeType === 1 && (
node.matches( TOC_CONTAINER_SELECTOR ) ||
!!node.querySelector( TOC_CONTAINER_SELECTOR )
);
} );
}
function observeTocChanges() {
if ( !window.MutationObserver || tocObserver || !document.body ) {
return;
}
tocObserver = new window.MutationObserver( function ( mutations ) {
if ( toArray( mutations ).some( mutationTouchesToc ) ) {
syncSwitchHeadings( activeSwitchTab );
}
} );
tocObserver.observe( document.body, {
childList: true,
subtree: true
} );
}
function tabberexSwitchTab( tabberId, index ) {
tabberexHideAllTabs( tabberId );
tabberexShowTab( tabberId, index );
if ( tabberId === SWITCH_TABBER_ID ) {
activeSwitchTab = index;
syncSwitchHeadings( index );
}
}
function getExHeader( element ) {
var attributes = {};
var names = element.getAttributeNames();
var i;
for ( i = 0; i < names.length; i++ ) {
attributes[ names[ i ] ] = element.getAttribute( names[ i ] );
}
return {
html: element.innerHTML,
attributes: attributes
};
}
function readTabDetails( tabElement, index ) {
var exHeaderElement = tabElement.querySelector( 'p > span.tabberex-tab-header, span.tabberex-tab-header' );
var details = {
header: tabElement.getAttribute( 'data-tab-header' ) || String( index + 1 ),
exHeader: exHeaderElement ? getExHeader( exHeaderElement ) : null,
isDefault: tabElement.classList.contains( 'tabberex-tab-default' ),
location: tabElement.getAttribute( 'data-tab-location' )
};
if ( exHeaderElement ) {
exHeaderElement.parentNode.removeChild( exHeaderElement );
}
return details;
}
function makeTabButton( tabberId, details, index ) {
var tab = document.createElement( 'div' );
var label = document.createElement( 'span' );
var attr;
tab.className = TAB_CLASS;
tab.style.cursor = 'pointer';
label.className = LABEL_CLASS;
if ( details.exHeader ) {
for ( attr in details.exHeader.attributes ) {
label.setAttribute( attr, details.exHeader.attributes[ attr ] );
}
label.innerHTML = details.exHeader.html;
} else {
label.textContent = details.header;
}
tab.appendChild( label );
tab.addEventListener( 'click', function () {
if ( details.location ) {
window.location.href = details.location;
return;
}
tabberexSwitchTab( tabberId, index + 1 );
} );
return tab;
}
function buildTabHead( host, tabberId, tabDetails ) {
var i;
host.innerHTML = '';
host.className = TABBER_HEAD_CLASS;
host.setAttribute( 'data-tabber-id', tabberId );
for ( i = 0; i < tabDetails.length; i++ ) {
host.appendChild( makeTabButton( tabberId, tabDetails[ i ], i ) );
}
}
function collectTabDetails( container, hideTabs ) {
var details = [];
var defaultIndex = 0;
toArray( container.querySelectorAll( ':scope > .tabberex-tab' ) ).forEach( function ( tab, index ) {
details.push( readTabDetails( tab, index ) );
if ( details[ index ].isDefault ) {
defaultIndex = index;
}
if ( hideTabs ) {
tab.style.display = 'none';
}
} );
return {
details: details,
defaultIndex: defaultIndex
};
}
function registerTabber( defaultTabs, tabberIds, tabberId, defaultIndex ) {
if ( defaultTabs[ tabberId ] === undefined ) {
defaultTabs[ tabberId ] = defaultIndex;
}
tabberIds.push( tabberId );
}
function processHead( head, defaultTabs, tabberIds, tabberIndex ) {
var tabberId = head.getAttribute( 'data-tabber-id' ) || String( tabberIndex );
var tabState;
if ( head.dataset.tabberexProcessed || !head.querySelector( '.tabberex-tab' ) ) {
return tabberIndex;
}
tabState = collectTabDetails( head, false );
head.dataset.tabberexProcessed = '1';
buildTabHead( head, tabberId, tabState.details );
registerTabber( defaultTabs, tabberIds, tabberId, tabState.defaultIndex );
return tabberIndex + 1;
}
function processStandaloneTabber( tabber, defaultTabs, tabberIds, tabberIndex ) {
var tabberId = tabber.getAttribute( 'data-tabber-id' ) || String( tabberIndex );
var head = document.createElement( 'div' );
var tabState;
if ( tabber.dataset.tabberexProcessed ) {
return tabberIndex;
}
tabState = collectTabDetails( tabber, true );
tabber.dataset.tabberexProcessed = '1';
tabber.setAttribute( 'data-tabber-id', tabberId );
tabber.classList.remove( 'tabberex' );
tabber.classList.add( 'tabberex-body' );
buildTabHead( head, tabberId, tabState.details );
tabber.parentNode.insertBefore( head, tabber );
registerTabber( defaultTabs, tabberIds, tabberId, tabState.defaultIndex );
return tabberIndex + 1;
}
function processTabbers() {
var defaultTabs = {};
var tabberIds = [];
var tabberIndex = 0;
Array.prototype.slice.call( document.querySelectorAll( '.tabberex-head' ) ).reverse().forEach( function ( head ) {
tabberIndex = processHead( head, defaultTabs, tabberIds, tabberIndex );
} );
Array.prototype.slice.call( document.querySelectorAll( '.tabberex-body' ) ).reverse().forEach( function ( body ) {
$( body ).children().css( 'display', 'none' );
} );
Array.prototype.slice.call( document.querySelectorAll( '.tabberex' ) ).reverse().forEach( function ( tabber ) {
tabberIndex = processStandaloneTabber( tabber, defaultTabs, tabberIds, tabberIndex );
} );
tabberIds.forEach( function ( tabberId ) {
tabberexShowTab( tabberId, defaultTabs[ tabberId ] + 1 );
} );
}
function getFileUrl( filename ) {
return mw.util.getUrl( 'Special:FilePath/' + filename );
}
function setIconMask( icon, filename ) {
var url = getFileUrl( filename );
icon.style.webkitMaskImage = 'url("' + url + '")';
icon.style.maskImage = 'url("' + url + '")';
}
function applySwitchTabberIconMasks( $content ) {
var root = $content && $content.length ? $content[ 0 ] : document;
$( root ).find( '.tabberex-head' + attrSelector( SWITCH_TABBER_ID ) + ' .switchtabber-icon--mobile' ).each( function () {
setIconMask( this, 'SwitchTabber_Mobile.png' );
} );
$( root ).find( '.tabberex-head' + attrSelector( SWITCH_TABBER_ID ) + ' .switchtabber-icon--switch' ).each( function () {
setIconMask( this, 'SwitchTabber_Switch.png' );
} );
}
function makeSwitchTab( index, labelText, filename, extraIconClass ) {
var tab = document.createElement( 'div' );
var label = document.createElement( 'span' );
var icon = document.createElement( 'span' );
tab.className = TAB_CLASS;
tab.style.cursor = 'pointer';
label.className = LABEL_CLASS;
icon.className = 'switchtabber-icon' + ( extraIconClass ? ' ' + extraIconClass : '' );
icon.setAttribute( 'aria-hidden', 'true' );
setIconMask( icon, filename );
label.appendChild( icon );
label.appendChild( document.createTextNode( labelText ) );
tab.appendChild( label );
tab.addEventListener( 'click', function () {
switchtabberSwitchTab( index );
} );
return tab;
}
function createSwitchTabber( modifierClass ) {
var switchTabber = document.createElement( 'div' );
switchTabber.className = TABBER_HEAD_CLASS + ' switchtabber-head' + ( modifierClass ? ' ' + modifierClass : '' );
switchTabber.setAttribute( 'data-tabber-id', SWITCH_TABBER_ID );
switchTabber.dataset.switchtabberGenerated = '1';
switchTabber.appendChild( makeSwitchTab( 1, getPlatformLabel( 'mobile' ), 'SwitchTabber_Mobile.png', 'switchtabber-icon--mobile' ) );
switchTabber.appendChild( makeSwitchTab( 2, getPlatformLabel( 'switch' ), 'SwitchTabber_Switch.png', 'switchtabber-icon--switch' ) );
return switchTabber;
}
function getSelectedSwitchTab() {
var match = document.cookie.match( /(?:^|;\s*)SwitchTab=(Mobile|Switch)(?:;|$)/ );
return match && match[ 1 ] === 'Switch' ? 2 : 1;
}
function setSelectedSwitchTab( index ) {
document.cookie = COOKIE_NAME + '=' + ( index === 1 ? 'Mobile' : 'Switch' ) + '; path=/; SameSite=Lax';
}
function switchtabberSwitchTab( index ) {
tabberexSwitchTab( SWITCH_TABBER_ID, index );
setSelectedSwitchTab( index );
}
function activateSwitchTabForHash() {
var fragment = window.location.hash.slice( 1 );
var target = getElementByFragment( fragment );
var tabIndex = getSwitchTabIndexForElement( target );
if ( !target ) {
return;
}
if ( tabIndex !== null && tabIndex !== activeSwitchTab ) {
switchtabberSwitchTab( tabIndex );
}
window.setTimeout( function () {
target.scrollIntoView();
}, 0 );
}
function hasSwitchTabberContent() {
return !!document.querySelector(
'.tabberex-body' + attrSelector( SWITCH_TABBER_ID ) + ', .tabberex' + attrSelector( SWITCH_TABBER_ID ) + ', .tabberex-head' + attrSelector( SWITCH_TABBER_ID ) + ':not([data-switchtabber-generated])'
);
}
function addLocalPageHeaderTabber() {
var heading = document.getElementById( 'firstHeading' ) || document.querySelector( '.mw-first-heading' );
var wrapper;
if ( !heading || document.querySelector( '.switchtabber-local-heading > .switchtabber-head' ) ) {
return;
}
wrapper = document.createElement( 'div' );
wrapper.className = 'switchtabber-local-heading';
heading.parentNode.insertBefore( wrapper, heading );
wrapper.appendChild( heading );
wrapper.appendChild( createSwitchTabber( 'switchtabber-head--page' ) );
}
function addFandomPageHeaderTabber() {
if ( document.querySelector( '.page-header__title-wrapper > .switchtabber-head' ) ) {
return;
}
if ( $( '.page-header__title-wrapper' ).length ) {
$( '.page-header__title-wrapper' )
.css( 'display', 'flex' )
.css( 'align-content', 'center' )
.css( 'justify-content', 'space-between' )
.append( createSwitchTabber( 'switchtabber-head--page' ) );
} else {
addLocalPageHeaderTabber();
}
}
function addFandomStickyHeaderTabber() {
if ( !$( '.fandom-sticky-header > :first-child' ).length || document.querySelector( '.fandom-sticky-header .switchtabber-head' ) ) {
return;
}
$( createSwitchTabber( 'switchtabber-head--sticky' ) ).insertAfter( '.fandom-sticky-header > :first-child' );
}
function addEditorPreviewTabber() {
if ( !$( '.oo-ui-processDialog-actions-safe' ).length || document.querySelector( '.oo-ui-processDialog-actions-safe .switchtabber-head' ) ) {
return;
}
$( '.oo-ui-processDialog-actions-safe' )
.css( 'flex-direction', 'row-reverse' )
.append( createSwitchTabber( 'switchtabber-head--editor' ) );
}
function addSwitchTabber() {
var initialTab;
if ( !hasSwitchTabberContent() ) {
return;
}
addFandomPageHeaderTabber();
addFandomStickyHeaderTabber();
addEditorPreviewTabber();
applySwitchTabberIconMasks( $( document ) );
initialTab = getSelectedSwitchTab();
observeTocChanges();
tabberexSwitchTab( SWITCH_TABBER_ID, initialTab );
activateSwitchTabForHash();
}
window.tabberex_showTab = tabberexShowTab;
window.tabberex_hideTab = tabberexHideTab;
window.tabberex_hideAllTabs = tabberexHideAllTabs;
window.tabberex_switchTab = tabberexSwitchTab;
window.switchtabber_switchTab = switchtabberSwitchTab;
window.addEventListener( 'hashchange', activateSwitchTabForHash );
mw.hook( 'wikipage.content' ).add( processTabbers );
mw.hook( 'wikipage.content' ).add( addSwitchTabber );
}( this, jQuery, mediaWiki ) );