Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.prefs.js |
— | — | @@ -1,5 +1,9 @@ |
2 | 2 | /* |
3 | 3 | * Legacy emulation for the now depricated skins/common/prefs.js |
| 4 | + * |
| 5 | + * Generate toc from prefs form, fold sections |
| 6 | + * |
| 7 | + * FIXME: Needs testing on IE/Mac and Safari |
4 | 8 | */ |
5 | 9 | |
6 | 10 | ( function( $, mw ) { |
— | — | @@ -7,13 +11,223 @@ |
8 | 12 | /* Extension */ |
9 | 13 | |
10 | 14 | $.extend( mw.legacy, { |
11 | | - // |
| 15 | + |
| 16 | + /* Functions */ |
| 17 | + |
| 18 | + 'tabbedprefs': function() { |
| 19 | + var prefform = document.getElementById( 'preferences' ); |
| 20 | + if ( !prefform || !document.createElement ) { |
| 21 | + return; |
| 22 | + } |
| 23 | + if ( prefform.nodeName.toLowerCase() == 'a' ) { |
| 24 | + return; // Occasional IE problem |
| 25 | + } |
| 26 | + prefform.className = prefform.className + 'jsprefs'; |
| 27 | + var sections = []; |
| 28 | + var children = prefform.childNodes; |
| 29 | + var seci = 0; |
| 30 | + for ( var i = 0; i < children.length; i++ ) { |
| 31 | + if ( children[i].nodeName.toLowerCase() == 'fieldset' ) { |
| 32 | + children[i].id = 'prefsection-' + seci; |
| 33 | + children[i].className = 'prefsection'; |
| 34 | + if ( is_opera ) { |
| 35 | + children[i].className = 'prefsection operaprefsection'; |
| 36 | + } |
| 37 | + var legends = children[i].getElementsByTagName('legend'); |
| 38 | + sections[seci] = {}; |
| 39 | + if ( legends[0] ) { |
| 40 | + legends[0].className = 'mainLegend'; |
| 41 | + } |
| 42 | + if ( legends[0] && legends[0].firstChild.nodeValue ) { |
| 43 | + sections[seci].text = legends[0].firstChild.nodeValue; |
| 44 | + } else { |
| 45 | + sections[seci].text = '# ' + seci; |
| 46 | + } |
| 47 | + sections[seci].secid = children[i].id; |
| 48 | + seci++; |
| 49 | + if ( sections.length != 1 ) { |
| 50 | + children[i].style.display = 'none'; |
| 51 | + } else { |
| 52 | + var selectedid = children[i].id; |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + var toc = document.createElement( 'ul' ); |
| 57 | + toc.id = 'preftoc'; |
| 58 | + toc.selectedid = selectedid; |
| 59 | + for ( i = 0; i < sections.length; i++ ) { |
| 60 | + var li = document.createElement( 'li' ); |
| 61 | + if ( i === 0 ) { |
| 62 | + li.className = 'selected'; |
| 63 | + } |
| 64 | + var a = document.createElement( 'a' ); |
| 65 | + a.href = '#' + sections[i].secid; |
| 66 | + a.onmousedown = a.onclick = uncoversection; |
| 67 | + a.appendChild( document.createTextNode( sections[i].text ) ); |
| 68 | + a.secid = sections[i].secid; |
| 69 | + li.appendChild( a ); |
| 70 | + toc.appendChild( li ); |
| 71 | + } |
| 72 | + prefform.parentNode.insertBefore( toc, prefform.parentNode.childNodes[0] ); |
| 73 | + document.getElementById( 'prefsubmit' ).id = 'prefcontrol'; |
| 74 | + }, |
| 75 | + 'uncoversection': function() { |
| 76 | + var oldsecid = this.parentNode.parentNode.selectedid; |
| 77 | + var newsec = document.getElementById( this.secid ); |
| 78 | + if ( oldsecid != this.secid ) { |
| 79 | + var ul = document.getElementById( 'preftoc' ); |
| 80 | + document.getElementById( oldsecid ).style.display = 'none'; |
| 81 | + newsec.style.display = 'block'; |
| 82 | + ul.selectedid = this.secid; |
| 83 | + var lis = ul.getElementsByTagName( 'li' ); |
| 84 | + for ( var i = 0; i< lis.length; i++ ) { |
| 85 | + lis[i].className = ''; |
| 86 | + } |
| 87 | + this.parentNode.className = 'selected'; |
| 88 | + } |
| 89 | + return false; |
| 90 | + }, |
| 91 | + /** |
| 92 | + * Timezone stuff tz in format [+-]HHMM |
| 93 | + */ |
| 94 | + 'checkTimezone': function( tz, msg ) { |
| 95 | + var localclock = new Date(); |
| 96 | + // returns negative offset from GMT in minutes |
| 97 | + var tzRaw = localclock.getTimezoneOffset(); |
| 98 | + var tzHour = Math.floor( Math.abs( tzRaw ) / 60 ); |
| 99 | + var tzMin = Math.abs( tzRaw ) % 60; |
| 100 | + var tzString = ( ( tzRaw >= 0 ) ? '-' : '+' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin; |
| 101 | + if ( tz != tzString ) { |
| 102 | + var junk = msg.split('$1'); |
| 103 | + document.write( junk[0] + 'UTC' + tzString + junk[1] ); |
| 104 | + } |
| 105 | + }, |
| 106 | + 'timezoneSetup': function() { |
| 107 | + var tzSelect = document.getElementById( 'mw-input-timecorrection' ); |
| 108 | + var tzTextbox = document.getElementById( 'mw-input-timecorrection-other' ); |
| 109 | + |
| 110 | + if ( tzSelect && tzTextbox ) { |
| 111 | + addHandler( tzSelect, 'change', function( e ) { updateTimezoneSelection( false ); } ); |
| 112 | + addHandler( tzTextbox, 'blur', function( e ) { updateTimezoneSelection( true ); } ); |
| 113 | + } |
| 114 | + |
| 115 | + updateTimezoneSelection( false ); |
| 116 | + }, |
| 117 | + /** |
| 118 | + * Timezone stuff tz in format [-]HH:MM - won't yet work with non-even tzs |
| 119 | + */ |
| 120 | + 'fetchTimezone': function() { |
| 121 | + // FIXME: work around Safari bug |
| 122 | + var localclock = new Date(); |
| 123 | + // returns negative offset from GMT in minutes |
| 124 | + var tzRaw = localclock.getTimezoneOffset(); |
| 125 | + var tzHour = Math.floor( Math.abs( tzRaw ) / 60 ); |
| 126 | + var tzMin = Math.abs( tzRaw ) % 60; |
| 127 | + var tzString = ( ( tzRaw >= 0 ) ? '-' : '' ) + ( ( tzHour < 10 ) ? '0' : '' ) + tzHour + |
| 128 | + ':' + ( ( tzMin < 10 ) ? '0' : '' ) + tzMin; |
| 129 | + return tzString; |
| 130 | + }, |
| 131 | + 'guessTimezone': function() { |
| 132 | + var textbox = document.getElementById( 'mw-input-timecorrection-other' ); |
| 133 | + var selector = document.getElementById( 'mw-input-timecorrection' ); |
| 134 | + |
| 135 | + selector.value = 'other'; |
| 136 | + textbox.value = fetchTimezone(); |
| 137 | + textbox.disabled = false; // The changed handler doesn't trip, obviously. |
| 138 | + updateTimezoneSelection( true ); |
| 139 | + }, |
| 140 | + 'updateTimezoneSelection': function( force_offset ) { |
| 141 | + var selector = document.getElementById( 'mw-input-timecorrection' ); |
| 142 | + |
| 143 | + if ( selector.value == 'guess' ) { |
| 144 | + return guessTimezone(); |
| 145 | + } |
| 146 | + |
| 147 | + var textbox = document.getElementById( 'mw-input-timecorrection-other' ); |
| 148 | + var localtimeHolder = document.getElementById( 'wpLocalTime' ); |
| 149 | + var servertime = document.getElementsByName( 'wpServerTime' )[0].value; |
| 150 | + var minDiff = 0; |
| 151 | + |
| 152 | + // Compatibility code. |
| 153 | + if ( !selector.value ) { |
| 154 | + selector.value = selector.options[selector.selectedIndex].value; |
| 155 | + } |
| 156 | + |
| 157 | + // Handle force_offset |
| 158 | + if ( force_offset ) { |
| 159 | + selector.value = 'other'; |
| 160 | + } |
| 161 | + |
| 162 | + // Get min_diff |
| 163 | + if ( selector.value == 'other' ) { |
| 164 | + // Grab data from the textbox, parse it. |
| 165 | + var diffArr = textbox.value.split(':'); |
| 166 | + if ( diffArr.length == 1 ) { |
| 167 | + // Specification is of the form [-]XX |
| 168 | + minDiff = parseInt( diffArr[0], 10 ) * 60; |
| 169 | + } else { |
| 170 | + // Specification is of the form [-]XX:XX |
| 171 | + minDiff = Math.abs( parseInt( diffArr[0], 10 ) ) * 60 + parseInt( diffArr[1], 10 ); |
| 172 | + if ( parseInt( diffArr[0], 10 ) < 0 ) { |
| 173 | + minDiff = -minDiff; |
| 174 | + } |
| 175 | + } |
| 176 | + } else { |
| 177 | + // Grab data from the selector value |
| 178 | + var diffArr = selector.value.split('|'); |
| 179 | + minDiff = parseInt( diffArr[1], 10 ); |
| 180 | + } |
| 181 | + |
| 182 | + // Gracefully handle non-numbers. |
| 183 | + if ( isNaN( minDiff ) ) { |
| 184 | + minDiff = 0; |
| 185 | + } |
| 186 | + |
| 187 | + // Determine local time from server time and minutes difference, for display. |
| 188 | + var localTime = parseInt( servertime, 10 ) + minDiff; |
| 189 | + |
| 190 | + // Bring time within the [0,1440) range. |
| 191 | + while ( localTime < 0 ) { |
| 192 | + localTime += 1440; |
| 193 | + } |
| 194 | + while ( localTime >= 1440 ) { |
| 195 | + localTime -= 1440; |
| 196 | + } |
| 197 | + |
| 198 | + // Split to hour and minute |
| 199 | + var hour = String( Math.floor( localTime / 60 ) ); |
| 200 | + if ( hour.length < 2 ) { |
| 201 | + hour = '0' + hour; |
| 202 | + } |
| 203 | + var min = String(localTime%60); |
| 204 | + if ( min.length < 2 ) { |
| 205 | + min = '0' + min; |
| 206 | + } |
| 207 | + changeText( localtimeHolder, hour + ':' + min ); |
| 208 | + |
| 209 | + // If the user selected from the drop-down, fill the offset field. |
| 210 | + if ( selector.value != 'other' ) { |
| 211 | + hour = String( Math.abs( Math.floor( minDiff / 60 ) ) ); |
| 212 | + if ( hour.length < 2 ) { |
| 213 | + hour = '0' + hour; |
| 214 | + } |
| 215 | + if ( minDiff < 0 ) { |
| 216 | + hour = '-' + hour; |
| 217 | + } |
| 218 | + min = String(minDiff%60); |
| 219 | + if ( min.length < 2 ) { |
| 220 | + min = '0' + min; |
| 221 | + } |
| 222 | + textbox.value = hour + ':' + min; |
| 223 | + } |
| 224 | + } |
12 | 225 | } ); |
13 | 226 | |
14 | 227 | /* Initialization */ |
15 | 228 | |
16 | 229 | $( document ).ready( function() { |
17 | | - // |
| 230 | + mw.legacy.timezoneSetup(); |
| 231 | + mw.legacy.tabbedprefs(); |
18 | 232 | } ); |
19 | 233 | |
20 | 234 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.metadata.js |
— | — | @@ -7,13 +7,47 @@ |
8 | 8 | /* Extension */ |
9 | 9 | |
10 | 10 | $.extend( mw.legacy, { |
11 | | - // |
| 11 | + /** |
| 12 | + * Exif metadata display for MediaWiki file uploads |
| 13 | + * |
| 14 | + * Add an expand/collapse link and collapse by default if set to (with JS disabled, user will see all items) |
| 15 | + * |
| 16 | + * Example: |
| 17 | + * attachMetadataToggle( 'mw_metadata', 'More...', 'Fewer...' ); |
| 18 | + */ |
| 19 | + 'attachMetadataToggle': function( tableId, showText, hideText ) { |
| 20 | + if ( document.createTextNode ) { |
| 21 | + var box = document.getElementById( tableId ); |
| 22 | + if ( !box ) { |
| 23 | + return false; |
| 24 | + } |
| 25 | + var tbody = box.getElementsByTagName('tbody')[0]; |
| 26 | + var row = document.createElement( 'tr' ); |
| 27 | + var col = document.createElement( 'td' ); |
| 28 | + col.colSpan = 2; |
| 29 | + var link = document.createElement( 'a' ); |
| 30 | + link.href = '#'; |
| 31 | + link.onclick = function() { |
| 32 | + if ( box.className == 'mw_metadata collapsed' ) { |
| 33 | + changeText( link, hideText ); |
| 34 | + box.className = 'mw_metadata expanded'; |
| 35 | + } else { |
| 36 | + changeText( link, showText ); |
| 37 | + box.className = 'mw_metadata collapsed'; |
| 38 | + } |
| 39 | + return false; |
| 40 | + }; |
| 41 | + var text = document.createTextNode( hideText ); |
| 42 | + link.appendChild( text ); |
| 43 | + col.appendChild( link ); |
| 44 | + row.appendChild( col ); |
| 45 | + tbody.appendChild( row ); |
| 46 | + // And collapse! |
| 47 | + link.onclick(); |
| 48 | + return true; |
| 49 | + } |
| 50 | + return false; |
| 51 | + } |
12 | 52 | } ); |
13 | 53 | |
14 | | -/* Initialization */ |
15 | | - |
16 | | -$( document ).ready( function() { |
17 | | - // |
18 | | -} ); |
19 | | - |
20 | 54 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.protect.js |
— | — | @@ -7,13 +7,340 @@ |
8 | 8 | /* Extension */ |
9 | 9 | |
10 | 10 | $.extend( mw.legacy, { |
11 | | - // |
12 | | -} ); |
| 11 | + |
| 12 | + /* Global Variables */ |
| 13 | + |
| 14 | + 'ProtectionForm': { |
| 15 | + |
| 16 | + /* Global Variables */ |
| 17 | + |
| 18 | + 'existingMatch': false, |
| 19 | + |
| 20 | + /* Functions */ |
| 21 | + |
| 22 | + /** |
| 23 | + * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox) |
| 24 | + * on the protection form |
| 25 | + * |
| 26 | + * @param Object opts : parameters with members: |
| 27 | + * tableId Identifier of the table containing UI bits |
| 28 | + * labelText Text to use for the checkbox label |
| 29 | + * numTypes The number of protection types |
| 30 | + * existingMatch True if all the existing expiry times match |
| 31 | + */ |
| 32 | + 'init': function( opts ) { |
| 33 | + if( !( document.createTextNode && document.getElementById && document.getElementsByTagName ) ) |
| 34 | + return false; |
| 35 | + var box = document.getElementById( opts.tableId ); |
| 36 | + if( !box ) |
| 37 | + return false; |
| 38 | + var boxbody = box.getElementsByTagName('tbody')[0] |
| 39 | + var row = document.createElement( 'tr' ); |
| 40 | + boxbody.insertBefore( row, boxbody.firstChild.nextSibling ); |
| 41 | + this.existingMatch = opts.existingMatch; |
| 42 | + var cell = document.createElement( 'td' ); |
| 43 | + row.appendChild( cell ); |
| 44 | + // If there is only one protection type, there is nothing to chain |
| 45 | + if( opts.numTypes > 1 ) { |
| 46 | + var check = document.createElement( 'input' ); |
| 47 | + check.id = 'mwProtectUnchained'; |
| 48 | + check.type = 'checkbox'; |
| 49 | + cell.appendChild( check ); |
| 50 | + addClickHandler( check, function() { ProtectionForm.onChainClick(); } ); |
13 | 51 | |
14 | | -/* Initialization */ |
| 52 | + cell.appendChild( document.createTextNode( ' ' ) ); |
| 53 | + var label = document.createElement( 'label' ); |
| 54 | + label.htmlFor = 'mwProtectUnchained'; |
| 55 | + label.appendChild( document.createTextNode( opts.labelText ) ); |
| 56 | + cell.appendChild( label ); |
15 | 57 | |
16 | | -$( document ).ready( function() { |
17 | | - // |
| 58 | + check.checked = !this.areAllTypesMatching(); |
| 59 | + this.enableUnchainedInputs( check.checked ); |
| 60 | + } |
| 61 | + this.updateCascadeCheckbox(); |
| 62 | + return true; |
| 63 | + }, |
| 64 | + |
| 65 | + /** |
| 66 | + * Sets the disabled attribute on the cascade checkbox depending on the current selected levels |
| 67 | + */ |
| 68 | + 'updateCascadeCheckbox': function() { |
| 69 | + // For non-existent titles, there is no cascade option |
| 70 | + if( !document.getElementById( 'mwProtect-cascade' ) ) { |
| 71 | + return; |
| 72 | + } |
| 73 | + var lists = this.getLevelSelectors(); |
| 74 | + for( var i = 0; i < lists.length; i++ ) { |
| 75 | + if( lists[i].selectedIndex > -1 ) { |
| 76 | + var items = lists[i].getElementsByTagName( 'option' ); |
| 77 | + var selected = items[ lists[i].selectedIndex ].value; |
| 78 | + if( !this.isCascadeableLevel(selected) ) { |
| 79 | + document.getElementById( 'mwProtect-cascade' ).checked = false; |
| 80 | + document.getElementById( 'mwProtect-cascade' ).disabled = true; |
| 81 | + return; |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + document.getElementById( 'mwProtect-cascade' ).disabled = false; |
| 86 | + }, |
| 87 | + /** |
| 88 | + * Is this protection level cascadeable? |
| 89 | + * @param String level |
| 90 | + * |
| 91 | + * @return boolean |
| 92 | + * |
| 93 | + */ |
| 94 | + 'isCascadeableLevel': function( level ) { |
| 95 | + for (var k = 0; k < wgCascadeableLevels.length; k++) { |
| 96 | + if ( wgCascadeableLevels[k] == level ) { |
| 97 | + return true; |
| 98 | + } |
| 99 | + } |
| 100 | + return false; |
| 101 | + }, |
| 102 | + /** |
| 103 | + * When protection levels are locked together, update the rest |
| 104 | + * when one action's level changes |
| 105 | + * |
| 106 | + * @param Element source Level selector that changed |
| 107 | + */ |
| 108 | + 'updateLevels': function(source) { |
| 109 | + if( !this.isUnchained() ) |
| 110 | + this.setAllSelectors( source.selectedIndex ); |
| 111 | + this.updateCascadeCheckbox(); |
| 112 | + }, |
| 113 | + /** |
| 114 | + * When protection levels are locked together, update the |
| 115 | + * expiries when one changes |
| 116 | + * |
| 117 | + * @param Element source expiry input that changed |
| 118 | + */ |
| 119 | + 'updateExpiry': function(source) { |
| 120 | + if( !this.isUnchained() ) { |
| 121 | + var expiry = source.value; |
| 122 | + this.forEachExpiryInput(function(element) { |
| 123 | + element.value = expiry; |
| 124 | + }); |
| 125 | + } |
| 126 | + var listId = source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ); |
| 127 | + var list = document.getElementById( listId ); |
| 128 | + if (list && list.value != 'othertime' ) { |
| 129 | + if ( this.isUnchained() ) { |
| 130 | + list.value = 'othertime'; |
| 131 | + } else { |
| 132 | + this.forEachExpirySelector(function(element) { |
| 133 | + element.value = 'othertime'; |
| 134 | + }); |
| 135 | + } |
| 136 | + } |
| 137 | + }, |
| 138 | + /** |
| 139 | + * When protection levels are locked together, update the |
| 140 | + * expiry lists when one changes and clear the custom inputs |
| 141 | + * |
| 142 | + * @param Element source expiry selector that changed |
| 143 | + */ |
| 144 | + 'updateExpiryList': function(source) { |
| 145 | + if( !this.isUnchained() ) { |
| 146 | + var expiry = source.value; |
| 147 | + this.forEachExpirySelector(function(element) { |
| 148 | + element.value = expiry; |
| 149 | + }); |
| 150 | + this.forEachExpiryInput(function(element) { |
| 151 | + element.value = ''; |
| 152 | + }); |
| 153 | + } |
| 154 | + }, |
| 155 | + /** |
| 156 | + * Update chain status and enable/disable various bits of the UI |
| 157 | + * when the user changes the "unlock move permissions" checkbox |
| 158 | + */ |
| 159 | + 'onChainClick': function() { |
| 160 | + if( this.isUnchained() ) { |
| 161 | + this.enableUnchainedInputs( true ); |
| 162 | + } else { |
| 163 | + this.setAllSelectors( this.getMaxLevel() ); |
| 164 | + this.enableUnchainedInputs( false ); |
| 165 | + } |
| 166 | + this.updateCascadeCheckbox(); |
| 167 | + }, |
| 168 | + /** |
| 169 | + * Returns true if the named attribute in all objects in the given array are matching |
| 170 | + */ |
| 171 | + 'matchAttribute' : function( objects, attrName ) { |
| 172 | + var value = null; |
| 173 | + |
| 174 | + // Check levels |
| 175 | + for ( var i = 0; i < objects.length; i++ ) { |
| 176 | + var element = objects[i]; |
| 177 | + if ( value == null ) { |
| 178 | + value = element[attrName]; |
| 179 | + } else { |
| 180 | + if ( value != element[attrName] ) { |
| 181 | + return false; |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + return true; |
| 186 | + }, |
| 187 | + /** |
| 188 | + * Are all actions protected at the same level, with the same expiry time? |
| 189 | + * |
| 190 | + * @return boolean |
| 191 | + */ |
| 192 | + 'areAllTypesMatching': function() { |
| 193 | + return this.existingMatch |
| 194 | + && this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' ) |
| 195 | + && this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' ) |
| 196 | + && this.matchAttribute( this.getExpiryInputs(), 'value' ); |
| 197 | + }, |
| 198 | + /** |
| 199 | + * Is protection chaining off? |
| 200 | + * |
| 201 | + * @return bool |
| 202 | + */ |
| 203 | + 'isUnchained': function() { |
| 204 | + var element = document.getElementById( 'mwProtectUnchained' ); |
| 205 | + return element |
| 206 | + ? element.checked |
| 207 | + : true; // No control, so we need to let the user set both levels |
| 208 | + }, |
| 209 | + /** |
| 210 | + * Find the highest protection level in any selector |
| 211 | + */ |
| 212 | + 'getMaxLevel': function() { |
| 213 | + var maxIndex = -1; |
| 214 | + this.forEachLevelSelector(function(element) { |
| 215 | + if (element.selectedIndex > maxIndex) { |
| 216 | + maxIndex = element.selectedIndex; |
| 217 | + } |
| 218 | + }); |
| 219 | + return maxIndex; |
| 220 | + }, |
| 221 | + /** |
| 222 | + * Protect all actions at the specified level |
| 223 | + * |
| 224 | + * @param int index Protection level |
| 225 | + */ |
| 226 | + 'setAllSelectors': function(index) { |
| 227 | + this.forEachLevelSelector(function(element) { |
| 228 | + if (element.selectedIndex != index) { |
| 229 | + element.selectedIndex = index; |
| 230 | + } |
| 231 | + }); |
| 232 | + }, |
| 233 | + /** |
| 234 | + * Apply a callback to each protection selector |
| 235 | + * |
| 236 | + * @param callable func Callback function |
| 237 | + */ |
| 238 | + 'forEachLevelSelector': function(func) { |
| 239 | + var selectors = this.getLevelSelectors(); |
| 240 | + for (var i = 0; i < selectors.length; i++) { |
| 241 | + func(selectors[i]); |
| 242 | + } |
| 243 | + }, |
| 244 | + /** |
| 245 | + * Get a list of all protection selectors on the page |
| 246 | + * |
| 247 | + * @return Array |
| 248 | + */ |
| 249 | + 'getLevelSelectors': function() { |
| 250 | + var all = document.getElementsByTagName("select"); |
| 251 | + var ours = new Array(); |
| 252 | + for (var i = 0; i < all.length; i++) { |
| 253 | + var element = all[i]; |
| 254 | + if (element.id.match(/^mwProtect-level-/)) { |
| 255 | + ours[ours.length] = element; |
| 256 | + } |
| 257 | + } |
| 258 | + return ours; |
| 259 | + }, |
| 260 | + /** |
| 261 | + * Apply a callback to each expiry input |
| 262 | + * |
| 263 | + * @param callable func Callback function |
| 264 | + */ |
| 265 | + 'forEachExpiryInput': function(func) { |
| 266 | + var inputs = this.getExpiryInputs(); |
| 267 | + for (var i = 0; i < inputs.length; i++) { |
| 268 | + func(inputs[i]); |
| 269 | + } |
| 270 | + }, |
| 271 | + /** |
| 272 | + * Get a list of all expiry inputs on the page |
| 273 | + * |
| 274 | + * @return Array |
| 275 | + */ |
| 276 | + 'getExpiryInputs': function() { |
| 277 | + var all = document.getElementsByTagName("input"); |
| 278 | + var ours = new Array(); |
| 279 | + for (var i = 0; i < all.length; i++) { |
| 280 | + var element = all[i]; |
| 281 | + if (element.name.match(/^mwProtect-expiry-/)) { |
| 282 | + ours[ours.length] = element; |
| 283 | + } |
| 284 | + } |
| 285 | + return ours; |
| 286 | + }, |
| 287 | + /** |
| 288 | + * Apply a callback to each expiry selector list |
| 289 | + * @param callable func Callback function |
| 290 | + */ |
| 291 | + 'forEachExpirySelector': function(func) { |
| 292 | + var inputs = this.getExpirySelectors(); |
| 293 | + for (var i = 0; i < inputs.length; i++) { |
| 294 | + func(inputs[i]); |
| 295 | + } |
| 296 | + }, |
| 297 | + /** |
| 298 | + * Get a list of all expiry selector lists on the page |
| 299 | + * |
| 300 | + * @return Array |
| 301 | + */ |
| 302 | + 'getExpirySelectors': function() { |
| 303 | + var all = document.getElementsByTagName("select"); |
| 304 | + var ours = new Array(); |
| 305 | + for (var i = 0; i < all.length; i++) { |
| 306 | + var element = all[i]; |
| 307 | + if (element.id.match(/^mwProtectExpirySelection-/)) { |
| 308 | + ours[ours.length] = element; |
| 309 | + } |
| 310 | + } |
| 311 | + return ours; |
| 312 | + }, |
| 313 | + /** |
| 314 | + * Enable/disable protection selectors and expiry inputs |
| 315 | + * |
| 316 | + * @param boolean val Enable? |
| 317 | + */ |
| 318 | + 'enableUnchainedInputs': function(val) { |
| 319 | + var first = true; |
| 320 | + this.forEachLevelSelector(function(element) { |
| 321 | + if (first) { |
| 322 | + first = false; |
| 323 | + } else { |
| 324 | + element.disabled = !val; |
| 325 | + } |
| 326 | + }); |
| 327 | + first = true; |
| 328 | + this.forEachExpiryInput(function(element) { |
| 329 | + if (first) { |
| 330 | + first = false; |
| 331 | + } else { |
| 332 | + element.disabled = !val; |
| 333 | + } |
| 334 | + }); |
| 335 | + first = true; |
| 336 | + this.forEachExpirySelector(function(element) { |
| 337 | + if (first) { |
| 338 | + first = false; |
| 339 | + } else { |
| 340 | + element.disabled = !val; |
| 341 | + } |
| 342 | + }); |
| 343 | + } |
| 344 | + } |
18 | 345 | } ); |
19 | 346 | |
20 | 347 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.preview.js |
— | — | @@ -1,5 +1,7 @@ |
2 | 2 | /* |
3 | 3 | * Legacy emulation for the now depricated skins/common/preview.js |
| 4 | + * |
| 5 | + * Inline ("Live") preview |
4 | 6 | */ |
5 | 7 | |
6 | 8 | ( function( $, mw ) { |
— | — | @@ -7,13 +9,113 @@ |
8 | 10 | /* Extension */ |
9 | 11 | |
10 | 12 | $.extend( mw.legacy, { |
11 | | - // |
| 13 | + |
| 14 | + /* Functions */ |
| 15 | + |
| 16 | + 'doLivePreview': function( e ) { |
| 17 | + e.preventDefault(); |
| 18 | + $j( mw ).trigger( 'LivePreviewPrepare' ); |
| 19 | + var postData = $j('#editform').formToArray(); |
| 20 | + postData.push( { 'name' : 'wpPreview', 'value' : '1' } ); |
| 21 | + // Hide active diff, used templates, old preview if shown |
| 22 | + var copyElements = ['#wikiPreview', '.templatesUsed', '.hiddencats', '#catlinks']; |
| 23 | + var copySelector = copyElements.join(','); |
| 24 | + $j.each( copyElements, function(k,v) { $j(v).fadeOut('fast'); } ); |
| 25 | + // Display a loading graphic |
| 26 | + var loadSpinner = $j('<div class="mw-ajax-loader"/>'); |
| 27 | + $j('#wikiPreview').before( loadSpinner ); |
| 28 | + var page = $j('<div/>'); |
| 29 | + var target = $j('#editform').attr('action'); |
| 30 | + if ( !target ) { |
| 31 | + target = window.location.href; |
| 32 | + } |
| 33 | + page.load( target + ' ' + copySelector, postData, function() { |
| 34 | + for ( var i=0; i<copyElements.length; ++i) { |
| 35 | + // For all the specified elements, find the elements in the loaded page |
| 36 | + // and the real page, empty the element in the real page, and fill it |
| 37 | + // with the content of the loaded page |
| 38 | + var copyContent = page.find( copyElements[i] ).contents(); |
| 39 | + $j(copyElements[i]).empty().append( copyContent ); |
| 40 | + var newClasses = page.find( copyElements[i] ).attr('class'); |
| 41 | + $j(copyElements[i]).attr( 'class', newClasses ); |
| 42 | + } |
| 43 | + $j.each( copyElements, function(k,v) { |
| 44 | + // Don't belligerently show elements that are supposed to be hidden |
| 45 | + $j(v).fadeIn( 'fast', function() { $j(this).css('display', ''); } ); |
| 46 | + } ); |
| 47 | + loadSpinner.remove(); |
| 48 | + $j( mw ).trigger( 'LivePreviewDone', [copyElements] ); |
| 49 | + } ); |
| 50 | + } |
12 | 51 | } ); |
13 | 52 | |
14 | 53 | /* Initialization */ |
15 | 54 | |
16 | 55 | $( document ).ready( function() { |
17 | | - // |
| 56 | + // Shamelessly stolen from the jQuery form plugin, which is licensed under the GPL. |
| 57 | + // http://jquery.malsup.com/form/#download |
| 58 | + $j.fn.formToArray = function() { |
| 59 | + var a = []; |
| 60 | + if (this.length == 0) return a; |
| 61 | + var form = this[0]; |
| 62 | + var els = form.elements; |
| 63 | + if (!els) return a; |
| 64 | + for(var i=0, max=els.length; i < max; i++) { |
| 65 | + var el = els[i]; |
| 66 | + var n = el.name; |
| 67 | + if (!n) continue; |
| 68 | + var v = $j.fieldValue(el, true); |
| 69 | + if (v && v.constructor == Array) { |
| 70 | + for(var j=0, jmax=v.length; j < jmax; j++) |
| 71 | + a.push({name: n, value: v[j]}); |
| 72 | + } |
| 73 | + else if (v !== null && typeof v != 'undefined') |
| 74 | + a.push({name: n, value: v}); |
| 75 | + } |
| 76 | + if (form.clk) { |
| 77 | + // input type=='image' are not found in elements array! handle it here |
| 78 | + var $input = $(form.clk), input = $input[0], n = input.name; |
| 79 | + if (n && !input.disabled && input.type == 'image') { |
| 80 | + a.push({name: n, value: $input.val()}); |
| 81 | + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); |
| 82 | + } |
| 83 | + } |
| 84 | + return a; |
| 85 | + } |
| 86 | + /** |
| 87 | + * Returns the value of the field element. |
| 88 | + */ |
| 89 | + $j.fieldValue = function(el, successful) { |
| 90 | + var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); |
| 91 | + if (typeof successful == 'undefined') successful = true; |
| 92 | + if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || |
| 93 | + (t == 'checkbox' || t == 'radio') && !el.checked || |
| 94 | + (t == 'submit' || t == 'image') && el.form && el.form.clk != el || |
| 95 | + tag == 'select' && el.selectedIndex == -1)) |
| 96 | + return null; |
| 97 | + if (tag == 'select') { |
| 98 | + var index = el.selectedIndex; |
| 99 | + if (index < 0) return null; |
| 100 | + var a = [], ops = el.options; |
| 101 | + var one = (t == 'select-one'); |
| 102 | + var max = (one ? index+1 : ops.length); |
| 103 | + for(var i=(one ? index : 0); i < max; i++) { |
| 104 | + var op = ops[i]; |
| 105 | + if (op.selected) { |
| 106 | + var v = op.value; |
| 107 | + if (!v) // extra pain for IE... |
| 108 | + v = (op.attributes && op.attributes['value'] && |
| 109 | + !(op.attributes['value'].specified)) |
| 110 | + ? op.text : op.value; |
| 111 | + if (one) return v; |
| 112 | + a.push(v); |
| 113 | + } |
| 114 | + } |
| 115 | + return a; |
| 116 | + } |
| 117 | + return el.value; |
| 118 | + }; |
| 119 | + $j('#wpPreview').click( doLivePreview ); |
18 | 120 | } ); |
19 | 121 | |
20 | 122 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.upload.js |
— | — | @@ -7,13 +7,326 @@ |
8 | 8 | /* Extension */ |
9 | 9 | |
10 | 10 | $.extend( mw.legacy, { |
11 | | - // |
| 11 | + |
| 12 | + /* Global Variables */ |
| 13 | + |
| 14 | + 'wgUploadWarningObj': { |
| 15 | + |
| 16 | + /* Global Variables */ |
| 17 | + |
| 18 | + 'responseCache' : { '' : ' ' }, |
| 19 | + 'nameToCheck' : '', |
| 20 | + 'typing': false, |
| 21 | + 'delay': 500, // ms |
| 22 | + 'timeoutID': false, |
| 23 | + |
| 24 | + /* Functions */ |
| 25 | + |
| 26 | + 'keypress': function () { |
| 27 | + if ( !wgAjaxUploadDestCheck || !sajax_init_object() ) return; |
| 28 | + |
| 29 | + // Find file to upload |
| 30 | + var destFile = document.getElementById('wpDestFile'); |
| 31 | + var warningElt = document.getElementById( 'wpDestFile-warning' ); |
| 32 | + if ( !destFile || !warningElt ) return ; |
| 33 | + this.nameToCheck = destFile.value ; |
| 34 | + // Clear timer |
| 35 | + if ( this.timeoutID ) { |
| 36 | + window.clearTimeout( this.timeoutID ); |
| 37 | + } |
| 38 | + // Check response cache |
| 39 | + for (cached in this.responseCache) { |
| 40 | + if (this.nameToCheck == cached) { |
| 41 | + this.setWarning(this.responseCache[this.nameToCheck]); |
| 42 | + return; |
| 43 | + } |
| 44 | + } |
| 45 | + this.timeoutID = window.setTimeout( 'wgUploadWarningObj.timeout()', this.delay ); |
| 46 | + }, |
| 47 | + 'checkNow': function (fname) { |
| 48 | + if ( !wgAjaxUploadDestCheck || !sajax_init_object() ) return; |
| 49 | + if ( this.timeoutID ) { |
| 50 | + window.clearTimeout( this.timeoutID ); |
| 51 | + } |
| 52 | + this.nameToCheck = fname; |
| 53 | + this.timeout(); |
| 54 | + }, |
| 55 | + 'timeout' : function() { |
| 56 | + if ( !wgAjaxUploadDestCheck || !sajax_init_object() ) return; |
| 57 | + injectSpinner( document.getElementById( 'wpDestFile' ), 'destcheck' ); |
| 58 | + |
| 59 | + // Get variables into local scope so that they will be preserved for the |
| 60 | + // anonymous callback. fileName is copied so that multiple overlapping |
| 61 | + // ajax requests can be supported. |
| 62 | + var obj = this; |
| 63 | + var fileName = this.nameToCheck; |
| 64 | + sajax_do_call( 'SpecialUpload::ajaxGetExistsWarning', [this.nameToCheck], |
| 65 | + function (result) { |
| 66 | + obj.processResult(result, fileName) |
| 67 | + } |
| 68 | + ); |
| 69 | + }, |
| 70 | + 'processResult' : function (result, fileName) { |
| 71 | + removeSpinner( 'destcheck' ); |
| 72 | + this.setWarning(result.responseText); |
| 73 | + this.responseCache[fileName] = result.responseText; |
| 74 | + }, |
| 75 | + 'setWarning' : function (warning) { |
| 76 | + var warningElt = document.getElementById( 'wpDestFile-warning' ); |
| 77 | + var ackElt = document.getElementsByName( 'wpDestFileWarningAck' ); |
| 78 | + |
| 79 | + this.setInnerHTML(warningElt, warning); |
| 80 | + |
| 81 | + // Set a value in the form indicating that the warning is acknowledged and |
| 82 | + // doesn't need to be redisplayed post-upload |
| 83 | + if ( warning == '' || warning == ' ' ) { |
| 84 | + ackElt[0].value = ''; |
| 85 | + } else { |
| 86 | + ackElt[0].value = '1'; |
| 87 | + } |
| 88 | + }, |
| 89 | + 'setInnerHTML' : function (element, text) { |
| 90 | + // Check for no change to avoid flicker in IE 7 |
| 91 | + if (element.innerHTML != text) { |
| 92 | + element.innerHTML = text; |
| 93 | + } |
| 94 | + } |
| 95 | + }, |
| 96 | + var wgUploadLicenseObj = { |
| 97 | + |
| 98 | + /* Global Variables */ |
| 99 | + |
| 100 | + 'responseCache' : { '' : '' }, |
| 101 | + |
| 102 | + /* Functions */ |
| 103 | + |
| 104 | + 'fetchPreview': function( license ) { |
| 105 | + if( !wgAjaxLicensePreview ) return; |
| 106 | + for (cached in this.responseCache) { |
| 107 | + if (cached == license) { |
| 108 | + this.showPreview( this.responseCache[license] ); |
| 109 | + return; |
| 110 | + } |
| 111 | + } |
| 112 | + injectSpinner( document.getElementById( 'wpLicense' ), 'license' ); |
| 113 | + var title = document.getElementById('wpDestFile').value; |
| 114 | + if ( !title ) title = 'File:Sample.jpg'; |
| 115 | + var url = wgScriptPath + '/api' + wgScriptExtension |
| 116 | + + '?action=parse&text={{' + encodeURIComponent( license ) + '}}' |
| 117 | + + '&title=' + encodeURIComponent( title ) |
| 118 | + + '&prop=text&pst&format=json'; |
| 119 | + var req = sajax_init_object(); |
| 120 | + req.onreadystatechange = function() { |
| 121 | + if ( req.readyState == 4 && req.status == 200 ) |
| 122 | + wgUploadLicenseObj.processResult( eval( '(' + req.responseText + ')' ), license ); |
| 123 | + }; |
| 124 | + req.open( 'GET', url, true ); |
| 125 | + req.send( '' ); |
| 126 | + }, |
| 127 | + 'processResult' : function( result, license ) { |
| 128 | + removeSpinner( 'license' ); |
| 129 | + this.responseCache[license] = result['parse']['text']['*']; |
| 130 | + this.showPreview( this.responseCache[license] ); |
| 131 | + }, |
| 132 | + 'showPreview' : function( preview ) { |
| 133 | + var previewPanel = document.getElementById( 'mw-license-preview' ); |
| 134 | + if( previewPanel.innerHTML != preview ) |
| 135 | + previewPanel.innerHTML = preview; |
| 136 | + } |
| 137 | + }, |
| 138 | + |
| 139 | + /* Functions */ |
| 140 | + |
| 141 | + 'licenseSelectorCheck': function() { |
| 142 | + var selector = document.getElementById( "wpLicense" ); |
| 143 | + var selection = selector.options[selector.selectedIndex].value; |
| 144 | + if( selector.selectedIndex > 0 ) { |
| 145 | + if( selection == "" ) { |
| 146 | + // Option disabled, but browser is broken and doesn't respect this |
| 147 | + selector.selectedIndex = 0; |
| 148 | + } |
| 149 | + } |
| 150 | + // We might show a preview |
| 151 | + wgUploadLicenseObj.fetchPreview( selection ); |
| 152 | + }, |
| 153 | + 'wgUploadSetup': function() { |
| 154 | + // Disable URL box if the URL copy upload source type is not selected |
| 155 | + var e = document.getElementById( 'wpSourceTypeurl' ); |
| 156 | + if( e ) { |
| 157 | + if( !e.checked ) { |
| 158 | + var ein = document.getElementById( 'wpUploadFileURL' ); |
| 159 | + if(ein) |
| 160 | + ein.setAttribute( 'disabled', 'disabled' ); |
| 161 | + } |
| 162 | + } |
| 163 | + // For MSIE/Mac: non-breaking spaces cause the <option> not to render. |
| 164 | + // But for some reason, setting the text to itself works |
| 165 | + var selector = document.getElementById("wpLicense"); |
| 166 | + if (selector) { |
| 167 | + var ua = navigator.userAgent; |
| 168 | + var isMacIe = (ua.indexOf("MSIE") != -1) && (ua.indexOf("Mac") != -1); |
| 169 | + if (isMacIe) { |
| 170 | + for (var i = 0; i < selector.options.length; i++) { |
| 171 | + selector.options[i].text = selector.options[i].text; |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + // Toggle source type |
| 176 | + var sourceTypeCheckboxes = document.getElementsByName( 'wpSourceType' ); |
| 177 | + for ( var i = 0; i < sourceTypeCheckboxes.length; i++ ) { |
| 178 | + sourceTypeCheckboxes[i].onchange = toggleUploadInputs; |
| 179 | + } |
| 180 | + // AJAX wpDestFile warnings |
| 181 | + if ( wgAjaxUploadDestCheck ) { |
| 182 | + // Insert an event handler that fetches upload warnings when wpDestFile |
| 183 | + // has been changed |
| 184 | + document.getElementById( 'wpDestFile' ).onchange = function ( e ) { |
| 185 | + wgUploadWarningObj.checkNow(this.value); |
| 186 | + }; |
| 187 | + // Insert a row where the warnings will be displayed just below the |
| 188 | + // wpDestFile row |
| 189 | + var optionsTable = document.getElementById( 'mw-htmlform-description' ).tBodies[0]; |
| 190 | + var row = optionsTable.insertRow( 1 ); |
| 191 | + var td = document.createElement( 'td' ); |
| 192 | + td.id = 'wpDestFile-warning'; |
| 193 | + td.colSpan = 2; |
| 194 | + row.appendChild( td ); |
| 195 | + } |
| 196 | + if ( wgAjaxLicensePreview ) { |
| 197 | + // License selector check |
| 198 | + document.getElementById( 'wpLicense' ).onchange = licenseSelectorCheck; |
| 199 | + // License selector table row |
| 200 | + var wpLicense = document.getElementById( 'wpLicense' ); |
| 201 | + var wpLicenseRow = wpLicense.parentNode.parentNode; |
| 202 | + var wpLicenseTbody = wpLicenseRow.parentNode; |
| 203 | + var row = document.createElement( 'tr' ); |
| 204 | + var td = document.createElement( 'td' ); |
| 205 | + row.appendChild( td ); |
| 206 | + td = document.createElement( 'td' ); |
| 207 | + td.id = 'mw-license-preview'; |
| 208 | + row.appendChild( td ); |
| 209 | + wpLicenseTbody.insertBefore( row, wpLicenseRow.nextSibling ); |
| 210 | + } |
| 211 | + // fillDestFile setup |
| 212 | + for ( var i = 0; i < wgUploadSourceIds.length; i++ ) |
| 213 | + document.getElementById( wgUploadSourceIds[i] ).onchange = function (e) { |
| 214 | + fillDestFilename( this.id ); |
| 215 | + }; |
| 216 | + }, |
| 217 | + /** |
| 218 | + * Iterate over all upload source fields and disable all except the selected one. |
| 219 | + * |
| 220 | + * @param enabledId The id of the selected radio button |
| 221 | + * @return emptiness |
| 222 | + */ |
| 223 | + 'toggleUploadInputs': function() { |
| 224 | + // Iterate over all rows with UploadSourceField |
| 225 | + var rows; |
| 226 | + if ( document.getElementsByClassName ) { |
| 227 | + rows = document.getElementsByClassName( 'mw-htmlform-field-UploadSourceField' ); |
| 228 | + } else { |
| 229 | + // Older browsers don't support getElementsByClassName |
| 230 | + rows = new Array(); |
| 231 | + var allRows = document.getElementsByTagName( 'tr' ); |
| 232 | + for ( var i = 0; i < allRows.length; i++ ) { |
| 233 | + if ( allRows[i].className == 'mw-htmlform-field-UploadSourceField' ) |
| 234 | + rows.push( allRows[i] ); |
| 235 | + } |
| 236 | + } |
| 237 | + for ( var i = 0; i < rows.length; i++ ) { |
| 238 | + var inputs = rows[i].getElementsByTagName( 'input' ); |
| 239 | + // Check if this row is selected |
| 240 | + var isChecked = true; // Default true in case wpSourceType is not found |
| 241 | + for ( var j = 0; j < inputs.length; j++ ) { |
| 242 | + if ( inputs[j].name == 'wpSourceType' ) |
| 243 | + isChecked = inputs[j].checked; |
| 244 | + } |
| 245 | + // Disable all unselected rows |
| 246 | + for ( var j = 0; j < inputs.length; j++ ) { |
| 247 | + if ( inputs[j].type != 'radio') |
| 248 | + inputs[j].disabled = !isChecked; |
| 249 | + } |
| 250 | + } |
| 251 | + }, |
| 252 | + 'fillDestFilename': function(id) { |
| 253 | + if (!wgUploadAutoFill) { |
| 254 | + return; |
| 255 | + } |
| 256 | + if (!document.getElementById) { |
| 257 | + return; |
| 258 | + } |
| 259 | + // Remove any previously flagged errors |
| 260 | + var e = document.getElementById( 'mw-upload-permitted' ); |
| 261 | + if( e ) e.className = ''; |
| 262 | + var e = document.getElementById( 'mw-upload-prohibited' ); |
| 263 | + if( e ) e.className = ''; |
| 264 | + var path = document.getElementById(id).value; |
| 265 | + // Find trailing part |
| 266 | + var slash = path.lastIndexOf('/'); |
| 267 | + var backslash = path.lastIndexOf('\\'); |
| 268 | + var fname; |
| 269 | + if (slash == -1 && backslash == -1) { |
| 270 | + fname = path; |
| 271 | + } else if (slash > backslash) { |
| 272 | + fname = path.substring(slash+1, 10000); |
| 273 | + } else { |
| 274 | + fname = path.substring(backslash+1, 10000); |
| 275 | + } |
| 276 | + // Clear the filename if it does not have a valid extension. |
| 277 | + // URLs are less likely to have a useful extension, so don't include them in the |
| 278 | + // extension check. |
| 279 | + if( wgStrictFileExtensions && wgFileExtensions && id != 'wpUploadFileURL' ) { |
| 280 | + var found = false; |
| 281 | + if( fname.lastIndexOf( '.' ) != -1 ) { |
| 282 | + var ext = fname.substr( fname.lastIndexOf( '.' ) + 1 ); |
| 283 | + for( var i = 0; i < wgFileExtensions.length; i++ ) { |
| 284 | + if( wgFileExtensions[i].toLowerCase() == ext.toLowerCase() ) { |
| 285 | + found = true; |
| 286 | + break; |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | + if( !found ) { |
| 291 | + // Not a valid extension |
| 292 | + // Clear the upload and set mw-upload-permitted to error |
| 293 | + document.getElementById(id).value = ''; |
| 294 | + var e = document.getElementById( 'mw-upload-permitted' ); |
| 295 | + if( e ) e.className = 'error'; |
| 296 | + var e = document.getElementById( 'mw-upload-prohibited' ); |
| 297 | + if( e ) e.className = 'error'; |
| 298 | + // Clear wpDestFile as well |
| 299 | + var e = document.getElementById( 'wpDestFile' ) |
| 300 | + if( e ) e.value = ''; |
| 301 | + |
| 302 | + return false; |
| 303 | + } |
| 304 | + } |
| 305 | + // Capitalise first letter and replace spaces by underscores |
| 306 | + // FIXME: $wgCapitalizedNamespaces |
| 307 | + fname = fname.charAt(0).toUpperCase().concat(fname.substring(1,10000)).replace(/ /g, '_'); |
| 308 | + // Output result |
| 309 | + var destFile = document.getElementById('wpDestFile'); |
| 310 | + if (destFile) { |
| 311 | + destFile.value = fname; |
| 312 | + wgUploadWarningObj.checkNow(fname) ; |
| 313 | + } |
| 314 | + }, |
| 315 | + 'toggleFilenameFiller': function() { |
| 316 | + if(!document.getElementById) return; |
| 317 | + var upfield = document.getElementById('wpUploadFile'); |
| 318 | + var destName = document.getElementById('wpDestFile').value; |
| 319 | + if (destName=='' || destName==' ') { |
| 320 | + wgUploadAutoFill = true; |
| 321 | + } else { |
| 322 | + wgUploadAutoFill = false; |
| 323 | + } |
| 324 | + } |
12 | 325 | } ); |
13 | 326 | |
14 | 327 | /* Initialization */ |
15 | 328 | |
16 | 329 | $( document ).ready( function() { |
17 | | - // |
| 330 | + mw.legacy.wgUploadSetup(); |
18 | 331 | } ); |
19 | 332 | |
20 | 333 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.wikibits.js |
— | — | @@ -1,5 +1,7 @@ |
2 | 2 | /* |
3 | 3 | * Legacy emulation for the now depricated skins/common/wikibits.js |
| 4 | + * |
| 5 | + * MediaWiki JavaScript support functions |
4 | 6 | */ |
5 | 7 | |
6 | 8 | ( function( $, mw ) { |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.mwsuggest.js |
— | — | @@ -1,5 +1,11 @@ |
2 | 2 | /* |
3 | 3 | * Legacy emulation for the now depricated skins/common/mwsuggest.js |
| 4 | + * |
| 5 | + * OpenSearch ajax suggestion engine for MediaWiki |
| 6 | + * |
| 7 | + * Uses core MediaWiki open search support to fetch suggestions and show them below search boxes and other inputs |
| 8 | + * |
| 9 | + * by Robert Stojnic (April 2008) |
4 | 10 | */ |
5 | 11 | |
6 | 12 | ( function( $, mw ) { |
— | — | @@ -7,13 +13,1019 @@ |
8 | 14 | /* Extension */ |
9 | 15 | |
10 | 16 | $.extend( mw.legacy, { |
11 | | - // |
| 17 | + |
| 18 | + /* Global Variables */ |
| 19 | + |
| 20 | + // search_box_id -> Results object |
| 21 | + 'os_map': {}, |
| 22 | + // cached data, url -> json_text |
| 23 | + 'os_cache': {}, |
| 24 | + // global variables for suggest_keypress |
| 25 | + 'os_cur_keypressed': 0, |
| 26 | + 'os_keypressed_count': 0, |
| 27 | + // type: Timer |
| 28 | + 'os_timer': null, |
| 29 | + // tie mousedown/up events |
| 30 | + 'os_mouse_pressed': false, |
| 31 | + 'os_mouse_num': -1, |
| 32 | + // if true, the last change was made by mouse (and not keyboard) |
| 33 | + 'os_mouse_moved': false, |
| 34 | + // delay between keypress and suggestion (in ms) |
| 35 | + 'os_search_timeout': 250, |
| 36 | + // these pairs of inputs/forms will be autoloaded at startup |
| 37 | + 'os_autoload_inputs': new Array('searchInput', 'searchInput2', 'powerSearchText', 'searchText'), |
| 38 | + 'os_autoload_forms': new Array('searchform', 'searchform2', 'powersearch', 'search' ), |
| 39 | + // if we stopped the service |
| 40 | + 'os_is_stopped': false, |
| 41 | + // max lines to show in suggest table |
| 42 | + 'os_max_lines_per_suggest': 7, |
| 43 | + // number of steps to animate expansion/contraction of container width |
| 44 | + 'os_animation_steps': 6, |
| 45 | + // num of pixels of smallest step |
| 46 | + 'os_animation_min_step': 2, |
| 47 | + // delay between steps (in ms) |
| 48 | + 'os_animation_delay': 30, |
| 49 | + // max width of container in percent of normal size (1 == 100%) |
| 50 | + 'os_container_max_width': 2, |
| 51 | + // currently active animation timer |
| 52 | + 'os_animation_timer': null, |
| 53 | + // <datalist> is a new HTML5 element that allows you to manually supply |
| 54 | + // suggestion lists and have them rendered according to the right platform |
| 55 | + // conventions. However, the only shipping browser as of early 2010 is Opera, |
| 56 | + // and that has a fatal problem: the suggestion lags behind what the user types |
| 57 | + // by one keypress. (Reported as DSK-276870 to Opera's secret bug tracker.) |
| 58 | + // The code here otherwise seems to work, though, so this can be flipped on |
| 59 | + // (maybe with a UA check) when some browser has a better implementation. |
| 60 | + // 'os_use_datalist': 'list' in document.createElement( 'input' ), |
| 61 | + 'os_use_datalist': false, |
| 62 | + |
| 63 | + /* Functions */ |
| 64 | + |
| 65 | + /** |
| 66 | + * Timeout timer class that will fetch the results |
| 67 | + */ |
| 68 | + 'os_Timer': function( id, r, query ) { |
| 69 | + this.id = id; |
| 70 | + this.r = r; |
| 71 | + this.query = query; |
| 72 | + }, |
| 73 | + /** |
| 74 | + * Property class for single search box |
| 75 | + */ |
| 76 | + 'os_Results': function( name, formname ) { |
| 77 | + this.searchform = formname; // id of the searchform |
| 78 | + this.searchbox = name; // id of the searchbox |
| 79 | + this.container = name + 'Suggest'; // div that holds results |
| 80 | + this.resultTable = name + 'Result'; // id base for the result table (+num = table row) |
| 81 | + this.resultText = name + 'ResultText'; // id base for the spans within result tables (+num) |
| 82 | + this.toggle = name + 'Toggle'; // div that has the toggle (enable/disable) link |
| 83 | + this.query = null; // last processed query |
| 84 | + this.results = null; // parsed titles |
| 85 | + this.resultCount = 0; // number of results |
| 86 | + this.original = null; // query that user entered |
| 87 | + this.selected = -1; // which result is selected |
| 88 | + this.containerCount = 0; // number of results visible in container |
| 89 | + this.containerRow = 0; // height of result field in the container |
| 90 | + this.containerTotal = 0; // total height of the container will all results |
| 91 | + this.visible = false; // if container is visible |
| 92 | + this.stayHidden = false; // don't try to show if lost focus |
| 93 | + }, |
| 94 | + /** |
| 95 | + * Timer user to animate expansion/contraction of container width |
| 96 | + */ |
| 97 | + 'os_AnimationTimer': function( r, target ) { |
| 98 | + this.r = r; |
| 99 | + var current = document.getElementById(r.container).offsetWidth; |
| 100 | + this.inc = Math.round( ( target - current ) / os_animation_steps ); |
| 101 | + if( this.inc < os_animation_min_step && this.inc >=0 ) { |
| 102 | + this.inc = os_animation_min_step; // minimal animation step |
| 103 | + } |
| 104 | + if( this.inc > -os_animation_min_step && this.inc < 0 ) { |
| 105 | + this.inc = -os_animation_min_step; |
| 106 | + } |
| 107 | + this.target = target; |
| 108 | + }, |
| 109 | + |
| 110 | + /* Intialization Functions */ |
| 111 | + |
| 112 | + /** |
| 113 | + * Initialization, call upon page onload |
| 114 | + */ |
| 115 | + 'os_MWSuggestInit': function() { |
| 116 | + for( i = 0; i < os_autoload_inputs.length; i++ ) { |
| 117 | + var id = os_autoload_inputs[i]; |
| 118 | + var form = os_autoload_forms[i]; |
| 119 | + element = document.getElementById( id ); |
| 120 | + if( element != null ) { |
| 121 | + os_initHandlers( id, form, element ); |
| 122 | + } |
| 123 | + } |
| 124 | + }, |
| 125 | + /** |
| 126 | + * Init Result objects and event handlers |
| 127 | + */ |
| 128 | + 'os_initHandlers': function( name, formname, element ) { |
| 129 | + var r = new os_Results( name, formname ); |
| 130 | + var formElement = document.getElementById( formname ); |
| 131 | + if( !formElement ) { |
| 132 | + // Older browsers (Opera 8) cannot get form elements |
| 133 | + return; |
| 134 | + } |
| 135 | + // event handler |
| 136 | + os_hookEvent( element, 'keyup', function( event ) { os_eventKeyup( event ); } ); |
| 137 | + os_hookEvent( element, 'keydown', function( event ) { os_eventKeydown( event ); } ); |
| 138 | + os_hookEvent( element, 'keypress', function( event ) { os_eventKeypress( event ); } ); |
| 139 | + if ( !os_use_datalist ) { |
| 140 | + // These are needed for the div hack to hide it if the user blurs. |
| 141 | + os_hookEvent( element, 'blur', function( event ) { os_eventBlur( event ); } ); |
| 142 | + os_hookEvent( element, 'focus', function( event ) { os_eventFocus( event ); } ); |
| 143 | + // We don't want browser auto-suggestions interfering with our div, but |
| 144 | + // autocomplete must be on for datalist to work (at least in Opera |
| 145 | + // 10.10). |
| 146 | + element.setAttribute( 'autocomplete', 'off' ); |
| 147 | + } |
| 148 | + // stopping handler |
| 149 | + os_hookEvent( formElement, 'submit', function( event ) { return os_eventOnsubmit( event ); } ); |
| 150 | + os_map[name] = r; |
| 151 | + // toggle link |
| 152 | + if( document.getElementById( r.toggle ) == null ) { |
| 153 | + // TODO: disable this while we figure out a way for this to work in all browsers |
| 154 | + /* if( name == 'searchInput' ) { |
| 155 | + // special case: place above the main search box |
| 156 | + var t = os_createToggle( r, 'os-suggest-toggle' ); |
| 157 | + var searchBody = document.getElementById( 'searchBody' ); |
| 158 | + var first = searchBody.parentNode.firstChild.nextSibling.appendChild(t); |
| 159 | + } else { |
| 160 | + // default: place below search box to the right |
| 161 | + var t = os_createToggle( r, 'os-suggest-toggle-def' ); |
| 162 | + var top = element.offsetTop + element.offsetHeight; |
| 163 | + var left = element.offsetLeft + element.offsetWidth; |
| 164 | + t.style.position = 'absolute'; |
| 165 | + t.style.top = top + 'px'; |
| 166 | + t.style.left = left + 'px'; |
| 167 | + element.parentNode.appendChild( t ); |
| 168 | + // only now width gets calculated, shift right |
| 169 | + left -= t.offsetWidth; |
| 170 | + t.style.left = left + 'px'; |
| 171 | + t.style.visibility = 'visible'; |
| 172 | + } */ |
| 173 | + } |
| 174 | + }, |
| 175 | + 'os_hookEvent': function( element, hookName, hookFunct ) { |
| 176 | + if ( element.addEventListener ) { |
| 177 | + element.addEventListener( hookName, hookFunct, false ); |
| 178 | + } else if ( window.attachEvent ) { |
| 179 | + element.attachEvent( 'on' + hookName, hookFunct ); |
| 180 | + } |
| 181 | + }, |
| 182 | + |
| 183 | + /* Keyboard Event Functions */ |
| 184 | + |
| 185 | + /** |
| 186 | + * Event handler that will fetch results on keyup |
| 187 | + */ |
| 188 | + 'os_eventKeyup': function( e ) { |
| 189 | + var targ = os_getTarget( e ); |
| 190 | + var r = os_map[targ.id]; |
| 191 | + if( r == null ) { |
| 192 | + return; // not our event |
| 193 | + } |
| 194 | + |
| 195 | + // some browsers won't generate keypressed for arrow keys, catch it |
| 196 | + if( os_keypressed_count == 0 ) { |
| 197 | + os_processKey( r, os_cur_keypressed, targ ); |
| 198 | + } |
| 199 | + var query = targ.value; |
| 200 | + os_fetchResults( r, query, os_search_timeout ); |
| 201 | + }, |
| 202 | + /** |
| 203 | + * Catch arrows up/down and escape to hide the suggestions |
| 204 | + */ |
| 205 | + 'os_processKey': function( r, keypressed, targ ) { |
| 206 | + if ( keypressed == 40 && !r.visible && os_timer == null ) { |
| 207 | + // If the user hits the down arrow, fetch results immediately if none |
| 208 | + // are already displayed. |
| 209 | + r.query = ''; |
| 210 | + os_fetchResults( r, targ.value, 0 ); |
| 211 | + } |
| 212 | + // Otherwise, if we're not using datalist, we need to handle scrolling and |
| 213 | + // so on. |
| 214 | + if ( os_use_datalist ) { |
| 215 | + return; |
| 216 | + } |
| 217 | + if ( keypressed == 40 ) { // Arrow Down |
| 218 | + if ( r.visible ) { |
| 219 | + os_changeHighlight( r, r.selected, r.selected + 1, true ); |
| 220 | + } |
| 221 | + } else if ( keypressed == 38 ) { // Arrow Up |
| 222 | + if ( r.visible ) { |
| 223 | + os_changeHighlight( r, r.selected, r.selected - 1, true ); |
| 224 | + } |
| 225 | + } else if( keypressed == 27 ) { // Escape |
| 226 | + document.getElementById( r.searchbox ).value = r.original; |
| 227 | + r.query = r.original; |
| 228 | + os_hideResults( r ); |
| 229 | + } else if( r.query != document.getElementById( r.searchbox ).value ) { |
| 230 | + // os_hideResults( r ); // don't show old suggestions |
| 231 | + } |
| 232 | + }, |
| 233 | + /** |
| 234 | + * When keys is held down use a timer to output regular events |
| 235 | + */ |
| 236 | + 'os_eventKeypress': function( e ) { |
| 237 | + var targ = os_getTarget( e ); |
| 238 | + var r = os_map[targ.id]; |
| 239 | + if( r == null ) { |
| 240 | + return; // not our event |
| 241 | + } |
| 242 | + |
| 243 | + var keypressed = os_cur_keypressed; |
| 244 | + |
| 245 | + os_keypressed_count++; |
| 246 | + os_processKey( r, keypressed, targ ); |
| 247 | + }, |
| 248 | + /** |
| 249 | + * Catch the key code (Firefox bug) |
| 250 | + */ |
| 251 | + 'os_eventKeydown': function( e ) { |
| 252 | + if ( !e ) { |
| 253 | + e = window.event; |
| 254 | + } |
| 255 | + var targ = os_getTarget( e ); |
| 256 | + var r = os_map[targ.id]; |
| 257 | + if( r == null ) { |
| 258 | + return; // not our event |
| 259 | + } |
| 260 | + |
| 261 | + os_mouse_moved = false; |
| 262 | + |
| 263 | + os_cur_keypressed = ( e.keyCode == undefined ) ? e.which : e.keyCode; |
| 264 | + os_keypressed_count = 0; |
| 265 | + }, |
| 266 | + /** |
| 267 | + * When the form is submitted hide everything, cancel updates... |
| 268 | + */ |
| 269 | + 'os_eventOnsubmit': function( e ) { |
| 270 | + var targ = os_getTarget( e ); |
| 271 | + |
| 272 | + os_is_stopped = true; |
| 273 | + // kill timed requests |
| 274 | + if( os_timer != null && os_timer.id != null ) { |
| 275 | + clearTimeout( os_timer.id ); |
| 276 | + os_timer = null; |
| 277 | + } |
| 278 | + // Hide all suggestions |
| 279 | + for( i = 0; i < os_autoload_inputs.length; i++ ) { |
| 280 | + var r = os_map[os_autoload_inputs[i]]; |
| 281 | + if( r != null ) { |
| 282 | + var b = document.getElementById( r.searchform ); |
| 283 | + if( b != null && b == targ ) { |
| 284 | + // set query value so the handler won't try to fetch additional results |
| 285 | + r.query = document.getElementById( r.searchbox ).value; |
| 286 | + } |
| 287 | + os_hideResults( r ); |
| 288 | + } |
| 289 | + } |
| 290 | + return true; |
| 291 | + }, |
| 292 | + /** |
| 293 | + * Hide results from the user, either making the div visibility=hidden or detaching the datalist from the input. |
| 294 | + */ |
| 295 | + 'os_hideResults': function( r ) { |
| 296 | + if ( os_use_datalist ) { |
| 297 | + document.getElementById( r.searchbox ).setAttribute( 'list', '' ); |
| 298 | + } else { |
| 299 | + var c = document.getElementById( r.container ); |
| 300 | + if ( c != null ) { |
| 301 | + c.style.visibility = 'hidden'; |
| 302 | + } |
| 303 | + } |
| 304 | + r.visible = false; |
| 305 | + r.selected = -1; |
| 306 | + }, |
| 307 | + 'os_decodeValue': function( value ) { |
| 308 | + if ( decodeURIComponent ) { |
| 309 | + return decodeURIComponent( value ); |
| 310 | + } |
| 311 | + if( unescape ) { |
| 312 | + return unescape( value ); |
| 313 | + } |
| 314 | + return null; |
| 315 | + }, |
| 316 | + 'os_encodeQuery': function( value ) { |
| 317 | + if ( encodeURIComponent ) { |
| 318 | + return encodeURIComponent( value ); |
| 319 | + } |
| 320 | + if( escape ) { |
| 321 | + return escape( value ); |
| 322 | + } |
| 323 | + return null; |
| 324 | + }, |
| 325 | + /** |
| 326 | + * Handles data from XMLHttpRequest, and updates the suggest results |
| 327 | + */ |
| 328 | + 'os_updateResults': function( r, query, text, cacheKey ) { |
| 329 | + os_cache[cacheKey] = text; |
| 330 | + r.query = query; |
| 331 | + r.original = query; |
| 332 | + if( text == '' ) { |
| 333 | + r.results = null; |
| 334 | + r.resultCount = 0; |
| 335 | + os_hideResults( r ); |
| 336 | + } else { |
| 337 | + try { |
| 338 | + var p = eval( '(' + text + ')' ); // simple json parse, could do a safer one |
| 339 | + if( p.length < 2 || p[1].length == 0 ) { |
| 340 | + r.results = null; |
| 341 | + r.resultCount = 0; |
| 342 | + os_hideResults( r ); |
| 343 | + return; |
| 344 | + } |
| 345 | + if ( os_use_datalist ) { |
| 346 | + os_setupDatalist( r, p[1] ); |
| 347 | + } else { |
| 348 | + os_setupDiv( r, p[1] ); |
| 349 | + } |
| 350 | + } catch( e ) { |
| 351 | + // bad response from server or such |
| 352 | + os_hideResults( r ); |
| 353 | + os_cache[cacheKey] = null; |
| 354 | + } |
| 355 | + } |
| 356 | + }, |
| 357 | + /** |
| 358 | + * Create and populate a <datalist>. |
| 359 | + * |
| 360 | + * @param r os_Result object |
| 361 | + * @param results Array of the new results to replace existing ones |
| 362 | + */ |
| 363 | + 'os_setupDatalist': function( r, results ) { |
| 364 | + var s = document.getElementById( r.searchbox ); |
| 365 | + var c = document.getElementById( r.container ); |
| 366 | + if ( c == null ) { |
| 367 | + c = document.createElement( 'datalist' ); |
| 368 | + c.setAttribute( 'id', r.container ); |
| 369 | + document.body.appendChild( c ); |
| 370 | + } else { |
| 371 | + c.innerHTML = ''; |
| 372 | + } |
| 373 | + s.setAttribute( 'list', r.container ); |
| 374 | + |
| 375 | + r.results = new Array(); |
| 376 | + r.resultCount = results.length; |
| 377 | + r.visible = true; |
| 378 | + for ( i = 0; i < results.length; i++ ) { |
| 379 | + var title = os_decodeValue( results[i] ); |
| 380 | + var opt = document.createElement( 'option' ); |
| 381 | + opt.value = title; |
| 382 | + r.results[i] = title; |
| 383 | + c.appendChild( opt ); |
| 384 | + } |
| 385 | + }, |
| 386 | + /** |
| 387 | + * Fetch namespaces from checkboxes or hidden fields in the search form, if none defined use wgSearchNamespaces |
| 388 | + * global |
| 389 | + */ |
| 390 | + 'os_getNamespaces': function( r ) { |
| 391 | + var namespaces = ''; |
| 392 | + var elements = document.forms[r.searchform].elements; |
| 393 | + for( i = 0; i < elements.length; i++ ) { |
| 394 | + var name = elements[i].name; |
| 395 | + if( typeof name != 'undefined' && name.length > 2 && name[0] == 'n' && |
| 396 | + name[1] == 's' && ( |
| 397 | + ( elements[i].type == 'checkbox' && elements[i].checked ) || |
| 398 | + ( elements[i].type == 'hidden' && elements[i].value == '1' ) |
| 399 | + ) |
| 400 | + ) { |
| 401 | + if( namespaces != '' ) { |
| 402 | + namespaces += '|'; |
| 403 | + } |
| 404 | + namespaces += name.substring( 2 ); |
| 405 | + } |
| 406 | + } |
| 407 | + if( namespaces == '' ) { |
| 408 | + namespaces = wgSearchNamespaces.join('|'); |
| 409 | + } |
| 410 | + return namespaces; |
| 411 | + }, |
| 412 | + /** |
| 413 | + * Update results if user hasn't already typed something else |
| 414 | + */ |
| 415 | + 'os_updateIfRelevant': function( r, query, text, cacheKey ) { |
| 416 | + var t = document.getElementById( r.searchbox ); |
| 417 | + if( t != null && t.value == query ) { // check if response is still relevant |
| 418 | + os_updateResults( r, query, text, cacheKey ); |
| 419 | + } |
| 420 | + r.query = query; |
| 421 | + }, |
| 422 | + /** |
| 423 | + * Fetch results after some timeout |
| 424 | + */ |
| 425 | + 'os_delayedFetch': function() { |
| 426 | + if( os_timer == null ) { |
| 427 | + return; |
| 428 | + } |
| 429 | + var r = os_timer.r; |
| 430 | + var query = os_timer.query; |
| 431 | + os_timer = null; |
| 432 | + var path = wgMWSuggestTemplate.replace( "{namespaces}", os_getNamespaces( r ) ) |
| 433 | + .replace( "{dbname}", wgDBname ) |
| 434 | + .replace( "{searchTerms}", os_encodeQuery( query ) ); |
| 435 | + // try to get from cache, if not fetch using ajax |
| 436 | + var cached = os_cache[path]; |
| 437 | + if( cached != null && cached != undefined ) { |
| 438 | + os_updateIfRelevant( r, query, cached, path ); |
| 439 | + } else { |
| 440 | + var xmlhttp = sajax_init_object(); |
| 441 | + if( xmlhttp ) { |
| 442 | + try { |
| 443 | + xmlhttp.open( 'GET', path, true ); |
| 444 | + xmlhttp.onreadystatechange = function() { |
| 445 | + if ( xmlhttp.readyState == 4 && typeof os_updateIfRelevant == 'function' ) { |
| 446 | + os_updateIfRelevant( r, query, xmlhttp.responseText, path ); |
| 447 | + } |
| 448 | + }; |
| 449 | + xmlhttp.send( null ); |
| 450 | + } catch ( e ) { |
| 451 | + if ( window.location.hostname == 'localhost' ) { |
| 452 | + alert( "Your browser blocks XMLHttpRequest to 'localhost', try using a real hostname for development/testing." ); |
| 453 | + } |
| 454 | + throw e; |
| 455 | + } |
| 456 | + } |
| 457 | + } |
| 458 | + }, |
| 459 | + /** |
| 460 | + * Init timed update via os_delayedUpdate() |
| 461 | + */ |
| 462 | + 'os_fetchResults': function( r, query, timeout ) { |
| 463 | + if( query == '' ) { |
| 464 | + r.query = ''; |
| 465 | + os_hideResults( r ); |
| 466 | + return; |
| 467 | + } else if( query == r.query ) { |
| 468 | + return; // no change |
| 469 | + } |
| 470 | + |
| 471 | + os_is_stopped = false; // make sure we're running |
| 472 | + |
| 473 | + // cancel any pending fetches |
| 474 | + if( os_timer != null && os_timer.id != null ) { |
| 475 | + clearTimeout( os_timer.id ); |
| 476 | + } |
| 477 | + // schedule delayed fetching of results |
| 478 | + if( timeout != 0 ) { |
| 479 | + os_timer = new os_Timer( setTimeout( "os_delayedFetch()", timeout ), r, query ); |
| 480 | + } else { |
| 481 | + os_timer = new os_Timer( null, r, query ); |
| 482 | + os_delayedFetch(); // do it now! |
| 483 | + } |
| 484 | + }, |
| 485 | + /** |
| 486 | + * Find event target |
| 487 | + */ |
| 488 | + 'os_getTarget': function( e ) { |
| 489 | + if ( !e ) { |
| 490 | + e = window.event; |
| 491 | + } |
| 492 | + if ( e.target ) { |
| 493 | + return e.target; |
| 494 | + } else if ( e.srcElement ) { |
| 495 | + return e.srcElement; |
| 496 | + } else { |
| 497 | + return null; |
| 498 | + } |
| 499 | + }, |
| 500 | + /** |
| 501 | + * Check if x is a valid integer |
| 502 | + */ |
| 503 | + 'os_isNumber': function( x ) { |
| 504 | + if( x == '' || isNaN( x ) ) { |
| 505 | + return false; |
| 506 | + } |
| 507 | + for( var i = 0; i < x.length; i++ ) { |
| 508 | + var c = x.charAt( i ); |
| 509 | + if( !( c >= '0' && c <= '9' ) ) { |
| 510 | + return false; |
| 511 | + } |
| 512 | + } |
| 513 | + return true; |
| 514 | + }, |
| 515 | + /** |
| 516 | + * Call this to enable suggestions on input (id=inputId), on a form (name=formName) |
| 517 | + */ |
| 518 | + 'os_enableSuggestionsOn': function( inputId, formName ) { |
| 519 | + os_initHandlers( inputId, formName, document.getElementById( inputId ) ); |
| 520 | + }, |
| 521 | + /** |
| 522 | + * Call this to disable suggestios on input box (id=inputId) |
| 523 | + */ |
| 524 | + 'os_disableSuggestionsOn': function( inputId ) { |
| 525 | + r = os_map[inputId]; |
| 526 | + if( r != null ) { |
| 527 | + // cancel/hide results |
| 528 | + os_timer = null; |
| 529 | + os_hideResults( r ); |
| 530 | + // turn autocomplete on ! |
| 531 | + document.getElementById( inputId ).setAttribute( 'autocomplete', 'on' ); |
| 532 | + // remove descriptor |
| 533 | + os_map[inputId] = null; |
| 534 | + } |
| 535 | + |
| 536 | + // Remove the element from the os_autoload_* arrays |
| 537 | + var index = os_autoload_inputs.indexOf( inputId ); |
| 538 | + if ( index >= 0 ) { |
| 539 | + os_autoload_inputs[index] = os_autoload_forms[index] = ''; |
| 540 | + } |
| 541 | + }, |
| 542 | + |
| 543 | + /* Div-only Functions */ |
| 544 | + |
| 545 | + /** |
| 546 | + * Event: loss of focus of input box |
| 547 | + */ |
| 548 | + 'os_eventBlur': function( e ) { |
| 549 | + var targ = os_getTarget( e ); |
| 550 | + var r = os_map[targ.id]; |
| 551 | + if( r == null ) { |
| 552 | + return; // not our event |
| 553 | + } |
| 554 | + if( !os_mouse_pressed ) { |
| 555 | + os_hideResults( r ); |
| 556 | + // force canvas to stay hidden |
| 557 | + r.stayHidden = true; |
| 558 | + // cancel any pending fetches |
| 559 | + if( os_timer != null && os_timer.id != null ) { |
| 560 | + clearTimeout( os_timer.id ); |
| 561 | + } |
| 562 | + os_timer = null; |
| 563 | + } |
| 564 | + }, |
| 565 | + /** |
| 566 | + * Event: focus (catch only when stopped) |
| 567 | + */ |
| 568 | + 'os_eventFocus': function( e ) { |
| 569 | + var targ = os_getTarget( e ); |
| 570 | + var r = os_map[targ.id]; |
| 571 | + if( r == null ) { |
| 572 | + return; // not our event |
| 573 | + } |
| 574 | + r.stayHidden = false; |
| 575 | + }, |
| 576 | + /** |
| 577 | + * Create and populate a <div>, for non-<datalist>-supporting browsers. |
| 578 | + * |
| 579 | + * @param r os_Result object |
| 580 | + * @param results Array of the new results to replace existing ones |
| 581 | + */ |
| 582 | + 'os_setupDiv': function( r, results ) { |
| 583 | + var c = document.getElementById( r.container ); |
| 584 | + if ( c == null ) { |
| 585 | + c = os_createContainer( r ); |
| 586 | + } |
| 587 | + c.innerHTML = os_createResultTable( r, results ); |
| 588 | + // init container table sizes |
| 589 | + var t = document.getElementById( r.resultTable ); |
| 590 | + r.containerTotal = t.offsetHeight; |
| 591 | + r.containerRow = t.offsetHeight / r.resultCount; |
| 592 | + os_fitContainer( r ); |
| 593 | + os_trimResultText( r ); |
| 594 | + os_showResults( r ); |
| 595 | + }, |
| 596 | + /** |
| 597 | + * Create the result table to be placed in the container div |
| 598 | + */ |
| 599 | + 'os_createResultTable': function( r, results ) { |
| 600 | + var c = document.getElementById( r.container ); |
| 601 | + var width = c.offsetWidth - os_operaWidthFix( c.offsetWidth ); |
| 602 | + var html = '<table class="os-suggest-results" id="' + r.resultTable + '" style="width: ' + width + 'px;">'; |
| 603 | + r.results = new Array(); |
| 604 | + r.resultCount = results.length; |
| 605 | + for( i = 0; i < results.length; i++ ) { |
| 606 | + var title = os_decodeValue( results[i] ); |
| 607 | + r.results[i] = title; |
| 608 | + html += '<tr><td class="os-suggest-result" id="' + r.resultTable + i + '"><span id="' + r.resultText + i + '">' + title + '</span></td></tr>'; |
| 609 | + } |
| 610 | + html += '</table>'; |
| 611 | + return html; |
| 612 | + }, |
| 613 | + /** |
| 614 | + * Show results div |
| 615 | + */ |
| 616 | + 'os_showResults': function( r ) { |
| 617 | + if( os_is_stopped ) { |
| 618 | + return; |
| 619 | + } |
| 620 | + if( r.stayHidden ) { |
| 621 | + return; |
| 622 | + } |
| 623 | + os_fitContainer( r ); |
| 624 | + var c = document.getElementById( r.container ); |
| 625 | + r.selected = -1; |
| 626 | + if( c != null ) { |
| 627 | + c.scrollTop = 0; |
| 628 | + c.style.visibility = 'visible'; |
| 629 | + r.visible = true; |
| 630 | + } |
| 631 | + }, |
| 632 | + 'os_operaWidthFix': function( x ) { |
| 633 | + // For browsers that don't understand overflow-x, estimate scrollbar width |
| 634 | + if( typeof document.body.style.overflowX != 'string' ) { |
| 635 | + return 30; |
| 636 | + } |
| 637 | + return 0; |
| 638 | + }, |
| 639 | + |
| 640 | + /* Brower-dependent Functions */ |
| 641 | + |
| 642 | + 'f_clientWidth': function() { |
| 643 | + return f_filterResults( |
| 644 | + window.innerWidth ? window.innerWidth : 0, |
| 645 | + document.documentElement ? document.documentElement.clientWidth : 0, |
| 646 | + document.body ? document.body.clientWidth : 0 |
| 647 | + ); |
| 648 | + }, |
| 649 | + 'f_clientHeight': function() { |
| 650 | + return f_filterResults( |
| 651 | + window.innerHeight ? window.innerHeight : 0, |
| 652 | + document.documentElement ? document.documentElement.clientHeight : 0, |
| 653 | + document.body ? document.body.clientHeight : 0 |
| 654 | + ); |
| 655 | + }, |
| 656 | + 'f_scrollLeft': function() { |
| 657 | + return f_filterResults( |
| 658 | + window.pageXOffset ? window.pageXOffset : 0, |
| 659 | + document.documentElement ? document.documentElement.scrollLeft : 0, |
| 660 | + document.body ? document.body.scrollLeft : 0 |
| 661 | + ); |
| 662 | + }, |
| 663 | + 'f_scrollTop': function() { |
| 664 | + return f_filterResults( |
| 665 | + window.pageYOffset ? window.pageYOffset : 0, |
| 666 | + document.documentElement ? document.documentElement.scrollTop : 0, |
| 667 | + document.body ? document.body.scrollTop : 0 |
| 668 | + ); |
| 669 | + }, |
| 670 | + 'f_filterResults': function( n_win, n_docel, n_body ) { |
| 671 | + var n_result = n_win ? n_win : 0; |
| 672 | + if ( n_docel && ( !n_result || ( n_result > n_docel ) ) ) { |
| 673 | + n_result = n_docel; |
| 674 | + } |
| 675 | + return n_body && ( !n_result || ( n_result > n_body ) ) ? n_body : n_result; |
| 676 | + }, |
| 677 | + /** |
| 678 | + * Get the height available for the results container |
| 679 | + */ |
| 680 | + 'os_availableHeight': function( r ) { |
| 681 | + var absTop = document.getElementById( r.container ).style.top; |
| 682 | + var px = absTop.lastIndexOf( 'px' ); |
| 683 | + if( px > 0 ) { |
| 684 | + absTop = absTop.substring( 0, px ); |
| 685 | + } |
| 686 | + return f_clientHeight() - ( absTop - f_scrollTop() ); |
| 687 | + }, |
| 688 | + /** |
| 689 | + * Get element absolute position {left,top} |
| 690 | + */ |
| 691 | + 'os_getElementPosition': function( elemID ) { |
| 692 | + var offsetTrail = document.getElementById( elemID ); |
| 693 | + var offsetLeft = 0; |
| 694 | + var offsetTop = 0; |
| 695 | + while ( offsetTrail ) { |
| 696 | + offsetLeft += offsetTrail.offsetLeft; |
| 697 | + offsetTop += offsetTrail.offsetTop; |
| 698 | + offsetTrail = offsetTrail.offsetParent; |
| 699 | + } |
| 700 | + if ( navigator.userAgent.indexOf('Mac') != -1 && typeof document.body.leftMargin != 'undefined' ) { |
| 701 | + offsetLeft += document.body.leftMargin; |
| 702 | + offsetTop += document.body.topMargin; |
| 703 | + } |
| 704 | + return { left:offsetLeft, top:offsetTop }; |
| 705 | + }, |
| 706 | + /** |
| 707 | + * Create the container div that will hold the suggested titles |
| 708 | + */ |
| 709 | + 'os_createContainer': function( r ) { |
| 710 | + var c = document.createElement( 'div' ); |
| 711 | + var s = document.getElementById( r.searchbox ); |
| 712 | + var pos = os_getElementPosition( r.searchbox ); |
| 713 | + var left = pos.left; |
| 714 | + var top = pos.top + s.offsetHeight; |
| 715 | + c.className = 'os-suggest'; |
| 716 | + c.setAttribute( 'id', r.container ); |
| 717 | + document.body.appendChild( c ); |
| 718 | + |
| 719 | + // dynamically generated style params |
| 720 | + // IE workaround, cannot explicitely set "style" attribute |
| 721 | + c = document.getElementById( r.container ); |
| 722 | + c.style.top = top + 'px'; |
| 723 | + c.style.left = left + 'px'; |
| 724 | + c.style.width = s.offsetWidth + 'px'; |
| 725 | + |
| 726 | + // mouse event handlers |
| 727 | + c.onmouseover = function( event ) { os_eventMouseover( r.searchbox, event ); }; |
| 728 | + c.onmousemove = function( event ) { os_eventMousemove( r.searchbox, event ); }; |
| 729 | + c.onmousedown = function( event ) { return os_eventMousedown( r.searchbox, event ); }; |
| 730 | + c.onmouseup = function( event ) { os_eventMouseup( r.searchbox, event ); }; |
| 731 | + return c; |
| 732 | + }, |
| 733 | + /** |
| 734 | + * Change container height to fit to screen |
| 735 | + */ |
| 736 | + 'os_fitContainer': function( r ) { |
| 737 | + var c = document.getElementById( r.container ); |
| 738 | + var h = os_availableHeight( r ) - 20; |
| 739 | + var inc = r.containerRow; |
| 740 | + h = parseInt( h / inc ) * inc; |
| 741 | + if( h < ( 2 * inc ) && r.resultCount > 1 ) { // min: two results |
| 742 | + h = 2 * inc; |
| 743 | + } |
| 744 | + if( ( h / inc ) > os_max_lines_per_suggest ) { |
| 745 | + h = inc * os_max_lines_per_suggest; |
| 746 | + } |
| 747 | + if( h < r.containerTotal ) { |
| 748 | + c.style.height = h + 'px'; |
| 749 | + r.containerCount = parseInt( Math.round( h / inc ) ); |
| 750 | + } else { |
| 751 | + c.style.height = r.containerTotal + 'px'; |
| 752 | + r.containerCount = r.resultCount; |
| 753 | + } |
| 754 | + }, |
| 755 | + /** |
| 756 | + * If some entries are longer than the box, replace text with "..." |
| 757 | + */ |
| 758 | + 'os_trimResultText': function( r ) { |
| 759 | + // find max width, first see if we could expand the container to fit it |
| 760 | + var maxW = 0; |
| 761 | + for( var i = 0; i < r.resultCount; i++ ) { |
| 762 | + var e = document.getElementById( r.resultText + i ); |
| 763 | + if( e.offsetWidth > maxW ) { |
| 764 | + maxW = e.offsetWidth; |
| 765 | + } |
| 766 | + } |
| 767 | + var w = document.getElementById( r.container ).offsetWidth; |
| 768 | + var fix = 0; |
| 769 | + if( r.containerCount < r.resultCount ) { |
| 770 | + fix = 20; // give 20px for scrollbar |
| 771 | + } else { |
| 772 | + fix = os_operaWidthFix( w ); |
| 773 | + } |
| 774 | + if( fix < 4 ) { |
| 775 | + fix = 4; // basic padding |
| 776 | + } |
| 777 | + maxW += fix; |
| 778 | + |
| 779 | + // resize container to fit more data if permitted |
| 780 | + var normW = document.getElementById( r.searchbox ).offsetWidth; |
| 781 | + var prop = maxW / normW; |
| 782 | + if( prop > os_container_max_width ) { |
| 783 | + prop = os_container_max_width; |
| 784 | + } else if( prop < 1 ) { |
| 785 | + prop = 1; |
| 786 | + } |
| 787 | + var newW = Math.round( normW * prop ); |
| 788 | + if( w != newW ) { |
| 789 | + w = newW; |
| 790 | + if( os_animation_timer != null ) { |
| 791 | + clearInterval( os_animation_timer.id ); |
| 792 | + } |
| 793 | + os_animation_timer = new os_AnimationTimer( r, w ); |
| 794 | + os_animation_timer.id = setInterval( "os_animateChangeWidth()", os_animation_delay ); |
| 795 | + w -= fix; // this much is reserved |
| 796 | + } |
| 797 | + |
| 798 | + // trim results |
| 799 | + if( w < 10 ) { |
| 800 | + return; |
| 801 | + } |
| 802 | + for( var i = 0; i < r.resultCount; i++ ) { |
| 803 | + var e = document.getElementById( r.resultText + i ); |
| 804 | + var replace = 1; |
| 805 | + var lastW = e.offsetWidth + 1; |
| 806 | + var iteration = 0; |
| 807 | + var changedText = false; |
| 808 | + while( e.offsetWidth > w && ( e.offsetWidth < lastW || iteration < 2 ) ) { |
| 809 | + changedText = true; |
| 810 | + lastW = e.offsetWidth; |
| 811 | + var l = e.innerHTML; |
| 812 | + e.innerHTML = l.substring( 0, l.length - replace ) + '...'; |
| 813 | + iteration++; |
| 814 | + replace = 4; // how many chars to replace |
| 815 | + } |
| 816 | + if( changedText ) { |
| 817 | + // show hint for trimmed titles |
| 818 | + document.getElementById( r.resultTable + i ).setAttribute( 'title', r.results[i] ); |
| 819 | + } |
| 820 | + } |
| 821 | + }, |
| 822 | + /** |
| 823 | + * Invoked on timer to animate change in container width |
| 824 | + */ |
| 825 | + 'os_animateChangeWidth': function() { |
| 826 | + var r = os_animation_timer.r; |
| 827 | + var c = document.getElementById( r.container ); |
| 828 | + var w = c.offsetWidth; |
| 829 | + var normW = document.getElementById( r.searchbox ).offsetWidth; |
| 830 | + var normL = os_getElementPosition( r.searchbox ).left; |
| 831 | + var inc = os_animation_timer.inc; |
| 832 | + var target = os_animation_timer.target; |
| 833 | + var nw = w + inc; |
| 834 | + if( ( inc > 0 && nw >= target ) || ( inc <= 0 && nw <= target ) ) { |
| 835 | + // finished ! |
| 836 | + c.style.width = target + 'px'; |
| 837 | + clearInterval( os_animation_timer.id ); |
| 838 | + os_animation_timer = null; |
| 839 | + } else { |
| 840 | + // in-progress |
| 841 | + c.style.width = nw + 'px'; |
| 842 | + if( document.documentElement.dir == 'rtl' ) { |
| 843 | + c.style.left = ( normL + normW + ( target - nw ) - os_animation_timer.target - 1 ) + 'px'; |
| 844 | + } |
| 845 | + } |
| 846 | + }, |
| 847 | + /** |
| 848 | + * Change the highlighted row (i.e. suggestion), from position cur to next |
| 849 | + */ |
| 850 | + 'os_changeHighlight': function( r, cur, next, updateSearchBox ) { |
| 851 | + if ( next >= r.resultCount ) { |
| 852 | + next = r.resultCount - 1; |
| 853 | + } |
| 854 | + if ( next < -1 ) { |
| 855 | + next = -1; |
| 856 | + } |
| 857 | + r.selected = next; |
| 858 | + if ( cur == next ) { |
| 859 | + return; // nothing to do. |
| 860 | + } |
| 861 | + |
| 862 | + if( cur >= 0 ) { |
| 863 | + var curRow = document.getElementById( r.resultTable + cur ); |
| 864 | + if( curRow != null ) { |
| 865 | + curRow.className = 'os-suggest-result'; |
| 866 | + } |
| 867 | + } |
| 868 | + var newText; |
| 869 | + if( next >= 0 ) { |
| 870 | + var nextRow = document.getElementById( r.resultTable + next ); |
| 871 | + if( nextRow != null ) { |
| 872 | + nextRow.className = os_HighlightClass(); |
| 873 | + } |
| 874 | + newText = r.results[next]; |
| 875 | + } else { |
| 876 | + newText = r.original; |
| 877 | + } |
| 878 | + |
| 879 | + // adjust the scrollbar if any |
| 880 | + if( r.containerCount < r.resultCount ) { |
| 881 | + var c = document.getElementById( r.container ); |
| 882 | + var vStart = c.scrollTop / r.containerRow; |
| 883 | + var vEnd = vStart + r.containerCount; |
| 884 | + if( next < vStart ) { |
| 885 | + c.scrollTop = next * r.containerRow; |
| 886 | + } else if( next >= vEnd ) { |
| 887 | + c.scrollTop = ( next - r.containerCount + 1 ) * r.containerRow; |
| 888 | + } |
| 889 | + } |
| 890 | + |
| 891 | + // update the contents of the search box |
| 892 | + if( updateSearchBox ) { |
| 893 | + os_updateSearchQuery( r, newText ); |
| 894 | + } |
| 895 | + }, |
| 896 | + 'os_HighlightClass': function() { |
| 897 | + var match = navigator.userAgent.match(/AppleWebKit\/(\d+)/); |
| 898 | + if ( match ) { |
| 899 | + var webKitVersion = parseInt( match[1] ); |
| 900 | + if ( webKitVersion < 523 ) { |
| 901 | + // CSS system highlight colors broken on old Safari |
| 902 | + // https://bugs.webkit.org/show_bug.cgi?id=6129 |
| 903 | + // Safari 3.0.4, 3.1 known ok |
| 904 | + return 'os-suggest-result-hl-webkit'; |
| 905 | + } |
| 906 | + } |
| 907 | + return 'os-suggest-result-hl'; |
| 908 | + }, |
| 909 | + 'os_updateSearchQuery': function( r, newText ) { |
| 910 | + document.getElementById( r.searchbox ).value = newText; |
| 911 | + r.query = newText; |
| 912 | + }, |
| 913 | + |
| 914 | + /* Mouse Event Functions */ |
| 915 | + |
| 916 | + /** |
| 917 | + * Mouse over the container |
| 918 | + */ |
| 919 | + 'os_eventMouseover': function( srcId, e ) { |
| 920 | + var targ = os_getTarget( e ); |
| 921 | + var r = os_map[srcId]; |
| 922 | + if( r == null || !os_mouse_moved ) { |
| 923 | + return; // not our event |
| 924 | + } |
| 925 | + var num = os_getNumberSuffix( targ.id ); |
| 926 | + if( num >= 0 ) { |
| 927 | + os_changeHighlight( r, r.selected, num, false ); |
| 928 | + } |
| 929 | + }, |
| 930 | + /** |
| 931 | + * Get row where the event occured (from its id) |
| 932 | + */ |
| 933 | + 'os_getNumberSuffix': function( id ) { |
| 934 | + var num = id.substring( id.length - 2 ); |
| 935 | + if( !( num.charAt( 0 ) >= '0' && num.charAt( 0 ) <= '9' ) ) { |
| 936 | + num = num.substring( 1 ); |
| 937 | + } |
| 938 | + if( os_isNumber( num ) ) { |
| 939 | + return parseInt( num ); |
| 940 | + } else { |
| 941 | + return -1; |
| 942 | + } |
| 943 | + }, |
| 944 | + /** |
| 945 | + * Save mouse move as last action |
| 946 | + */ |
| 947 | + 'os_eventMousemove': function( srcId, e ) { |
| 948 | + os_mouse_moved = true; |
| 949 | + }, |
| 950 | + /** |
| 951 | + * Mouse button held down, register possible click |
| 952 | + */ |
| 953 | + 'os_eventMousedown': function( srcId, e ) { |
| 954 | + var targ = os_getTarget( e ); |
| 955 | + var r = os_map[srcId]; |
| 956 | + if( r == null ) { |
| 957 | + return; // not our event |
| 958 | + } |
| 959 | + var num = os_getNumberSuffix( targ.id ); |
| 960 | + |
| 961 | + os_mouse_pressed = true; |
| 962 | + if( num >= 0 ) { |
| 963 | + os_mouse_num = num; |
| 964 | + // os_updateSearchQuery( r, r.results[num] ); |
| 965 | + } |
| 966 | + // keep the focus on the search field |
| 967 | + document.getElementById( r.searchbox ).focus(); |
| 968 | + |
| 969 | + return false; // prevents selection |
| 970 | + }, |
| 971 | + /** |
| 972 | + * Mouse button released, check for click on some row |
| 973 | + */ |
| 974 | + 'os_eventMouseup': function( srcId, e ) { |
| 975 | + var targ = os_getTarget( e ); |
| 976 | + var r = os_map[srcId]; |
| 977 | + if( r == null ) { |
| 978 | + return; // not our event |
| 979 | + } |
| 980 | + var num = os_getNumberSuffix( targ.id ); |
| 981 | + |
| 982 | + if( num >= 0 && os_mouse_num == num ) { |
| 983 | + os_updateSearchQuery( r, r.results[num] ); |
| 984 | + os_hideResults( r ); |
| 985 | + document.getElementById( r.searchform ).submit(); |
| 986 | + } |
| 987 | + os_mouse_pressed = false; |
| 988 | + // keep the focus on the search field |
| 989 | + document.getElementById( r.searchbox ).focus(); |
| 990 | + }, |
| 991 | + /** |
| 992 | + * Return the span element that contains the toggle link (dead code?) |
| 993 | + */ |
| 994 | + 'os_createToggle': function( r, className ) { |
| 995 | + var t = document.createElement( 'span' ); |
| 996 | + t.className = className; |
| 997 | + t.setAttribute( 'id', r.toggle ); |
| 998 | + var link = document.createElement( 'a' ); |
| 999 | + link.setAttribute( 'href', 'javascript:void(0);' ); |
| 1000 | + link.onclick = function() { os_toggle( r.searchbox, r.searchform ); }; |
| 1001 | + var msg = document.createTextNode( wgMWSuggestMessages[0] ); |
| 1002 | + link.appendChild( msg ); |
| 1003 | + t.appendChild( link ); |
| 1004 | + return t; |
| 1005 | + }, |
| 1006 | + /** |
| 1007 | + * Call when user clicks on some of the toggle links (dead code?) |
| 1008 | + */ |
| 1009 | + 'os_toggle': function( inputId, formName ) { |
| 1010 | + r = os_map[inputId]; |
| 1011 | + var msg = ''; |
| 1012 | + if( r == null ) { |
| 1013 | + os_enableSuggestionsOn( inputId, formName ); |
| 1014 | + r = os_map[inputId]; |
| 1015 | + msg = wgMWSuggestMessages[0]; |
| 1016 | + } else{ |
| 1017 | + os_disableSuggestionsOn( inputId, formName ); |
| 1018 | + msg = wgMWSuggestMessages[1]; |
| 1019 | + } |
| 1020 | + // change message |
| 1021 | + var link = document.getElementById( r.toggle ).firstChild; |
| 1022 | + link.replaceChild( document.createTextNode( msg ), link.firstChild ); |
| 1023 | + } |
12 | 1024 | } ); |
13 | 1025 | |
14 | 1026 | /* Initialization */ |
15 | 1027 | |
16 | 1028 | $( document ).ready( function() { |
17 | | - // |
| 1029 | + mw.legacy.os_MWSuggestInit(); |
18 | 1030 | } ); |
19 | 1031 | |
20 | 1032 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.htmlform.js |
— | — | @@ -7,13 +7,44 @@ |
8 | 8 | /* Extension */ |
9 | 9 | |
10 | 10 | $.extend( mw.legacy, { |
11 | | - // |
| 11 | + |
| 12 | + /* Global Variables */ |
| 13 | + |
| 14 | + 'htmlforms': { |
| 15 | + 'selectOrOtherSelectChanged': function( e ) { |
| 16 | + var select; |
| 17 | + if ( !e ) { |
| 18 | + e = window.event; |
| 19 | + } |
| 20 | + if ( e.target ) { |
| 21 | + select = e.target; |
| 22 | + } else if ( e.srcElement ) { |
| 23 | + select = e.srcElement; |
| 24 | + } |
| 25 | + // Defeat Safari bug |
| 26 | + if ( select.nodeType == 3 ) { |
| 27 | + select = select.parentNode; |
| 28 | + } |
| 29 | + var id = select.id; |
| 30 | + var textbox = document.getElementById( id + '-other' ); |
| 31 | + if ( select.value == 'other' ) { |
| 32 | + textbox.disabled = false; |
| 33 | + } else { |
| 34 | + textbox.disabled = true; |
| 35 | + } |
| 36 | + } |
| 37 | + } |
12 | 38 | } ); |
13 | 39 | |
14 | 40 | /* Initialization */ |
15 | 41 | |
16 | 42 | $( document ).ready( function() { |
17 | | - // |
| 43 | + // Find select-or-other fields |
| 44 | + $( 'select .mw-htmlform-select-or-other' ).each( function() { |
| 45 | + $(this).change( function() { mw.legacy.htmlforms.selectOrOtherSelectChanged(); } ); |
| 46 | + // Use a fake event to update it. |
| 47 | + mw.legacy.htmlforms.selectOrOtherSelectChanged( { 'target': $(this).get( 0 ) } ); |
| 48 | + } ); |
18 | 49 | } ); |
19 | 50 | |
20 | 51 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.rightclickedit.js |
— | — | @@ -7,13 +7,60 @@ |
8 | 8 | /* Extension */ |
9 | 9 | |
10 | 10 | $.extend( mw.legacy, { |
11 | | - // |
| 11 | + |
| 12 | + /* Functions */ |
| 13 | + |
| 14 | + 'setupRightClickEdit': function() { |
| 15 | + if (document.getElementsByTagName) { |
| 16 | + var spans = document.getElementsByTagName('span'); |
| 17 | + for (var i = 0; i < spans.length; i++) { |
| 18 | + var el = spans[i]; |
| 19 | + if(el.className == 'editsection') { |
| 20 | + mw.legacy.addRightClickEditHandler(el); |
| 21 | + } |
| 22 | + } |
| 23 | + } |
| 24 | + } |
| 25 | + 'addRightClickEditHandler': function(el) { |
| 26 | + for (var i = 0; i < el.childNodes.length; i++) { |
| 27 | + var link = el.childNodes[i]; |
| 28 | + if (link.nodeType == 1 && link.nodeName.toLowerCase() == 'a') { |
| 29 | + var editHref = link.getAttribute('href'); |
| 30 | + // find the enclosing (parent) header |
| 31 | + var prev = el.parentNode; |
| 32 | + if (prev && prev.nodeType == 1 && |
| 33 | + prev.nodeName.match(/^[Hh][1-6]$/)) { |
| 34 | + prev.oncontextmenu = function(e) { |
| 35 | + if (!e) { e = window.event; } |
| 36 | + // e is now the event in all browsers |
| 37 | + var targ; |
| 38 | + if (e.target) { targ = e.target; } |
| 39 | + else if (e.srcElement) { targ = e.srcElement; } |
| 40 | + if (targ.nodeType == 3) { // defeat Safari bug |
| 41 | + targ = targ.parentNode; |
| 42 | + } |
| 43 | + // targ is now the target element |
| 44 | + |
| 45 | + // We don't want to deprive the noble reader of a context menu |
| 46 | + // for the section edit link, do we? (Might want to extend this |
| 47 | + // to all <a>'s?) |
| 48 | + if (targ.nodeName.toLowerCase() != 'a' |
| 49 | + || targ.parentNode.className != 'editsection') { |
| 50 | + document.location = editHref; |
| 51 | + return false; |
| 52 | + } |
| 53 | + return true; |
| 54 | + }; |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | + } |
12 | 59 | } ); |
13 | 60 | |
14 | 61 | /* Initialization */ |
15 | 62 | |
16 | 63 | $( document ).ready( function() { |
17 | | - // |
| 64 | + mw.legacy.setupRightClickEdit(); |
18 | 65 | } ); |
19 | 66 | |
20 | 67 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.IEFixes.js |
— | — | @@ -1,19 +1,141 @@ |
2 | 2 | /* |
3 | 3 | * Legacy emulation for the now depricated skins/common/IEFixes.js |
| 4 | + * |
| 5 | + * Internet Explorer JavaScript fixes |
4 | 6 | */ |
5 | 7 | |
6 | 8 | ( function( $, mw ) { |
7 | 9 | |
| 10 | +/* Support */ |
| 11 | + |
| 12 | +/** |
| 13 | + * Expand links for printing |
| 14 | + */ |
| 15 | +String.prototype.hasClass = function( classWanted ) { |
| 16 | + var classArr = this.split(/\s/); |
| 17 | + for ( var i = 0; i < classArr.length; i++ ) { |
| 18 | + if ( classArr[i].toLowerCase() == classWanted.toLowerCase() ) { |
| 19 | + return true; |
| 20 | + } |
| 21 | + } |
| 22 | + return false; |
| 23 | +} |
| 24 | + |
8 | 25 | /* Extension */ |
9 | 26 | |
10 | 27 | $.extend( mw.legacy, { |
11 | | - // |
| 28 | + |
| 29 | + /* Global Variables */ |
| 30 | + |
| 31 | + 'isMSIE55': ( window.showModalDialog && window.clipboardData && window.createPopup ), |
| 32 | + 'doneIETransform': null, |
| 33 | + 'doneIEAlphaFix': null, |
| 34 | + 'expandedURLs': null, |
| 35 | + |
| 36 | + /* Functions */ |
| 37 | + |
| 38 | + 'hookit': function() { |
| 39 | + if ( !doneIETransform && document.getElementById && document.getElementById( 'bodyContent' ) ) { |
| 40 | + doneIETransform = true; |
| 41 | + relativeforfloats(); |
| 42 | + fixalpha(); |
| 43 | + } |
| 44 | + }, |
| 45 | + /** |
| 46 | + * Fixes PNG alpha transparency |
| 47 | + */ |
| 48 | + function fixalpha( logoId ) { |
| 49 | + // bg |
| 50 | + if ( isMSIE55 && !doneIEAlphaFix ) { |
| 51 | + var plogo = document.getElementById( logoId || 'p-logo' ); |
| 52 | + if ( !plogo ) { |
| 53 | + return; |
| 54 | + } |
| 55 | + var logoa = plogo.getElementsByTagName('a')[0]; |
| 56 | + if ( !logoa ) { |
| 57 | + return; |
| 58 | + } |
| 59 | + var bg = logoa.currentStyle.backgroundImage; |
| 60 | + var imageUrl = bg.substring( 5, bg.length - 2 ); |
| 61 | + doneIEAlphaFix = true; |
| 62 | + if ( imageUrl.substr( imageUrl.length - 4 ).toLowerCase() == '.png' ) { |
| 63 | + var logospan = logoa.appendChild( document.createElement( 'span' ) ); |
| 64 | + logoa.style.backgroundImage = 'none'; |
| 65 | + logospan.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=' + imageUrl + ')'; |
| 66 | + logospan.style.height = '100%'; |
| 67 | + logospan.style.position = 'absolute'; |
| 68 | + logospan.style.width = logoa.currentStyle.width; |
| 69 | + logospan.style.cursor = 'hand'; |
| 70 | + // Center image with hack for IE5.5 |
| 71 | + if ( document.documentElement.dir == 'rtl' ) { |
| 72 | + logospan.style.right = '50%'; |
| 73 | + logospan.style.setExpression( 'marginRight', '"-" + (this.offsetWidth / 2) + "px"' ); |
| 74 | + } else { |
| 75 | + logospan.style.left = '50%'; |
| 76 | + logospan.style.setExpression( 'marginLeft', '"-" + (this.offsetWidth / 2) + "px"' ); |
| 77 | + } |
| 78 | + logospan.style.top = '50%'; |
| 79 | + logospan.style.setExpression( 'marginTop', '"-" + (this.offsetHeight / 2) + "px"' ); |
| 80 | + var linkFix = logoa.appendChild( logoa.cloneNode() ); |
| 81 | + linkFix.style.position = 'absolute'; |
| 82 | + linkFix.style.height = '100%'; |
| 83 | + linkFix.style.width = '100%'; |
| 84 | + } |
| 85 | + } |
| 86 | + }, |
| 87 | + /* |
| 88 | + * Fixes IE6 disappering float bug |
| 89 | + */ |
| 90 | + 'relativeforfloats': function() { |
| 91 | + var bc = document.getElementById( 'bodyContent' ); |
| 92 | + if ( bc ) { |
| 93 | + var tables = bc.getElementsByTagName( 'table' ); |
| 94 | + var divs = bc.getElementsByTagName( 'div' ); |
| 95 | + } |
| 96 | + setrelative( tables ); |
| 97 | + setrelative( divs ); |
| 98 | + }, |
| 99 | + 'setrelative': function ( nodes ) { |
| 100 | + var i = 0; |
| 101 | + while ( i < nodes.length ) { |
| 102 | + if( ( ( nodes[i].style.float && nodes[i].style.float != ( 'none' ) || |
| 103 | + ( nodes[i].align && nodes[i].align != ( 'none' ) ) ) && |
| 104 | + ( !nodes[i].style.position || nodes[i].style.position != 'relative' ) ) ) |
| 105 | + { |
| 106 | + nodes[i].style.position = 'relative'; |
| 107 | + } |
| 108 | + i++; |
| 109 | + } |
| 110 | + }, |
| 111 | + 'onbeforeprint': function() { |
| 112 | + expandedURLs = []; |
| 113 | + var contentEl = document.getElementById( 'content' ); |
| 114 | + if ( contentEl ) { |
| 115 | + var allLinks = contentEl.getElementsByTagName( 'a' ); |
| 116 | + for ( var i = 0; i < allLinks.length; i++ ) { |
| 117 | + if ( allLinks[i].className.hasClass( 'external' ) && !allLinks[i].className.hasClass( 'free' ) ) { |
| 118 | + var expandedLink = document.createElement( 'span' ); |
| 119 | + var expandedText = document.createTextNode( ' (' + allLinks[i].href + ')' ); |
| 120 | + expandedLink.appendChild( expandedText ); |
| 121 | + allLinks[i].parentNode.insertBefore( expandedLink, allLinks[i].nextSibling ); |
| 122 | + expandedURLs[i] = expandedLink; |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + }, |
| 127 | + 'onafterprint': function() { |
| 128 | + for ( var i = 0; i < expandedURLs.length; i++ ) { |
| 129 | + if ( expandedURLs[i] ) { |
| 130 | + expandedURLs[i].removeNode( true ); |
| 131 | + } |
| 132 | + } |
| 133 | + } |
12 | 134 | } ); |
13 | 135 | |
14 | 136 | /* Initialization */ |
15 | 137 | |
16 | 138 | $( document ).ready( function() { |
17 | | - // |
| 139 | + mw.legacy.hookit(); |
18 | 140 | } ); |
19 | 141 | |
20 | 142 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |
Index: branches/resourceloader/phase3/resources/mw/legacy/mw.legacy.search.js |
— | — | @@ -1,5 +1,7 @@ |
2 | 2 | /* |
3 | 3 | * Legacy emulation for the now depricated skins/common/search.js |
| 4 | + * |
| 5 | + * Progressive enhancement for Special:Search |
4 | 6 | */ |
5 | 7 | |
6 | 8 | ( function( $, mw ) { |
— | — | @@ -7,13 +9,55 @@ |
8 | 10 | /* Extension */ |
9 | 11 | |
10 | 12 | $.extend( mw.legacy, { |
11 | | - // |
| 13 | + |
| 14 | + /* Functions */ |
| 15 | + |
| 16 | + /** |
| 17 | + * Change the search link to what user entered |
| 18 | + */ |
| 19 | + function mwSearchHeaderClick( obj ) { |
| 20 | + var searchbox = document.getElementById( 'searchText' ); |
| 21 | + if( searchbox === null ) { |
| 22 | + searchbox = document.getElementById( 'powerSearchText' ); |
| 23 | + } |
| 24 | + if( searchbox === null ) { |
| 25 | + return; // should always have either normal or advanced search |
| 26 | + } |
| 27 | + var searchterm = searchbox.value; |
| 28 | + var parts = obj.href.split( 'search=' ); |
| 29 | + var lastpart = ''; |
| 30 | + var prefix = 'search='; |
| 31 | + if( parts.length > 1 && parts[1].indexOf('&') >= 0 ) { |
| 32 | + lastpart = parts[1].substring( parts[1].indexOf('&') ); |
| 33 | + } else { |
| 34 | + prefix = '&search='; |
| 35 | + } |
| 36 | + obj.href = parts[0] + prefix + encodeURIComponent( searchterm ) + lastpart; |
| 37 | + } |
| 38 | + function mwToggleSearchCheckboxes( btn ) { |
| 39 | + if( !document.getElementById ) { |
| 40 | + return; |
| 41 | + } |
| 42 | + var nsInputs = document.getElementById( 'powersearch' ).getElementsByTagName( 'input' ); |
| 43 | + var isChecked = false; |
| 44 | + for ( var i = 0; i < nsInputs.length; i++ ) { |
| 45 | + var pattern = /^ns/; |
| 46 | + if ( ( nsInputs[i].type == 'checkbox' ) && ( pattern.test( nsInputs[i].name ) ) ) { |
| 47 | + switch ( btn ) { |
| 48 | + case 'none': |
| 49 | + if ( nsInputs[i].checked ) { |
| 50 | + nsInputs[i].checked = false; |
| 51 | + } |
| 52 | + break; |
| 53 | + case 'all': |
| 54 | + if ( !nsInputs[i].checked ) { |
| 55 | + nsInputs[i].checked = true; |
| 56 | + } |
| 57 | + break; |
| 58 | + } |
| 59 | + } |
| 60 | + } |
| 61 | + } |
12 | 62 | } ); |
13 | 63 | |
14 | | -/* Initialization */ |
15 | | - |
16 | | -$( document ).ready( function() { |
17 | | - // |
18 | | -} ); |
19 | | - |
20 | 64 | } )( jQuery, MediaWiki ); |
\ No newline at end of file |