Index: trunk/extensions/MobileFrontend2/MobileFrontend2.php |
— | — | @@ -37,6 +37,8 @@ |
38 | 38 | $wgHooks['RequestContextCreateSkin'][] = 'MobileFrontend2_Hooks::createSkin'; |
39 | 39 | $wgHooks['ParserSectionCreate'][] = 'MobileFrontend2_Hooks::parserSectionCreate'; |
40 | 40 | $wgHooks['ArticleViewHeader'][] = 'MobileFrontend2_Hooks::articleView'; |
| 41 | +$wgHooks['ResourceLoaderGetStartupModules'][] = 'MobileFrontend2_Hooks::startupModule'; |
| 42 | +$wgHooks['ResourceLoaderRegisterModules'][] = 'MobileFrontend2_Hooks::registerModules'; |
41 | 43 | $wgExtensionFunctions[] = 'MobileFrontend2_Hooks::setup'; |
42 | 44 | |
43 | 45 | // Modules |
— | — | @@ -52,12 +54,23 @@ |
53 | 55 | 'mobile-frontend2-show-button', |
54 | 56 | 'mobile-frontend2-hide-button', |
55 | 57 | ), |
| 58 | + 'dependencies' => array( |
| 59 | + 'mediawiki.util.lite', |
| 60 | + 'mediawiki.api.lite', |
| 61 | + ), |
56 | 62 | ) + $commonModuleInfo; |
57 | 63 | |
58 | 64 | $wgResourceModules['ext.mobileFrontend2.common'] = array( |
59 | 65 | 'styles' => 'ext.mobileFrontend2/ext.mobileFrontend2.css', |
60 | 66 | ) + $commonModuleInfo; |
61 | 67 | |
| 68 | +$wgResourceModules['zepto'] = array( |
| 69 | + 'scripts' => array( |
| 70 | + 'zepto/zepto.js', |
| 71 | + 'zepto/zepto.mw.js', |
| 72 | + ), |
| 73 | +) + $commonModuleInfo; |
| 74 | + |
62 | 75 | // Config |
63 | 76 | /** |
64 | 77 | * Logo used on MobileFrontend2 |
Index: trunk/extensions/MobileFrontend2/MobileFrontend2_Hooks.php |
— | — | @@ -65,9 +65,72 @@ |
66 | 66 | } |
67 | 67 | |
68 | 68 | /** |
| 69 | + * Replaces jQuery with zepto.js for mobile |
| 70 | + * |
| 71 | + * @param $modules |
| 72 | + * @return bool |
| 73 | + */ |
| 74 | + public static function startupModule( &$modules ) { |
| 75 | + // comment about this |
| 76 | + if ( self::isMobileSkin() ) { |
| 77 | + $modules = array( |
| 78 | + 'zepto', |
| 79 | + 'mediawiki', |
| 80 | + ); |
| 81 | + return false; |
| 82 | + } |
| 83 | + |
| 84 | + return true; |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Registers lite versions of core modules for mobile |
| 89 | + * |
| 90 | + * @param ResourceLoader $resourceLoader |
| 91 | + * @return bool |
| 92 | + */ |
| 93 | + public static function registerModules( ResourceLoader &$resourceLoader ) { |
| 94 | + if ( self::isMobileSkin() ) { |
| 95 | + global $wgResourceModules; |
| 96 | + |
| 97 | + // We need to remove dependencies from mw.util that will don't use and |
| 98 | + // aren't compatible with zepto.js |
| 99 | + // Krinkle will hate me |
| 100 | + // TODO: This only saves about 4KB, reevaluate later |
| 101 | + $wgResourceModules['mediawiki.util.lite'] = array( |
| 102 | + 'scripts' => 'resources/mediawiki/mediawiki.util.js', |
| 103 | + /*'dependencies' => array( |
| 104 | + 'jquery.client', |
| 105 | + 'jquery.cookie', |
| 106 | + 'jquery.messageBox', |
| 107 | + 'jquery.mwExtension', |
| 108 | + ),*/ |
| 109 | + //'messages' => array( 'showtoc', 'hidetoc' ), |
| 110 | + 'position' => 'top', // For $wgPreloadJavaScriptMwUtil |
| 111 | + ); |
| 112 | + $wgResourceModules['mediawiki.api.lite'] = array( |
| 113 | + 'scripts' => 'resources/mediawiki/mediawiki.api.js', |
| 114 | + 'dependencies' => 'mediawiki.util.lite', |
| 115 | + ); |
| 116 | + } |
| 117 | + |
| 118 | + return true; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Checks if the skin parameter is for a mobile skin |
| 123 | + * |
| 124 | + * This only works for load.php |
| 125 | + * |
| 126 | + * @return bool |
| 127 | + */ |
| 128 | + protected static function isMobileSkin() { |
| 129 | + return RequestContext::getMain()->getRequest()->getVal( 'skin' ) == 'mobile'; |
| 130 | + } |
| 131 | + |
| 132 | + /** |
69 | 133 | * Perform very early setup |
70 | 134 | * |
71 | | - * This implements the parser if we're going to use the frontend |
72 | 135 | * @return bool |
73 | 136 | */ |
74 | 137 | public static function setup() { |
Index: trunk/extensions/MobileFrontend2/skins/Mobile.php |
— | — | @@ -69,7 +69,7 @@ |
70 | 70 | |
71 | 71 | // CSS & JS |
72 | 72 | // Make these last |
73 | | - $tpl->set( 'headscripts', $out->getHeadScripts() ); |
| 73 | + $tpl->set( 'headscripts', $this->getHeadScripts( $out ) ); |
74 | 74 | $tpl->set( 'csslinks', $out->buildCssLinks() ); |
75 | 75 | $tpl->set( 'bottomscripts', $this->bottomScripts() ); |
76 | 76 | |
— | — | @@ -105,6 +105,24 @@ |
106 | 106 | public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) { |
107 | 107 | return '<button>' . wfMessage( 'mobile-frontend2-show-button' )->escaped() . '</button>'; |
108 | 108 | } |
| 109 | + |
| 110 | + /** |
| 111 | + * More minimal version of getHeadScripts from OutputPage |
| 112 | + * |
| 113 | + * @param OutputPage $out |
| 114 | + * @return string |
| 115 | + */ |
| 116 | + protected function getHeadScripts( OutputPage $out ) { |
| 117 | + $scripts = $out->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true, array( 'mobile' => true ) ); |
| 118 | + |
| 119 | + $scripts .= Html::inlineScript( |
| 120 | + ResourceLoader::makeLoaderConditionalScript( |
| 121 | + ResourceLoader::makeConfigSetScript( $out->getJSVars() ) |
| 122 | + ) |
| 123 | + ); |
| 124 | + |
| 125 | + return $scripts; |
| 126 | + } |
109 | 127 | } |
110 | 128 | |
111 | 129 | class MobileTemplate extends BaseTemplate { |
— | — | @@ -129,8 +147,10 @@ |
130 | 148 | <?php $this->html( 'headscripts' ) ?> |
131 | 149 | </head> |
132 | 150 | <body> |
| 151 | + |
133 | 152 | <?php if ( !MobileFrontend2_Options::getHideSearch() ): ?> |
134 | 153 | <!-- search/header --> |
| 154 | + <div id="results"></div> |
135 | 155 | <div id="header"> |
136 | 156 | <div id="searchbox"> |
137 | 157 | <?php if ( !MobileFrontend2_Options::getHideLogo() ): ?> |
Index: trunk/extensions/MobileFrontend2/modules/zepto/zepto.mw.js |
— | — | @@ -0,0 +1,46 @@ |
| 2 | +// Alias jQuery to Zepto (ew) |
| 3 | +window.jQuery = window.Zepto; |
| 4 | + |
| 5 | +// Add a few things missing from jQuery |
| 6 | +(function ( $ ){ |
| 7 | + $.isPlainObject = function( obj ) { |
| 8 | + // Must be an Object. |
| 9 | + // Because of IE, we also have to check the presence of the constructor property. |
| 10 | + // Make sure that DOM nodes and window objects don't pass through, as well |
| 11 | + if ( !obj || $.isObject() || obj.nodeType || jQuery.isWindow( obj ) ) { |
| 12 | + return false; |
| 13 | + } |
| 14 | + |
| 15 | + var hasOwn = Object.prototype.hasOwnProperty; |
| 16 | + try { |
| 17 | + // Not own constructor property must be Object |
| 18 | + if ( obj.constructor && |
| 19 | + !hasOwn.call(obj, "constructor") && |
| 20 | + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { |
| 21 | + return false; |
| 22 | + } |
| 23 | + } catch ( e ) { |
| 24 | + // IE8,9 Will throw exceptions on certain host objects #9897 |
| 25 | + return false; |
| 26 | + } |
| 27 | + |
| 28 | + // Own properties are enumerated firstly, so to speed up, |
| 29 | + // if last one is own, then all properties are own. |
| 30 | + |
| 31 | + var key; |
| 32 | + for ( key in obj ) {} |
| 33 | + |
| 34 | + return key === undefined || hasOwn.call( obj, key ); |
| 35 | + }; |
| 36 | + |
| 37 | + $.isWindow = function( obj ) { |
| 38 | + return obj && typeof obj === "object" && "setInterval" in obj; |
| 39 | + }; |
| 40 | + |
| 41 | + $.isEmptyObject = function( obj ) { |
| 42 | + for ( var name in obj ) { |
| 43 | + return false; |
| 44 | + } |
| 45 | + return true; |
| 46 | + }; |
| 47 | +})( Zepto ); |
\ No newline at end of file |
Property changes on: trunk/extensions/MobileFrontend2/modules/zepto/zepto.mw.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 48 | + native |
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.js |
— | — | @@ -1,9 +1,39 @@ |
| 2 | +(function( $, mw, undefined ) { |
| 3 | + |
2 | 4 | var MobileFrontend2 = mf2 = { |
| 5 | + /** |
| 6 | + * Timer for updating search suggestions |
| 7 | + */ |
| 8 | + searchTimer: undefined, |
| 9 | + |
| 10 | + /** |
| 11 | + * mw.Api object for accessing the API |
| 12 | + * |
| 13 | + * @var api {mw.Api} |
| 14 | + */ |
| 15 | + api: undefined, |
| 16 | + |
| 17 | + /** |
| 18 | + * Run to set up the page |
| 19 | + */ |
3 | 20 | init: function() { |
| 21 | + // Create our API object |
| 22 | + mf2.api = new mw.Api(); |
| 23 | + |
4 | 24 | // Hook the section toggle |
5 | 25 | $( '.mf2-section-container h2' ).click( mf2.toggleSection ); |
| 26 | + |
| 27 | + // Listen for searches |
| 28 | + $( '#search' ).keyup( mf2.searchKeyUp ); |
| 29 | + |
| 30 | + // Setup the width for search |
| 31 | + // TODO: Only worry about this if we have the search element |
| 32 | + mf2.updateSearchWidth(); |
6 | 33 | }, |
7 | 34 | |
| 35 | + /** |
| 36 | + * Toggles page section visibility |
| 37 | + */ |
8 | 38 | toggleSection: function() { |
9 | 39 | var $header = $( this ), |
10 | 40 | $contentDiv = $header.next(), |
— | — | @@ -15,7 +45,111 @@ |
16 | 46 | // Change the button text |
17 | 47 | buttonMsg = $contentDiv.css( 'display' ) === 'block' ? 'mobile-frontend2-hide-button' : 'mobile-frontend2-show-button'; |
18 | 48 | $header.find( 'button' ).html( mw.msg( buttonMsg ) ); |
| 49 | + }, |
| 50 | + |
| 51 | + /** |
| 52 | + * Schedules an update of search suggestions |
| 53 | + * |
| 54 | + * Fired when data is entered into the search box |
| 55 | + */ |
| 56 | + searchKeyUp: function() { |
| 57 | + clearTimeout( mf2.searchTimer ); |
| 58 | + |
| 59 | + if ( this.value.length < 1 ) { |
| 60 | + $( '#results' ).html( '' ); |
| 61 | + } else { |
| 62 | + // TODO: Config |
| 63 | + mf2.searchTimer = setTimeout( mf2.search, 500 ); |
| 64 | + } |
| 65 | + }, |
| 66 | + |
| 67 | + /** |
| 68 | + * Fires off the request to the API to get search results |
| 69 | + */ |
| 70 | + search: function() { |
| 71 | + mf2.api.get( { |
| 72 | + action: 'opensearch', |
| 73 | + limit: 5, |
| 74 | + namespace: 0, |
| 75 | + search: $( '#search' ).val() |
| 76 | + }, mf2.searchResults ); |
| 77 | + }, |
| 78 | + |
| 79 | + /** |
| 80 | + * Update the search suggestions with API results |
| 81 | + * |
| 82 | + * @param data |
| 83 | + */ |
| 84 | + searchResults: function( data ) { |
| 85 | + var results = data[1], // Second element has the results, fuck standards |
| 86 | + $results = $( '#results' ); |
| 87 | + |
| 88 | + $results.show(); |
| 89 | + if ( results.length < 1 ) { |
| 90 | + $results.text( 'No results' ); // TRANSLATE |
| 91 | + return; |
| 92 | + } |
| 93 | + |
| 94 | + $r = $( '<div class="suggestions-results">' ); |
| 95 | + |
| 96 | + $.each( results, function( i, title ) { |
| 97 | + $( '<div class="suggestions-result">' ) |
| 98 | + .attr( 'title', title ) |
| 99 | + .attr( 'rel', i + 1 ) // ? |
| 100 | + .append( |
| 101 | + $( '<a class="sq-val-update">' ) |
| 102 | + .text( '+' ) // Translate? |
| 103 | + .click( mf2.updateSearchValue ) |
| 104 | + ) |
| 105 | + .append( |
| 106 | + $( '<a class="search-result-item">' ) |
| 107 | + .attr( 'href', mw.util.wikiGetlink( title ) ) |
| 108 | + .text( title ) |
| 109 | + ) |
| 110 | + .appendTo( $r ); |
| 111 | + } ); |
| 112 | + |
| 113 | + $results.html( $r ); |
| 114 | + }, |
| 115 | + |
| 116 | + /** |
| 117 | + * Update the value of the search box with the selected suggestion |
| 118 | + * |
| 119 | + * Fire when the plus symbol is clicked |
| 120 | + */ |
| 121 | + updateSearchValue: function () { |
| 122 | + var title = $( this ).parent().attr( 'title' ); |
| 123 | + |
| 124 | + $( '#search' ) |
| 125 | + .val( title + ' ' ) |
| 126 | + .focus(); |
| 127 | + |
| 128 | + // Reload suggestions |
| 129 | + mf2.search(); |
| 130 | + }, |
| 131 | + |
| 132 | + /** |
| 133 | + * Updates the width of the header and search box |
| 134 | + * |
| 135 | + * Fired when the screen orientation changes |
| 136 | + */ |
| 137 | + updateSearchWidth: function () { |
| 138 | + var clientWidth = $( window ).width(), |
| 139 | + $sq = $( '#sq' ), |
| 140 | + sqOffset = $sq.offset(); |
| 141 | + |
| 142 | + // TODO: ew. This should be CSS |
| 143 | + $( '#searchbox' ).width( clientWidth - 30 ); |
| 144 | + $sq.width( clientWidth - 110 ); |
| 145 | + $( '#search' ).width( clientWidth - 130 ); |
| 146 | + $( '#results' ).css({ |
| 147 | + width: $sq.width() - 2, |
| 148 | + left: sqOffset.left, |
| 149 | + top: sqOffset.top + $sq.height() |
| 150 | + }); |
19 | 151 | } |
20 | 152 | }; |
21 | 153 | |
22 | | -$( mf2.init ); |
\ No newline at end of file |
| 154 | +$( mf2.init ); |
| 155 | + |
| 156 | +})( Zepto, MediaWiki ); |
\ No newline at end of file |
Index: trunk/extensions/MobileFrontend2/MobileFrontend2_Detection.php |
— | — | @@ -48,6 +48,8 @@ |
49 | 49 | * @return bool |
50 | 50 | */ |
51 | 51 | private static function enable() { |
| 52 | + global $wgResourceModules; |
| 53 | + |
52 | 54 | self::$enabled = true; |
53 | 55 | |
54 | 56 | // Do some initialization |