%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/resources/mobile.toggle/ |
Current File : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/resources/mobile.toggle/toggle.js |
( function ( M ) { var browser = M.require( 'mobile.startup/Browser' ).getSingleton(), util = M.require( 'mobile.startup/util' ), escapeHash = util.escapeHash, arrowOptions = { name: 'arrow', additionalClassNames: 'indicator' }, Icon = M.require( 'mobile.startup/Icon' ); /** * A class for enabling toggling * * @class Toggler * @extends OO.EventEmitter */ function Toggler() { OO.EventEmitter.call( this ); this._enable.apply( this, arguments ); } OO.mixinClass( Toggler, OO.EventEmitter ); /** * Using the settings module looks at what sections were previously expanded on * existing page. * * @param {Page} page * @return {Object} representing open sections */ function getExpandedSections( page ) { var expandedSections = JSON.parse( mw.storage.get( 'expandedSections' ) || '{}' ); expandedSections[page.title] = expandedSections[page.title] || {}; return expandedSections; } /** * @param {Object} expandedSections * Save expandedSections to localStorage */ function saveExpandedSections( expandedSections ) { mw.storage.set( 'expandedSections', JSON.stringify( expandedSections ) ); } /** * Given an expanded heading, store it to localStorage. * If the heading is collapsed, remove it from localStorage. * * @param {jQuery.Object} $heading - A heading belonging to a section * @param {Page} page */ function storeSectionToggleState( $heading, page ) { var headline = $heading.find( 'span' ).attr( 'id' ), isSectionOpen = $heading.hasClass( 'open-block' ), expandedSections = getExpandedSections( page ); if ( headline ) { if ( isSectionOpen ) { expandedSections[page.title][headline] = ( new Date() ).getTime(); } else { delete expandedSections[page.title][headline]; } saveExpandedSections( expandedSections ); } } /** * Expand sections that were previously expanded before leaving this page. * @param {Toggler} toggler * @param {jQuery.Object} $container * @param {Page} page */ function expandStoredSections( toggler, $container, page ) { var $sectionHeading, $headline, expandedSections = getExpandedSections( page ), $headlines = $container.find( '.section-heading span' ); $headlines.each( function () { $headline = $container.find( this ); $sectionHeading = $headline.parents( '.section-heading' ); // toggle only if the section is not already expanded if ( expandedSections[page.title][$headline.attr( 'id' )] && !$sectionHeading.hasClass( 'open-block' ) ) { toggler.toggle( $sectionHeading, page ); } } ); } /** * Clean obsolete (saved more than a day ago) expanded sections from * localStorage. * @param {Page} page */ function cleanObsoleteStoredSections( page ) { var now = ( new Date() ).getTime(), expandedSections = getExpandedSections( page ), // the number of days between now and the time a setting was saved daysDifference; Object.keys( expandedSections ).forEach( function ( page ) { var sections = expandedSections[ page ]; // clean the setting if it is more than a day old Object.keys( sections ).forEach( function ( section ) { var timestamp = sections[ section ]; daysDifference = Math.floor( ( now - timestamp ) / 1000 / 60 / 60 / 24 ); if ( daysDifference >= 1 ) { delete expandedSections[page][section]; } } ); } ); saveExpandedSections( expandedSections ); } /** * Given a heading, toggle it and any of its children * * @memberof Toggler * @instance * @param {jQuery.Object} $heading A heading belonging to a section */ Toggler.prototype.toggle = function ( $heading ) { var indicator, wasExpanded = $heading.is( '.open-block' ), page = $heading.data( 'page' ), sectionNumber = $heading.data( 'section-number' ), $content = $heading.next(); $heading.toggleClass( 'open-block' ); $heading.data( 'indicator' ).remove(); /** * @event toggled */ this.emit( 'toggled', wasExpanded, sectionNumber ); arrowOptions.rotation = wasExpanded ? 0 : 180; indicator = new Icon( arrowOptions ).prependTo( $heading ); $heading.data( 'indicator', indicator ); /** * Global event emitted before a section is being toggled * * @event section-toggling * @type {Object} * @property {Page} page * @property {bool} wasExpanded * @property {bool} isReferenceSection * @property {jQuery.Object} $heading */ M.emit( 'before-section-toggled', { page: page, wasExpanded: wasExpanded, $heading: $heading, isReferenceSection: Boolean( $content.attr( 'data-is-reference-section' ) ) } ); $content .toggleClass( 'open-block' ) .attr( { 'aria-pressed': !wasExpanded, 'aria-expanded': !wasExpanded } ); /** * Global event emitted after a section has been toggled * @event section-toggled */ M.emit( 'section-toggled', wasExpanded, sectionNumber ); if ( !browser.isWideScreen() ) { storeSectionToggleState( $heading, page ); } }; /** * Enables toggling via enter and space keys * * @param {Toggler} toggler instance. * @param {jQuery.Object} $heading */ function enableKeyboardActions( toggler, $heading ) { $heading.on( 'keypress', function ( ev ) { if ( ev.which === 13 || ev.which === 32 ) { // Only handle keypresses on the "Enter" or "Space" keys toggler.toggle( $heading ); } } ).find( 'a' ).on( 'keypress mouseup', function ( ev ) { ev.stopPropagation(); } ); } /** * Reveals an element and its parent section as identified by it's id * * @memberof Toggler * @instance * @param {string} selector A css selector that identifies a single element * @param {Object} $container jQuery element to search in */ Toggler.prototype.reveal = function ( selector, $container ) { var $target, $heading; // jQuery will throw for hashes containing certain characters which can break toggling try { $target = $container.find( escapeHash( selector ) ); $heading = $target.parents( '.collapsible-heading' ); // The heading is not a section heading, check if in a content block! if ( !$heading.length ) { $heading = $target.parents( '.collapsible-block' ).prev( '.collapsible-heading' ); } if ( $heading.length && !$heading.hasClass( 'open-block' ) ) { this.toggle( $heading ); } if ( $heading.length ) { // scroll again after opening section (opening section makes the page longer) window.scrollTo( 0, $target.offset().top ); } } catch ( e ) {} }; /** * Enables section toggling in a given container when wgMFCollapseSectionsByDefault * is enabled. * * @memberof Toggler * @instance * @param {jQuery.Object} $container to apply toggling to * @param {string} prefix a prefix to use for the id. * @param {Page} [page] to allow storage of session for future visits * @param {Page} [isClosed] whether the element should begin closed * @private */ Toggler.prototype._enable = function ( $container, prefix, page, isClosed ) { var tagName, expandSections, indicator, $content, $firstHeading, $link, self = this, collapseSectionsByDefault = mw.config.get( 'wgMFCollapseSectionsByDefault' ); // Also allow .section-heading if some extensions like Wikibase // want to toggle other headlines than direct descendants of $container. $firstHeading = $container.find( '> h1,> h2,> h3,> h4,> h5,> h6,.section-heading' ).eq( 0 ); tagName = $firstHeading.prop( 'tagName' ) || 'H1'; if ( collapseSectionsByDefault === undefined ) { // Old default behavior if on cached output collapseSectionsByDefault = true; } expandSections = !collapseSectionsByDefault || ( mw.config.get( 'wgMFExpandAllSectionsUserOption' ) && mw.storage.get( 'expandSections' ) === 'true' ); $container.children( tagName ).each( function ( i ) { var isReferenceSection, $heading = $container.find( this ), $indicator = $heading.find( '.indicator' ), id = prefix + 'collapsible-block-' + i; // Be sure there is a div wrapping the section content. // Otherwise, collapsible sections for this page is not enabled. if ( $heading.next().is( 'div' ) ) { $content = $heading.next( 'div' ); isReferenceSection = Boolean( $content.attr( 'data-is-reference-section' ) ); $heading .addClass( 'collapsible-heading ' ) .data( 'section-number', i ) .data( 'page', page ) .attr( { tabindex: 0, 'aria-haspopup': 'true', 'aria-controls': id } ) .on( 'click', function ( ev ) { // don't toggle, if the click target was a link // (a link in a section heading) // See T117880 if ( !ev.target.href ) { // prevent taps/clicks on edit button after toggling (bug 56209) ev.preventDefault(); self.toggle( $heading ); } } ); arrowOptions.rotation = expandSections ? 180 : 0; indicator = new Icon( arrowOptions ); if ( $indicator.length ) { // replace the existing indicator $indicator.replaceWith( indicator.$el ); } else { indicator.prependTo( $heading ); } $heading.data( 'indicator', indicator.$el ); $content .addClass( 'collapsible-block' ) .eq( 0 ) .attr( { // We need to give each content block a unique id as that's // the only way we can tell screen readers what element we're // referring to (aria-controls) id: id, 'aria-pressed': 'false', 'aria-expanded': 'false' } ); enableKeyboardActions( self, $heading ); if ( !isReferenceSection && ( !isClosed && browser.isWideScreen() || expandSections ) ) { // Expand sections by default on wide screen devices // or if the expand sections setting is set. // The wide screen logic for determining whether to collapse sections initially // should be kept in sync with mobileoptions#initLocalStorageElements(). self.toggle( $heading ); } } } ); /* eslint-disable no-restricted-properties */ /** * Checks the existing hash and toggles open any section that contains the fragment. * * @method */ function checkHash() { var hash = window.location.hash; if ( hash.indexOf( '#' ) === 0 ) { self.reveal( hash, $container ); } } /** * Checks the value of wgInternalRedirectTargetUrl and reveals the collapsed * section that contains it if present * * @method */ function checkInternalRedirectAndHash() { var internalRedirect = mw.config.get( 'wgInternalRedirectTargetUrl' ), internalRedirectHash = internalRedirect ? internalRedirect.split( '#' )[1] : false; if ( internalRedirectHash ) { window.location.hash = internalRedirectHash; self.reveal( internalRedirectHash, $container ); } } /* eslint-enable no-restricted-properties */ checkInternalRedirectAndHash(); checkHash(); // Restricted to links created by editors and thus outside our control // T166544 - don't do this for reference links - they will be handled elsewhere $link = $container.find( 'a:not(.reference a)' ); $link.on( 'click', function () { // the link might be an internal link with a hash. // if it is check if we need to reveal any sections. if ( $link.attr( 'href' ) !== undefined && $link.attr( 'href' ).indexOf( '#' ) > -1 ) { checkHash(); } } ); util.getWindow().on( 'hashchange', function () { checkHash(); } ); if ( !browser.isWideScreen() && page ) { expandStoredSections( this, $container, page ); cleanObsoleteStoredSections( page ); } }; Toggler._getExpandedSections = getExpandedSections; Toggler._expandStoredSections = expandStoredSections; Toggler._cleanObsoleteStoredSections = cleanObsoleteStoredSections; M.define( 'mobile.toggle/Toggler', Toggler ); // resource-modules-disable-line }( mw.mobileFrontend ) );