From 3801e54c29a65182b2b82137810c815d94b53704 Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Thu, 7 Jun 2018 03:54:24 -0700 Subject: [PATCH] resourceloader: move hook() and html() from mediawiki.js to base module Bug: T192623 Change-Id: I9c2ff828b663fdc1b0345b72c6c45289d2e2a7cf --- resources/src/mediawiki/mediawiki.base.js | 227 +++++++++++++++++++++- resources/src/mediawiki/mediawiki.js | 226 +-------------------- 2 files changed, 227 insertions(+), 226 deletions(-) diff --git a/resources/src/mediawiki/mediawiki.base.js b/resources/src/mediawiki/mediawiki.base.js index 89b3902aa6..9683570c99 100644 --- a/resources/src/mediawiki/mediawiki.base.js +++ b/resources/src/mediawiki/mediawiki.base.js @@ -21,7 +21,8 @@ var slice = Array.prototype.slice, mwLoaderTrack = mw.track, trackCallbacks = $.Callbacks( 'memory' ), - trackHandlers = []; + trackHandlers = [], + hasOwn = Object.prototype.hasOwnProperty; /** * Object constructor for messages. @@ -357,4 +358,228 @@ // Fire events from before track() triggred fire() trackCallbacks.fire( mw.trackQueue ); + + /** + * Registry and firing of events. + * + * MediaWiki has various interface components that are extended, enhanced + * or manipulated in some other way by extensions, gadgets and even + * in core itself. + * + * This framework helps streamlining the timing of when these other + * code paths fire their plugins (instead of using document-ready, + * which can and should be limited to firing only once). + * + * Features like navigating to other wiki pages, previewing an edit + * and editing itself – without a refresh – can then retrigger these + * hooks accordingly to ensure everything still works as expected. + * + * Example usage: + * + * mw.hook( 'wikipage.content' ).add( fn ).remove( fn ); + * mw.hook( 'wikipage.content' ).fire( $content ); + * + * Handlers can be added and fired for arbitrary event names at any time. The same + * event can be fired multiple times. The last run of an event is memorized + * (similar to `$(document).ready` and `$.Deferred().done`). + * This means if an event is fired, and a handler added afterwards, the added + * function will be fired right away with the last given event data. + * + * Like Deferreds and Promises, the mw.hook object is both detachable and chainable. + * Thus allowing flexible use and optimal maintainability and authority control. + * You can pass around the `add` and/or `fire` method to another piece of code + * without it having to know the event name (or `mw.hook` for that matter). + * + * var h = mw.hook( 'bar.ready' ); + * new mw.Foo( .. ).fetch( { callback: h.fire } ); + * + * Note: Events are documented with an underscore instead of a dot in the event + * name due to jsduck not supporting dots in that position. + * + * @class mw.hook + */ + mw.hook = ( function () { + var lists = {}; + + /** + * Create an instance of mw.hook. + * + * @method hook + * @member mw + * @param {string} name Name of hook. + * @return {mw.hook} + */ + return function ( name ) { + var list = hasOwn.call( lists, name ) ? + lists[ name ] : + lists[ name ] = $.Callbacks( 'memory' ); + + return { + /** + * Register a hook handler + * + * @param {...Function} handler Function to bind. + * @chainable + */ + add: list.add, + + /** + * Unregister a hook handler + * + * @param {...Function} handler Function to unbind. + * @chainable + */ + remove: list.remove, + + /** + * Run a hook. + * + * @param {...Mixed} data + * @return {mw.hook} + * @chainable + */ + fire: function () { + return list.fireWith.call( this, null, slice.call( arguments ) ); + } + }; + }; + }() ); + + /** + * HTML construction helper functions + * + * @example + * + * var Html, output; + * + * Html = mw.html; + * output = Html.element( 'div', {}, new Html.Raw( + * Html.element( 'img', { src: '<' } ) + * ) ); + * mw.log( output ); //
+ * + * @class mw.html + * @singleton + */ + mw.html = ( function () { + function escapeCallback( s ) { + switch ( s ) { + case '\'': + return '''; + case '"': + return '"'; + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; + } + } + + return { + /** + * Escape a string for HTML. + * + * Converts special characters to HTML entities. + * + * mw.html.escape( '< > \' & "' ); + * // Returns < > ' & " + * + * @param {string} s The string to escape + * @return {string} HTML + */ + escape: function ( s ) { + return s.replace( /['"<>&]/g, escapeCallback ); + }, + + /** + * Create an HTML element string, with safe escaping. + * + * @param {string} name The tag name. + * @param {Object} [attrs] An object with members mapping element names to values + * @param {string|mw.html.Raw|mw.html.Cdata|null} [contents=null] The contents of the element. + * + * - string: Text to be escaped. + * - null: The element is treated as void with short closing form, e.g. `
`. + * - this.Raw: The raw value is directly included. + * - this.Cdata: The raw value is directly included. An exception is + * thrown if it contains any illegal ETAGO delimiter. + * See . + * @return {string} HTML + */ + element: function ( name, attrs, contents ) { + var v, attrName, s = '<' + name; + + if ( attrs ) { + for ( attrName in attrs ) { + v = attrs[ attrName ]; + // Convert name=true, to name=name + if ( v === true ) { + v = attrName; + // Skip name=false + } else if ( v === false ) { + continue; + } + s += ' ' + attrName + '="' + this.escape( String( v ) ) + '"'; + } + } + if ( contents === undefined || contents === null ) { + // Self close tag + s += '/>'; + return s; + } + // Regular open tag + s += '>'; + switch ( typeof contents ) { + case 'string': + // Escaped + s += this.escape( contents ); + break; + case 'number': + case 'boolean': + // Convert to string + s += String( contents ); + break; + default: + if ( contents instanceof this.Raw ) { + // Raw HTML inclusion + s += contents.value; + } else if ( contents instanceof this.Cdata ) { + // CDATA + if ( /<\/[a-zA-z]/.test( contents.value ) ) { + throw new Error( 'mw.html.element: Illegal end tag found in CDATA' ); + } + s += contents.value; + } else { + throw new Error( 'mw.html.element: Invalid type of contents' ); + } + } + s += ''; + return s; + }, + + /** + * Wrapper object for raw HTML passed to mw.html.element(). + * + * @class mw.html.Raw + * @constructor + * @param {string} value + */ + Raw: function ( value ) { + this.value = value; + }, + + /** + * Wrapper object for CDATA element contents passed to mw.html.element() + * + * @class mw.html.Cdata + * @constructor + * @param {string} value + */ + Cdata: function ( value ) { + this.value = value; + } + }; + }() ); }() ); diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 4ce6ff86fb..7d40ce745c 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -13,7 +13,6 @@ var mw, StringSet, log, hasOwn = Object.prototype.hasOwnProperty, - slice = Array.prototype.slice, trackQueue = []; /** @@ -2282,144 +2281,6 @@ }; }() ), - /** - * HTML construction helper functions - * - * @example - * - * var Html, output; - * - * Html = mw.html; - * output = Html.element( 'div', {}, new Html.Raw( - * Html.element( 'img', { src: '<' } ) - * ) ); - * mw.log( output ); //
- * - * @class mw.html - * @singleton - */ - html: ( function () { - function escapeCallback( s ) { - switch ( s ) { - case '\'': - return '''; - case '"': - return '"'; - case '<': - return '<'; - case '>': - return '>'; - case '&': - return '&'; - } - } - - return { - /** - * Escape a string for HTML. - * - * Converts special characters to HTML entities. - * - * mw.html.escape( '< > \' & "' ); - * // Returns < > ' & " - * - * @param {string} s The string to escape - * @return {string} HTML - */ - escape: function ( s ) { - return s.replace( /['"<>&]/g, escapeCallback ); - }, - - /** - * Create an HTML element string, with safe escaping. - * - * @param {string} name The tag name. - * @param {Object} [attrs] An object with members mapping element names to values - * @param {string|mw.html.Raw|mw.html.Cdata|null} [contents=null] The contents of the element. - * - * - string: Text to be escaped. - * - null: The element is treated as void with short closing form, e.g. `
`. - * - this.Raw: The raw value is directly included. - * - this.Cdata: The raw value is directly included. An exception is - * thrown if it contains any illegal ETAGO delimiter. - * See . - * @return {string} HTML - */ - element: function ( name, attrs, contents ) { - var v, attrName, s = '<' + name; - - if ( attrs ) { - for ( attrName in attrs ) { - v = attrs[ attrName ]; - // Convert name=true, to name=name - if ( v === true ) { - v = attrName; - // Skip name=false - } else if ( v === false ) { - continue; - } - s += ' ' + attrName + '="' + this.escape( String( v ) ) + '"'; - } - } - if ( contents === undefined || contents === null ) { - // Self close tag - s += '/>'; - return s; - } - // Regular open tag - s += '>'; - switch ( typeof contents ) { - case 'string': - // Escaped - s += this.escape( contents ); - break; - case 'number': - case 'boolean': - // Convert to string - s += String( contents ); - break; - default: - if ( contents instanceof this.Raw ) { - // Raw HTML inclusion - s += contents.value; - } else if ( contents instanceof this.Cdata ) { - // CDATA - if ( /<\/[a-zA-z]/.test( contents.value ) ) { - throw new Error( 'mw.html.element: Illegal end tag found in CDATA' ); - } - s += contents.value; - } else { - throw new Error( 'mw.html.element: Invalid type of contents' ); - } - } - s += ''; - return s; - }, - - /** - * Wrapper object for raw HTML passed to mw.html.element(). - * - * @class mw.html.Raw - * @constructor - * @param {string} value - */ - Raw: function ( value ) { - this.value = value; - }, - - /** - * Wrapper object for CDATA element contents passed to mw.html.element() - * - * @class mw.html.Cdata - * @constructor - * @param {string} value - */ - Cdata: function ( value ) { - this.value = value; - } - }; - }() ), - // Skeleton user object, extended by the 'mediawiki.user' module. /** * @class mw.user @@ -2437,93 +2298,8 @@ }, // OOUI widgets specific to MediaWiki - widgets: {}, - - /** - * Registry and firing of events. - * - * MediaWiki has various interface components that are extended, enhanced - * or manipulated in some other way by extensions, gadgets and even - * in core itself. - * - * This framework helps streamlining the timing of when these other - * code paths fire their plugins (instead of using document-ready, - * which can and should be limited to firing only once). - * - * Features like navigating to other wiki pages, previewing an edit - * and editing itself – without a refresh – can then retrigger these - * hooks accordingly to ensure everything still works as expected. - * - * Example usage: - * - * mw.hook( 'wikipage.content' ).add( fn ).remove( fn ); - * mw.hook( 'wikipage.content' ).fire( $content ); - * - * Handlers can be added and fired for arbitrary event names at any time. The same - * event can be fired multiple times. The last run of an event is memorized - * (similar to `$(document).ready` and `$.Deferred().done`). - * This means if an event is fired, and a handler added afterwards, the added - * function will be fired right away with the last given event data. - * - * Like Deferreds and Promises, the mw.hook object is both detachable and chainable. - * Thus allowing flexible use and optimal maintainability and authority control. - * You can pass around the `add` and/or `fire` method to another piece of code - * without it having to know the event name (or `mw.hook` for that matter). - * - * var h = mw.hook( 'bar.ready' ); - * new mw.Foo( .. ).fetch( { callback: h.fire } ); - * - * Note: Events are documented with an underscore instead of a dot in the event - * name due to jsduck not supporting dots in that position. - * - * @class mw.hook - */ - hook: ( function () { - var lists = {}; + widgets: {} - /** - * Create an instance of mw.hook. - * - * @method hook - * @member mw - * @param {string} name Name of hook. - * @return {mw.hook} - */ - return function ( name ) { - var list = hasOwn.call( lists, name ) ? - lists[ name ] : - lists[ name ] = $.Callbacks( 'memory' ); - - return { - /** - * Register a hook handler - * - * @param {...Function} handler Function to bind. - * @chainable - */ - add: list.add, - - /** - * Unregister a hook handler - * - * @param {...Function} handler Function to unbind. - * @chainable - */ - remove: list.remove, - - /** - * Run a hook. - * - * @param {...Mixed} data - * @return {mw.hook} - * @chainable - */ - fire: function () { - return list.fireWith.call( this, null, slice.call( arguments ) ); - } - }; - }; - }() ) }; // Alias $j to jQuery for backwards compatibility -- 2.20.1