jenkins-bot has submitted this change and it was merged. Change subject: Convert to use OOUI ......................................................................
Convert to use OOUI On the backend, we now use a simplified OOUIHTMLForm. The frontend was mostly re-written to use OOUI. Co-Authored-By: Prateek Saxena <[email protected]> Change-Id: Icecc027206484feaf08dc5247f82356eb7d45469 --- A .gitignore A .jscsrc A .jshintignore M .jshintrc A Gruntfile.js M SpecialUrlShortener.php M UrlShortener.php D js/ext.urlShortener.special.js D less/ext.urlShortener.special.less A modules/ext.urlShortener.special.js A package.json 11 files changed, 238 insertions(+), 282 deletions(-) Approvals: Yuvipanda: Looks good to me, approved jenkins-bot: Verified diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..9d22e3f --- /dev/null +++ b/.jscsrc @@ -0,0 +1,3 @@ +{ + "preset": "wikimedia" +} diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +node_modules diff --git a/.jshintrc b/.jshintrc index ca284e1..66e3d48 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,18 +1,24 @@ { - "globals": { - "console": true, - "require": true, - "module": true, - "marshaller": true - }, - - "browser": true, - "curly": true, + // Enforcing + "bitwise": true, "eqeqeq": true, - "forin": false, - "onevar": false, - "trailing": true, - "undef" : true, + "freeze": true, + "latedef": true, + "noarg": true, + "nonew": true, + "undef": true, "unused": true, - "supernew": true + "strict": false, + + // Relaxing + "es5": false, + + // Environment + "browser": true, + "jquery": true, + + "globals": { + "mediaWiki": false, + "OO": false + } } diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..c2a64cc --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,44 @@ +/*jshint node:true */ +module.exports = function ( grunt ) { + grunt.loadNpmTasks( 'grunt-contrib-jshint' ); + grunt.loadNpmTasks( 'grunt-jsonlint' ); + grunt.loadNpmTasks( 'grunt-banana-checker' ); + grunt.loadNpmTasks( 'grunt-jscs' ); + grunt.loadNpmTasks( 'grunt-tyops' ); + + grunt.initConfig( { + tyops: { + options: { + typos: 'build/typos.json' + }, + src: [ + '**/*', + '!{node_modules,vendor}/**', + '!build/typos.json' + ] + }, + jshint: { + options: { + jshintrc: true + }, + all: [ + 'modules/**/*.js' + ] + }, + jscs: { + src: '<%= jshint.all %>' + }, + banana: { + all: [ 'i18n/' ] + }, + jsonlint: { + all: [ + '**/*.json', + '!node_modules/**' + ] + } + } ); + + grunt.registerTask( 'test', [ 'tyops', 'jshint', 'jscs', 'jsonlint', 'banana' ] ); + grunt.registerTask( 'default', 'test' ); +}; diff --git a/SpecialUrlShortener.php b/SpecialUrlShortener.php index d12c699..ea1ef28 100644 --- a/SpecialUrlShortener.php +++ b/SpecialUrlShortener.php @@ -20,42 +20,22 @@ parent::__construct( 'UrlShortener' ); } + protected function getDisplayFormat() { + return 'ooui'; + } + /** - * Remove the legend wrapper and also use the agora styles. * @param HTMLForm $form */ protected function alterForm( HTMLForm $form ) { - $form->setWrapperLegend( false ); - $form->setDisplayFormat( 'raw' ); - $form->suppressDefaultSubmit( true ); - $form->addHeaderText( - Html::element( "span", array( "id" => "mwe-urlshortener-form-header" ), - $this->msg( 'urlshortener-form-header' )->text() - ) - ); - $form->addFooterText( - Html::rawElement( 'div', array( 'id' => 'mwe-urlshortener-form-footer' ), - Html::element( 'span', array( 'id' => 'mwe-urlshortener-shortened-url-label' ), - $this->msg( 'urlshortener-shortened-url-label')->text() - ) . - Html::rawElement( 'div', array( - // Using a div instead of an <input> so we don't have to worry about sizing the - // input to match the length of the shortened URL - 'id' => 'mwe-urlshortener-shorturl-display', - ) - ) - ) . - Html::rawElement( 'div', array( 'id' => 'mwe-urlshortener-form-error' ) ) - ); + $form->setMethod( 'GET' ); + $form->setSubmitID( 'mw-urlshortener-submit' ); + $form->setSubmitTextMsg( 'urlshortener-url-input-submit' ); $this->getOutput()->addModules( 'ext.urlShortener.special' ); $this->getOutput()->addJsConfigVars( array( 'wgUrlShortenerDomainsWhitelist' => UrlShortenerUtils::getWhitelistRegex(), ) ); - $this->getOutput()->addModuleStyles( 'mediawiki.ui' ); - // Send Styles anyway, even without JS - $this->getOutput()->addModuleStyles( 'ext.urlShortener.special.styles' ); - } @@ -67,6 +47,10 @@ * @return bool|string true if url is valid, error message otherwise */ public function validateURL( $url, $allData ) { + if ( $url === null ) { + // No input + return true; + } $validity_check = UrlShortenerUtils::validateUrl( $url ); if ( $validity_check === true ) { return true; @@ -82,29 +66,16 @@ protected function getFormFields() { return array( 'url' => array( - 'class' => 'HTMLTextField', - 'cssclass' => 'mw-ui-input', 'validation-callback' => array( $this, 'validateURL' ), 'required' => true, 'type' => 'url', - 'id' => 'mwe-urlshortener-url-input', + 'name' => 'url', + 'label-message' => 'urlshortener-form-header', + 'autofocus' => true, + 'id' => 'mw-urlshortener-url-input', 'placeholder' => $this->msg( 'urlshortener-url-input-label' )->text() ), - 'submit' => array( - 'class' => 'HTMLSubmitField', - 'default' => $this->msg( 'urlshortener-url-input-submit' )->text(), - 'cssclass' => 'mw-ui-button mw-ui-progressive', - 'id' => 'mwe-urlshortener-url-submit' - ) ); - } - - /** - * Stub to make this compatible with MW1.21, since this was - * abstract in that version. - */ - public function onSuccess() { - parent::onSuccess(); } /** @@ -117,14 +88,15 @@ */ public function onSubmit( array $data ) { $out = $this->getOutput(); - $out->addModuleStyles( 'ext.urlShortener.special.styles' ); + $out->enableOOUI(); + if ( $data['url'] === null ) { + return false; + } - $html = Html::element( 'input', array( - 'type' => 'text', - 'readonly' => true, - 'id' => 'mwe-urlshortener-shorturl-display', - 'value' => UrlShortenerUtils::makeUrl( UrlShortenerUtils::getShortCode( $data['url'] ) ) - )); + $html = new OOUI\TextInputWidget( array( + 'value' => UrlShortenerUtils::makeUrl( UrlShortenerUtils::getShortCode( $data['url'] ) ), + 'readOnly' => true, + ) ); $out->addHTML( $html ); return true; } diff --git a/UrlShortener.php b/UrlShortener.php index f1b5182..9033bad 100644 --- a/UrlShortener.php +++ b/UrlShortener.php @@ -101,35 +101,23 @@ $wgAPIModules['shortenurl'] = 'ApiShortenUrl'; -// Served both to JS and non-JS clients -$wgResourceModules['ext.urlShortener.special.styles'] = array( - 'styles' => 'less/ext.urlShortener.special.less', - 'targets' => array ( 'desktop', 'mobile' ), - 'position' => 'top', - 'dependencies' => array( - 'mediawiki.ui', - ), - 'localBasePath' => __DIR__, - 'remoteExtPath' => 'UrlShortener', -); - // Served only to JS clients $wgResourceModules['ext.urlShortener.special'] = array( 'scripts' => array( - 'js/ext.urlShortener.special.js', + 'modules/ext.urlShortener.special.js', ), 'messages' => array( 'urlshortener-error-malformed-url', 'urlshortener-error-disallowed-url', 'urlshortener-url-input-submit', 'urlshortener-url-input-submitting', + 'urlshortener-shortened-url-label', ), 'localBasePath' => __DIR__, 'remoteExtPath' => 'UrlShortener', 'dependencies' => array( + 'oojs-ui', 'mediawiki.api', 'mediawiki.Uri', - 'jquery.tipsy', ), - "position" => "top" ); diff --git a/js/ext.urlShortener.special.js b/js/ext.urlShortener.special.js deleted file mode 100644 index f1e53fd..0000000 --- a/js/ext.urlShortener.special.js +++ /dev/null @@ -1,138 +0,0 @@ -(function ( mw, $ ) { - function UrlShortener() { - } - - /** - * Initialize the UrlShortener! - * - * Primarily sets up event handlers - */ - UrlShortener.prototype.init = function () { - $( '#mwe-urlshortener-url-submit' ).click( $.proxy( this.onSubmit, this ) ); - $( '#mwe-urlshortener-shorturl-display' ).click( function () { - selectElement( this ); - } ); - $( '#mwe-urlshortener-url-input' ).tipsy( { - gravity: 'n', - fade: true, - tigger: 'manual', - className: 'mwe-urlshortener-tipsy' - } ); - this.api = new mw.Api(); - }; - - /** - * Event Handler for the 'shorten' button - */ - UrlShortener.prototype.onSubmit = function () { - $( "#mwe-urlshortener-form-footer" ).hide( 'blind' ); - $( '#mwe-urlshortener-url-submit' ).val( mw.message( 'urlshortener-url-input-submitting' ).text() ); - this.shortenUrl( - this.getLongUrl() - ).done( function ( shorturl ) { - $( '#mwe-urlshortener-url-submit' ).val( mw.message( 'urlshortener-url-input-submit' ).text() ); - $( "#mwe-urlshortener-form-footer" ).show( 'blind' ); - // The selectElement() call makes the text selected, so the user can just ctrl-C it - $( '#mwe-urlshortener-url-input' ) - .attr( 'title', '' ) - .attr( 'original-title', '' ); - selectElement( $( "#mwe-urlshortener-shorturl-display" ).text( shorturl )[0] ); - } ).fail( function ( err ) { - $( '#mwe-urlshortener-url-submit' ).val( mw.message( 'urlshortener-url-input-submit' ).text() ); - $( '#mwe-urlshortener-form-footer' ).hide(); - $( '#mwe-urlshortener-url-input' ) - .attr( 'title', err.info ) - .tipsy( 'show' ); - } ); - return false; - }; - - /** - * Returns the current user specified long url that we need to shorten - * - * @returns String The long URL that the user has specified - */ - UrlShortener.prototype.getLongUrl = function () { - return $( '#mwe-urlshortener-url-input' ).val(); - }; - - /** - * Shorten a given url by making an API request - * - * @param url Url to shorten - * @returns jQuery.Promise A deferred object that resolves - * with the short url on success or - * fails with an error object on failure - */ - UrlShortener.prototype.shortenUrl = function ( url ) { - var validate = this.validateInput( url ); - if ( validate !== true ) { - return $.Deferred().reject( validate ).promise(); - } - return this.api.get( { - action: 'shortenurl', - url: url - } ).then( function ( data ) { - return data.shortenurl.shorturl; - }, function ( errCode, data ) { - return data.error; - } ); - }; - - /** - * Validate the input URL clientside. Note that this is not the - * only check - they are checked serverside too. - * - * Checks for both URL validity and whitelist matching. - * - * @param input String the URL that is to be shortened - * @returns Boolean|Object true if object is validated, an object matching what is - * returned by the API in case of error. - */ - UrlShortener.prototype.validateInput = function( input ) { - var parsed; - try { - parsed = new mw.Uri( input ); - } catch ( e ) { - return { - code: 'urlshortener-error-malformed-url', - info: mw.msg( 'urlshortener-error-malformed-url' ) - }; - } - if ( parsed.host.match( new RegExp( mw.config.get( 'wgUrlShortenerDomainsWhitelist' ) ) ) ) { - return true; - } else { - return { - code: 'urlshortener-error-disallowed-url', - info: mw.msg( 'urlshortener-error-disallowed-url', parsed.host ) - }; - } - }; - - /** - * Method to 'select' an element, which is equivalent to the user - * clicking and dragging over it. - * - * Contains code from http://stackoverflow.com/a/987376 - * @param element Element the element to select - */ - function selectElement( element ) { - var range, selection; - if ( document.body.createTextRange ) { //ms - range = document.body.createTextRange(); - range.moveToElementText( element ); - range.select(); - } else if ( window.getSelection ) { //all others - selection = window.getSelection(); - range = document.createRange(); - range.selectNodeContents( element ); - selection.removeAllRanges(); - selection.addRange( range ); - } - } - - $( function () { - var us = new UrlShortener(); - us.init(); - } ) -})( mediaWiki, jQuery ); diff --git a/less/ext.urlShortener.special.less b/less/ext.urlShortener.special.less deleted file mode 100644 index 88a0270..0000000 --- a/less/ext.urlShortener.special.less +++ /dev/null @@ -1,64 +0,0 @@ -#firstHeading, #mwe-urlshortener-shortened-url-label { - font: 16px sans-serif !important; - font-weight: bold !important; - color: #444; - border: none; - margin-bottom: 0; -} - -#mwe-urlshortener-url-textbox { - font-size: 1em; - line-height: 1.4em; - min-width: 40%; -} - -#mwe-urlshortener-form-header { - display: block; - font: 14px sans-serif; - margin-bottom: 8px; -} - -#mwe-urlshortener-form-footer { - display: none; - margin-top: 1em; - padding: 10px; - border: 1px solid #ddd; - border-bototm: 3px solid #e1e1e1; - background: #eee; - width: 430px; - box-radius: 2px; -} - -#mwe-urlshortener-shorturl-display { - font: 1.5em monospace; - line-height: 1.6em; -} - -#mwe-urlshortener-form-error { - display: none; - color: #FF9494; -} - -.mwe-urlshortener-tipsy { - .tipsy-inner { - background: #d11d13; - border: none; - border-bottom: 3px solid #b51700; - border-radius: 0; - color: #fff; - font-weight: bold; - padding: 10px; - } - - .tipsy-arrow { - width: 0 !important; - height: 5px !important; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid #d11d13; - background-image: none; - -webkit-transform: rotate( 180deg ); - margin-top: -5px; - } - -} \ No newline at end of file diff --git a/modules/ext.urlShortener.special.js b/modules/ext.urlShortener.special.js new file mode 100644 index 0000000..914642a --- /dev/null +++ b/modules/ext.urlShortener.special.js @@ -0,0 +1,129 @@ +( function ( mw, $, OO ) { + + mw.urlshortener = { + /** + * @var {mw.Api) + */ + api: new mw.Api(), + + /** + * @var {RegExp} + */ + regex: new RegExp( mw.config.get( 'wgUrlShortenerDomainsWhitelist' ) ), + + /** + * @var {OO.ui.TextInputWidget} + */ + shortened: null, + + /** + * @var {OO.ui.TextInputWidget} + */ + input: null, + /** + * @var {OO.ui.ButtonInputWidget} + */ + submit: null, + + /** + * Validate the input URL clientside. Note that this is not the + * only check - they are checked serverside too. + * + * Checks for both URL validity and whitelist matching. + * + * @param {string} input the URL that is to be shortened + * @return {boolean|Object} true if object is validated, an object matching what is + * returned by the API in case of error. + */ + validateInput: function ( input ) { + var parsed, error, self = mw.urlshortener; + try { + parsed = new mw.Uri( input ); + if ( parsed.host.match( self.regex ) ) { + this.setLabel( null ); + return true; + } else { + error = mw.msg( 'urlshortener-error-disallowed-url', parsed.host ); + } + } catch ( e ) { + error = mw.msg( 'urlshortener-error-malformed-url' ); + } + + this.setLabel( error ); + return false; + }, + + /** + * Click handler for the submit button + */ + onSubmit: function () { + var self = mw.urlshortener; + self.input.pushPending().setReadOnly( true ); + self.setSubmit( 'submitting' ); + self.input.isValid().done( function () { + self.shortenUrl( + self.input.getValue() + ).done( function ( shorturl ) { + self.setSubmit( 'submit' ); + self.input.popPending().setReadOnly( false ); + + if ( !self.shortened ) { + self.shortened = new OO.ui.TextInputWidget( { + value: shorturl, + readOnly: true + } ); + // Wrap in a FieldLayout so we get the label + self.input.$element.after( new OO.ui.FieldLayout( self.shortened, { + align: 'top', + label: mw.msg( 'urlshortener-shortened-url-label' ) + } ).$element ); + } else { + self.shortened.setValue( shorturl ); + } + self.shortened.select(); + } ).fail( function ( err ) { + self.setSubmit( 'submit' ); + self.input.popPending().setReadOnly( false ); + self.input.setLabel( err.info ); + } ); + } ); + }, + + init: function () { + this.input = OO.ui.infuse( 'mw-urlshortener-url-input' ); + this.input.focus(); // FIXME not staying focused due to T106313 + this.input.setValidation( this.validateInput ); + this.submit = OO.ui.infuse( 'mw-urlshortener-submit' ); + this.submit.on( 'click', this.onSubmit ); + }, + + /** + * @param {string} status either 'submitting' or 'submit' + */ + setSubmit: function ( status ) { + // urlshortener-url-input-submitting, urlshortener-url-input-submit + this.submit.setLabel( mw.message( 'urlshortener-url-input-' + status ).text() ); + }, + + /** + * Shorten the provided url + * + * @param {string} url + * @return {jQuery.Promise} + */ + shortenUrl: function ( url ) { + return this.api.get( { + action: 'shortenurl', + url: url + } ).then( function ( data ) { + return data.shortenurl.shorturl; + }, function ( errCode, data ) { + return data.error; + } ); + } + }; + + $( function () { + mw.urlshortener.init(); + } ); +} )( mediaWiki, jQuery, OO ); diff --git a/package.json b/package.json new file mode 100644 index 0000000..63d5896 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "test": "grunt test" + }, + "devDependencies": { + "grunt": "0.4.5", + "grunt-cli": "0.1.13", + "grunt-contrib-jshint": "0.11.2", + "grunt-banana-checker": "0.2.2", + "grunt-jscs": "1.8.0", + "grunt-jsonlint": "1.0.4", + "grunt-tyops": "0.1.0" + } +} -- To view, visit https://gerrit.wikimedia.org/r/221354 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Icecc027206484feaf08dc5247f82356eb7d45469 Gerrit-PatchSet: 12 Gerrit-Project: mediawiki/extensions/UrlShortener Gerrit-Branch: master Gerrit-Owner: Legoktm <[email protected]> Gerrit-Reviewer: Bartosz DziewoĆski <[email protected]> Gerrit-Reviewer: Legoktm <[email protected]> Gerrit-Reviewer: Prtksxna <[email protected]> Gerrit-Reviewer: Yuvipanda <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
