/* TOC Module for wikiEditor */ /*jshint onevar:false */ ( function ( $, mw ) { $.wikiEditor.modules.toc = { /** * Compatability map */ browsers: { // Left-to-right languages ltr: { msie: [['>=', 7]], firefox: [['>=', 3]], opera: [['>=', 10]], safari: [['>=', 4]], chrome: [['>=', 4]] }, // Right-to-left languages rtl: { msie: [['>=', 8]], firefox: [['>=', 3]], opera: [['>=', 10]], safari: [['>=', 4]], chrome: [['>=', 4]] } }, /** * Core Requirements */ req: [ 'iframe' ], /** * Configuration */ cfg: { // Default width of table of contents defaultWidth: '166px', // Minimum width to allow resizing to before collapsing the table of contents - used when resizing and collapsing minimumWidth: '70px', // Minimum width of the wikiText area textMinimumWidth: '450px', // The style property to be used for positioning the flexible module in regular mode flexProperty: 'marginRight', // Boolean var indicating text direction rtl: false }, /** * API accessible functions */ api: { // }, /** * Event handlers */ evt: { /** * @param context * @param event */ change: function( context ) { $.wikiEditor.modules.toc.fn.update( context ); }, /** * @param context * @param event */ ready: function( context ) { // Add the TOC to the document $.wikiEditor.modules.toc.fn.build( context ); if ( !context.$content ) { return; } context.$content.parent() .blur( function() { var context = event.data.context; $.wikiEditor.modules.toc.fn.unhighlight( context ); }); $.wikiEditor.modules.toc.fn.improveUI(); $.wikiEditor.modules.toc.evt.resize( context ); }, /** * @param context * @param event */ resize: function( context ) { var availableWidth = context.$wikitext.width() - parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ), totalMinWidth = parseFloat( $.wikiEditor.modules.toc.cfg.minimumWidth ) + parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ); context.$ui.find( '.wikiEditor-ui-right' ) .resizable( 'option', 'maxWidth', availableWidth ); if ( context.modules.toc.$toc.data( 'positionMode' ) !== 'disabled' && context.$wikitext.width() < totalMinWidth ) { $.wikiEditor.modules.toc.fn.disable( context ); } else if ( context.modules.toc.$toc.data( 'positionMode' ) === 'disabled' && context.$wikitext.width() > totalMinWidth ) { $.wikiEditor.modules.toc.fn.enable( context ); } else if ( context.modules.toc.$toc.data( 'positionMode' ) === 'regular' && context.$ui.find( '.wikiEditor-ui-right' ).width() > availableWidth ) { //switch mode $.wikiEditor.modules.toc.fn.switchLayout( context ); } else if ( context.modules.toc.$toc.data( 'positionMode' ) === 'goofy' && context.modules.toc.$toc.data( 'previousWidth' ) < context.$wikitext.width() ) { //switch mode $.wikiEditor.modules.toc.fn.switchLayout( context ); } if ( context.modules.toc.$toc.data( 'positionMode' ) === 'goofy' ) { context.modules.toc.$toc.find( 'div' ).autoEllipsis( { 'position': 'right', 'tooltip': true, 'restoreText': true } ); } // reset the height of the TOC if ( !context.modules.toc.$toc.data( 'collapsed' ) ){ context.modules.toc.$toc.height( context.$ui.find( '.wikiEditor-ui-left' ).height() - context.$ui.find( '.tab-toc' ).outerHeight() ); } // store the width of the view for comparison on next resize context.modules.toc.$toc.data( 'previousWidth', context.$wikitext.width() ); }, /** * @param context * @param event */ mark: function( context ) { var hash = ''; var markers = context.modules.highlight.markers; var tokenArray = context.modules.highlight.tokenArray; var outline = context.data.outline = []; var h = 0; for ( var i = 0; i < tokenArray.length; i++ ) { if ( tokenArray[i].label !== 'TOC_HEADER' ) { continue; } h++; markers.push( { index: h, start: tokenArray[i].tokenStart, end: tokenArray[i].offset, type: 'toc', anchor: 'tag', afterWrap: function( node ) { var marker = $( node ).data( 'marker' ); $( node ).addClass( 'wikiEditor-toc-header' ) .addClass( 'wikiEditor-toc-section-' + marker.index ) .data( 'section', marker.index ); }, beforeUnwrap: function( node ) { $( node ).removeClass( 'wikiEditor-toc-header' ) .removeClass( 'wikiEditor-toc-section-' + $( node ).data( 'section' ) ); }, onSkip: function( node ) { var marker = $( node ).data( 'marker' ); if ( $( node ).data( 'section' ) !== marker.index ) { $( node ) .removeClass( 'wikiEditor-toc-section-' + $( node ).data( 'section' ) ) .addClass( 'wikiEditor-toc-section-' + marker.index ) .data( 'section', marker.index ); } }, getAnchor: function( ca1 ) { return $( ca1.parentNode ).is( '.wikiEditor-toc-header' ) ? ca1.parentNode : null; } } ); hash += tokenArray[i].match[2] + '\n'; outline.push ( { 'text': tokenArray[i].match[2], 'level': tokenArray[i].match[1].length, 'index': h } ); } // Only update the TOC if it's been changed - we do this by comparing a hash of the headings this time to last if ( typeof context.modules.toc.lastHash === 'undefined' || context.modules.toc.lastHash !== hash ) { $.wikiEditor.modules.toc.fn.build( context ); $.wikiEditor.modules.toc.fn.update( context ); // Remember the changed version context.modules.toc.lastHash = hash; } } }, exp: [ { 'regex': /^(={1,6})([^\r\n]+?)\1\s*$/m, 'label': 'TOC_HEADER', 'markAfter': true } ], /** * Internally used functions */ fn: { /** * Creates a table of contents module within a wikiEditor * * @param {Object} context Context object of editor to create module in * @param {Object} config Configuration object to create module from */ create: function( context ) { if ( '$toc' in context.modules.toc ) { return; } $.wikiEditor.modules.toc.cfg.rtl = $( 'body' ).is( '.rtl' ); $.wikiEditor.modules.toc.cfg.flexProperty = $.wikiEditor.modules.toc.cfg.rtl ? 'marginLeft' : 'marginRight'; context.$ui.find( '.wikiEditor-ui-left' ).height(); context.modules.toc.$toc = $( '
' ) .addClass( 'wikiEditor-ui-toc' ) .data( 'context', context ) .data( 'positionMode', 'regular' ) .data( 'collapsed', false ); context.$ui.find( '.wikiEditor-ui-right' ) .append( context.modules.toc.$toc ); context.modules.toc.$toc.height( context.$ui.find( '.wikiEditor-ui-left' ).height() ); $.wikiEditor.modules.toc.fn.redraw( context, $.wikiEditor.modules.toc.cfg.defaultWidth ); }, redraw: function( context, fixedWidth ) { fixedWidth = parseFloat( fixedWidth ); if ( context.modules.toc.$toc.data( 'positionMode' ) === 'regular' ) { context.$ui.find( '.wikiEditor-ui-right' ) .css( 'width', fixedWidth + 'px' ); context.$ui.find( '.wikiEditor-ui-left' ) .css( $.wikiEditor.modules.toc.cfg.flexProperty, ( -1 * fixedWidth ) + 'px' ) .children() .css( $.wikiEditor.modules.toc.cfg.flexProperty, fixedWidth + 'px' ); } else if( context.modules.toc.$toc.data( 'positionMode' ) === 'goofy' ) { context.$ui.find( '.wikiEditor-ui-left' ) .css( 'width', fixedWidth ); context.$ui.find( '.wikiEditor-ui-right' ) .css( $.wikiEditor.modules.toc.cfg.rtl ? 'right': 'left', fixedWidth ); context.$wikitext.css( 'height', context.$ui.find( '.wikiEditor-ui-right' ).height() ); } }, switchLayout: function( context ) { var width; context.$ui.find( '.wikiEditor-ui-right' ).height(); if ( context.modules.toc.$toc.data( 'positionMode' ) === 'regular' && !context.modules.toc.$toc.data( 'collapsed' ) ) { // store position mode context.modules.toc.$toc.data( 'positionMode', 'goofy' ); // store the width of the TOC, to ensure we dont allow it to be larger than this when switching back context.modules.toc.$toc.data( 'positionModeChangeAt', context.$ui.find( '.wikiEditor-ui-right' ).width() ); width = $.wikiEditor.modules.toc.cfg.textMinimumWidth; // set our styles for goofy mode context.$ui.find( '.wikiEditor-ui-left' ) .css( $.wikiEditor.modules.toc.cfg.flexProperty, '') .css( { 'position': 'absolute', 'float': 'none', 'left': $.wikiEditor.modules.toc.cfg.rtl ? 'auto': 0, 'right' : $.wikiEditor.modules.toc.cfg.rtl ? 0 : 'auto' } ) .children() .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ); context.$ui.find( '.wikiEditor-ui-right' ) .css( { 'width': 'auto', 'position': 'absolute', 'float': 'none', 'right': $.wikiEditor.modules.toc.cfg.rtl ? 'auto': 0, 'left' : $.wikiEditor.modules.toc.cfg.rtl ? 0 : 'auto' } ); context.$wikitext .css( 'position', 'relative' ); } else if ( context.modules.toc.$toc.data( 'positionMode' ) === 'goofy' ) { // store position mode context.modules.toc.$toc.data( 'positionMode', 'regular' ); // set width width = context.$wikitext.width() - context.$ui.find( '.wikiEditor-ui-left' ).width(); if ( width > context.modules.toc.$toc.data( 'positionModeChangeAt' ) ) { width = context.modules.toc.$toc.data( 'positionModeChangeAt' ); } // set our styles for regular mode context.$wikitext .css( { 'position': '', 'height': '' } ); context.$ui.find( '.wikiEditor-ui-right' ) .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ) .css( { 'position': '', 'left': '', 'right': '', 'float': '', 'top': '', 'height': '' } ); context.$ui.find( '.wikiEditor-ui-left' ) .css( { 'width': '', 'position': '', 'left': '', 'float': '', 'right': '' } ); } $.wikiEditor.modules.toc.fn.redraw( context, width ); }, disable: function( context ) { if ( context.modules.toc.$toc.data( 'collapsed' ) ) { context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).hide(); } else { if( context.modules.toc.$toc.data( 'positionMode' ) === 'goofy' ) { $.wikiEditor.modules.toc.fn.switchLayout( context ); } context.$ui.find( '.wikiEditor-ui-right' ).hide(); context.$ui.find( '.wikiEditor-ui-left' ) .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ) .children() .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ); } context.modules.toc.$toc.data( 'positionMode', 'disabled' ); }, enable: function( context ) { context.modules.toc.$toc.data( 'positionMode', 'regular' ); if ( context.modules.toc.$toc.data( 'collapsed' ) ) { context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).show(); } else { context.$ui.find( '.wikiEditor-ui-right' ).show(); $.wikiEditor.modules.toc.fn.redraw( context, $.wikiEditor.modules.toc.cfg.minimumWidth ); context.modules.toc.$toc.find( 'div' ).autoEllipsis( { 'position': 'right', 'tooltip': true, 'restoreText': true } ); } }, unhighlight: function( context ) { // FIXME: For some reason, IE calls this function twice, the first time with context undefined // Investigate this when you have time please! In the meantime, the user interaction is working just // fine because the second call is valid if ( context ) { context.modules.toc.$toc.find( 'div' ).removeClass( 'current' ); } }, /** * Highlight the section the cursor is currently within * * @param {Object} context */ update: function () { //temporarily commenting this out because it is causing all kinds of cursor //and text jumping issues in IE. WIll get back to this --pdhanda /* var div = context.fn.beforeSelection( 'wikiEditor-toc-header' ); if ( div === null ) { // beforeSelection couldn't figure it out, keep the old highlight state return; } $.wikiEditor.modules.toc.fn.unhighlight( context ); var section = div.data( 'section' ) || 0; if ( context.data.outline.length > 0 ) { var sectionLink = context.modules.toc.$toc.find( 'div.section-' + section ); sectionLink.addClass( 'current' ); // Scroll the highlighted link into view if necessary var relTop = sectionLink.offset().top - context.modules.toc.$toc.offset().top; var scrollTop = context.modules.toc.$toc.scrollTop(); var divHeight = context.modules.toc.$toc.height(); var sectionHeight = sectionLink.height(); if ( relTop < 0 ) // Scroll up context.modules.toc.$toc.scrollTop( scrollTop + relTop ); else if ( relTop + sectionHeight > divHeight ) // Scroll down context.modules.toc.$toc.scrollTop( scrollTop + relTop + sectionHeight - divHeight ); } */ }, /** * Collapse the contents module * * @param {Object} event Event object with context as data */ collapse: function () { var $this = $( this ), context = $this.data( 'context' ); if ( context.modules.toc.$toc.data( 'positionMode' ) === 'goofy' ) { $.wikiEditor.modules.toc.fn.switchLayout( context ); } var pT = $this.parent().position().top - 1; context.modules.toc.$toc.data( 'collapsed', true ); var leftParam = {}, leftChildParam = {}; leftParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = '-1px'; leftChildParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = '1px'; context.$ui.find( '.wikiEditor-ui-left' ) .animate( leftParam, 'fast', function() { $( this ).css( $.wikiEditor.modules.toc.cfg.flexProperty, 0 ); } ) .children() .animate( leftChildParam, 'fast', function() { $( this ).css( $.wikiEditor.modules.toc.cfg.flexProperty, 0 ); } ); context.$ui.find( '.wikiEditor-ui-right' ) .css( { 'marginTop' : '1px', 'position' : 'absolute', 'left' : $.wikiEditor.modules.toc.cfg.rtl ? 0 : 'auto', 'right' : $.wikiEditor.modules.toc.cfg.rtl ? 'auto' : 0, 'top' : pT } ) .fadeOut( 'fast', function() { $( this ).hide() .css( { 'marginTop': '0', 'width': '1px' } ); context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).fadeIn( 'fast' ); // Let the UI know things have moved around context.fn.trigger( 'tocCollapse' ); context.fn.trigger( 'resize' ); } ); $.cookie( 'wikiEditor-' + context.instance + '-toc-width', 0 ); return false; }, /** * Expand the contents module * * @param {Object} event Event object with context as data */ expand: function () { var $this = $( this ), context = $this.data( 'context' ), openWidth = parseFloat( context.modules.toc.$toc.data( 'openWidth' ) ), availableSpace = context.$wikitext.width() - parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ); if ( availableSpace < $.wikiEditor.modules.toc.cfg.textMinmumWidth ) { return false; } context.modules.toc.$toc.data( 'collapsed', false ); // check if we've got enough room to open to our stored width if ( availableSpace < openWidth ) { openWidth = availableSpace; } context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).hide(); var leftParam = {}, leftChildParam = {}; leftParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = parseFloat( openWidth ) * -1; leftChildParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = openWidth; context.$ui.find( '.wikiEditor-ui-left' ) .animate( leftParam, 'fast' ) .children() .animate( leftChildParam, 'fast' ); context.$ui.find( '.wikiEditor-ui-right' ) .show() .css( 'marginTop', '1px' ) .animate( { 'width' : openWidth }, 'fast', function() { context.$content.trigger( 'mouseup' ); $( this ).css( { 'marginTop' : '0', 'position' : 'relative', 'right' : 'auto', 'left' : 'auto', 'top': 'auto' } ); context.fn.trigger( 'tocExpand' ); context.fn.trigger( 'resize' ); } ); $.cookie( 'wikiEditor-' + context.instance + '-toc-width', context.modules.toc.$toc.data( 'openWidth' ) ); return false; }, /** * Builds table of contents * * @param {Object} context */ build: function( context ) { /** * Builds a structured outline from flat outline * * @param {Object} outline Array of objects with level fields */ function buildStructure( outline, offset, level ) { if ( offset === undefined ) { offset = 0; } if ( level === undefined ) { level = 1; } var sections = []; for ( var i = offset; i < outline.length; i++ ) { if ( outline[i].nLevel === level ) { var sub = buildStructure( outline, i + 1, level + 1 ); if ( sub.length ) { outline[i].sections = sub; } sections[sections.length] = outline[i]; } else if ( outline[i].nLevel < level ) { break; } } return sections; } /** * Builds unordered list HTML object from structured outline * * @param {Object} structure Structured outline */ function buildList( structure ) { var list = $( '