Jforrester has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/362144 )
Change subject: Update OOjs UI to v0.22.2 ...................................................................... Update OOjs UI to v0.22.2 Release notes: https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v0.22.2 Change-Id: If6a7aa1d924b416fc95831e1a1b26313e6482cbf --- M composer.json M composer.lock M composer/installed.json M oojs/oojs-ui/History.md M oojs/oojs-ui/bin/docparser.rb M oojs/oojs-ui/bin/testsuitegenerator.rb M oojs/oojs-ui/demos/demo.js M oojs/oojs-ui/demos/demos.php M oojs/oojs-ui/demos/index.html M oojs/oojs-ui/demos/pages/icons.js M oojs/oojs-ui/demos/pages/widgets.js M oojs/oojs-ui/demos/styles/demo.css M oojs/oojs-ui/i18n/jv.json M oojs/oojs-ui/package.json M oojs/oojs-ui/php/mixins/TabIndexedElement.php 15 files changed, 433 insertions(+), 204 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/vendor refs/changes/44/362144/1 diff --git a/composer.json b/composer.json index 4a05d97..587d9d8 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "monolog/monolog": "1.22.1", "mustangostang/spyc": "0.6.2", "nmred/kafka-php": "0.1.5", - "oojs/oojs-ui": "0.22.1", + "oojs/oojs-ui": "0.22.2", "oyejorge/less.php": "1.7.0.14", "pear/console_getopt": "1.4.1", "pear/mail": "1.3.0", diff --git a/composer.lock b/composer.lock index 167b31a..daa16e8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "52a3bd30d59872778eaf081e7734b7c9", + "content-hash": "d5635cfadceb2bfc5954f0657124a504", "packages": [ { "name": "composer/semver", @@ -516,16 +516,16 @@ }, { "name": "oojs/oojs-ui", - "version": "v0.22.1", + "version": "v0.22.2", "source": { "type": "git", "url": "https://github.com/wikimedia/oojs-ui.git", - "reference": "610ed06f7aa8d915e689efad93c45a2d54206031" + "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wikimedia/oojs-ui/zipball/610ed06f7aa8d915e689efad93c45a2d54206031", - "reference": "610ed06f7aa8d915e689efad93c45a2d54206031", + "url": "https://api.github.com/repos/wikimedia/oojs-ui/zipball/ca5f31425138961c4a779d8e0d7674f16dbc7d24", + "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24", "shasum": "" }, "require": { @@ -591,7 +591,7 @@ ], "description": "Provides library of common widgets, layouts, and windows.", "homepage": "https://www.mediawiki.org/wiki/OOjs_UI", - "time": "2017-05-31T18:50:29+00:00" + "time": "2017-06-28T19:15:29+00:00" }, { "name": "oyejorge/less.php", diff --git a/composer/installed.json b/composer/installed.json index 9097952..1e05c03 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -2215,18 +2215,67 @@ ] }, { - "name": "oojs/oojs-ui", - "version": "v0.22.1", - "version_normalized": "0.22.1.0", + "name": "wikimedia/css-sanitizer", + "version": "v1.0.2", + "version_normalized": "1.0.2.0", "source": { "type": "git", - "url": "https://github.com/wikimedia/oojs-ui.git", - "reference": "610ed06f7aa8d915e689efad93c45a2d54206031" + "url": "https://github.com/wikimedia/css-sanitizer.git", + "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wikimedia/oojs-ui/zipball/610ed06f7aa8d915e689efad93c45a2d54206031", - "reference": "610ed06f7aa8d915e689efad93c45a2d54206031", + "url": "https://api.github.com/repos/wikimedia/css-sanitizer/zipball/53071d588f7ac20f1467fb5952e968c1f8e2bb69", + "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-mbstring": "*", + "mediawiki/at-ease": "1.1.0", + "php": ">=5.5.9", + "wikimedia/utfnormal": "1.1.0" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "0.9.2", + "mediawiki/mediawiki-codesniffer": "0.7.2", + "phpunit/phpunit": "4.8.31", + "wikimedia/testing-access-wrapper": "1.0.0" + }, + "time": "2017-06-13T15:51:12+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Brad Jorsch", + "email": "bjor...@wikimedia.org" + } + ], + "description": "Classes to parse and sanitize CSS", + "homepage": "https://www.mediawiki.org/wiki/Css-sanitizer" + }, + { + "name": "oojs/oojs-ui", + "version": "v0.22.2", + "version_normalized": "0.22.2.0", + "source": { + "type": "git", + "url": "https://github.com/wikimedia/oojs-ui.git", + "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wikimedia/oojs-ui/zipball/ca5f31425138961c4a779d8e0d7674f16dbc7d24", + "reference": "ca5f31425138961c4a779d8e0d7674f16dbc7d24", "shasum": "" }, "require": { @@ -2238,7 +2287,7 @@ "mediawiki/mediawiki-codesniffer": "0.6.0", "phpunit/phpunit": "4.8.21" }, - "time": "2017-05-31T18:50:29+00:00", + "time": "2017-06-28T19:15:29+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2294,54 +2343,5 @@ ], "description": "Provides library of common widgets, layouts, and windows.", "homepage": "https://www.mediawiki.org/wiki/OOjs_UI" - }, - { - "name": "wikimedia/css-sanitizer", - "version": "v1.0.2", - "version_normalized": "1.0.2.0", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/css-sanitizer.git", - "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/css-sanitizer/zipball/53071d588f7ac20f1467fb5952e968c1f8e2bb69", - "reference": "53071d588f7ac20f1467fb5952e968c1f8e2bb69", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "ext-mbstring": "*", - "mediawiki/at-ease": "1.1.0", - "php": ">=5.5.9", - "wikimedia/utfnormal": "1.1.0" - }, - "require-dev": { - "jakub-onderka/php-parallel-lint": "0.9.2", - "mediawiki/mediawiki-codesniffer": "0.7.2", - "phpunit/phpunit": "4.8.31", - "wikimedia/testing-access-wrapper": "1.0.0" - }, - "time": "2017-06-13T15:51:12+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Brad Jorsch", - "email": "bjor...@wikimedia.org" - } - ], - "description": "Classes to parse and sanitize CSS", - "homepage": "https://www.mediawiki.org/wiki/Css-sanitizer" } ] diff --git a/oojs/oojs-ui/History.md b/oojs/oojs-ui/History.md index 2a800a6..b5bf1a2 100644 --- a/oojs/oojs-ui/History.md +++ b/oojs/oojs-ui/History.md @@ -1,4 +1,43 @@ # OOjs UI Release History +## v0.22.2 / 2017-06-28 +### Deprecations +* [DEPRECATING CHANGE] TextInputWidget: Move multi-line support out (Prateek Saxena) +* [DEPRECATING CHANGE] icons: Move and rename 'stripeSideMenu' to 'draggable' (Volker E.) + +### Features +* DropdownInputWidget: Unbreak setting 'value' via config options (Bartosz Dziewoński) +* Element: Work around browsers that set fractional scrollTop values (Roan Kattouw) + +### Styles +* BookletLayout: Workaround for horizontal scrollbars on menu when editable (Bartosz Dziewoński) +* icons: Let's stop referring to removed icons, hmm? (James D. Forrester) +* Rewrite all styling for "outline controls" (Bartosz Dziewoński) +* Apex theme: Align appearance of tags' close icon to WikimediaUI theme (Volker E.) +* Apex theme: Fix HorizontalLayout containing FieldLayouts (Bartosz Dziewoński) +* WikimediaUI theme: Remove default DraggableElement styling (Ed Sanders) +* WikimediaUI theme: Use icon instead of indicator in Tag-/CapsuleItemWidget (Volker E.) +* WikimediaUI: Strengthen Radio*Widget's `:checked` state (Volker E.) + +### Code +* MenuSelectWidget: Fix item hiding when menu contents change (Roan Kattouw) +* MultilineTextInputWidget: Fix autosizing (Bartosz Dziewoński) +* PopupWidget: Replace CSS with Less comments for smaller dist (Volker E.) +* SearchInputWidget: Fix ability to clear the input (Bartosz Dziewoński) +* TabIndexedElement: Fix validation and make consistent in PHP and JS (Bartosz Dziewoński) +* Use javascript-stringify instead of JSON.stringify (Ed Sanders) +* Apex theme: Fix order of selectors for :first-child FieldLayout (Bartosz Dziewoński) +* demos: Add links to documentation from code examples (Prateek Saxena) +* demos: Allow linking to specific widgets (Bartosz Dziewoński) +* demos: Indicate code toggle clearer (Volker E.) +* demos: Pull out all links to docs/sources to the top of the code (Bartosz Dziewoński) +* demos: Simplify code generation, now that we use javascript-stringify (Bartosz Dziewoński) +* demos: Use URL 'query' part for linking to demo sections rather than URL 'fragment' (Bartosz Dziewoński) +* docs: Fix some typos in documentation (Bartosz Dziewoński) +* docparser: Fix handling for fake trait constructors (Bartosz Dziewoński) +* docparser: Make matching '(default: ...)' case-insensitive (Bartosz Dziewoński) +* docparser: Tighter check for 'use' statements in PHP (Bartosz Dziewoński) + + ## v0.22.1 / 2017-05-31 ### Code * WindowManager: Do not use return value of `#closeWindow` as promise (Bartosz Dziewoński) diff --git a/oojs/oojs-ui/bin/docparser.rb b/oojs/oojs-ui/bin/docparser.rb index 7bdff9c..e995ec8 100644 --- a/oojs/oojs-ui/bin/docparser.rb +++ b/oojs/oojs-ui/bin/docparser.rb @@ -22,7 +22,7 @@ end def extract_default_from_description item - m = item[:description].match(/\(default: (.+?)\)\s*?$/) + m = item[:description].match(/\(default: (.+?)\)\s*?$/i) return if !m # modify `item` in-place item[:default] = m[1] @@ -73,6 +73,7 @@ js_class_constructor = false js_class_constructor_desc = '' + php_trait_constructor = false ignore = false comment, code_line = d.split '*/' @@ -223,6 +224,7 @@ data[:static] = true if static data[:parent] = cleanup_class_name(parent) if parent data[:name] ||= cleanup_class_name(name) + php_trait_constructor = true if kind == :method && data[:name] == 'initialize' + current_class[:name] end end @@ -237,7 +239,8 @@ end # standardize - if data[:name] == '__construct' || js_class_constructor + # (also handle fake constructors for traits) + if data[:name] == '__construct' || js_class_constructor || php_trait_constructor data[:name] = '#constructor' end @@ -258,7 +261,7 @@ # this is evil, assumes we only have one class in a file, but we'd need a proper parser to do it better if current_class current_class[:mixins] += - text.scan(/[ \t]use (\w+)(?: ?\{|;)/).flatten.map(&method(:cleanup_class_name)) + text.scan(/^[ \t]*use (\w+)(?: ?\{|;)/).flatten.map(&method(:cleanup_class_name)) end output << current_class if current_class diff --git a/oojs/oojs-ui/bin/testsuitegenerator.rb b/oojs/oojs-ui/bin/testsuitegenerator.rb index bada71b..f91e066 100644 --- a/oojs/oojs-ui/bin/testsuitegenerator.rb +++ b/oojs/oojs-ui/bin/testsuitegenerator.rb @@ -57,7 +57,7 @@ 'method' => %w[GET POST], 'target' => ['_blank'], 'accessKey' => ['k'], - 'tabIndex' => [-1, 0, 100], + 'tabIndex' => [-1, 0, 100, '42'], 'maxLength' => [100], 'icon' => ['image'], 'indicator' => ['down'], diff --git a/oojs/oojs-ui/demos/demo.js b/oojs/oojs-ui/demos/demo.js index 3c8dc00..f8905b8 100644 --- a/oojs/oojs-ui/demos/demo.js +++ b/oojs/oojs-ui/demos/demo.js @@ -1,8 +1,8 @@ /* eslint-disable no-console */ -/* globals Prism */ +/* globals Prism, javascriptStringify */ /** * @class - * @extends {OO.ui.Element} + * @extends OO.ui.Element * * @constructor */ @@ -16,7 +16,7 @@ OO.EventEmitter.call( this ); // Normalization - this.normalizeHash(); + this.normalizeQuery(); // Properties this.stylesheetLinks = this.getStylesheetLinks(); @@ -51,10 +51,7 @@ new OO.ui.ButtonWidget( { label: 'JS' } ).setActive( true ), new OO.ui.ButtonWidget( { label: 'PHP', - href: 'demos.php' + - '?page=widgets' + - '&theme=' + this.mode.theme + - '&direction=' + this.mode.direction + href: 'demos.php' + this.getUrlQuery( this.getCurrentFactorValues() ) } ) ] ); this.platformSelect = new OO.ui.ButtonSelectWidget().addItems( [ @@ -236,6 +233,19 @@ */ Demo.static.defaultPlatform = 'desktop'; +/* Static Methods */ + +/** + * Scroll to current fragment identifier. We have to do this manually because of the fixed header. + */ +Demo.static.scrollToFragment = function () { + var elem = document.getElementById( location.hash.slice( 1 ) ); + if ( elem ) { + // The additional '10' is just because it looks nicer. + $( window ).scrollTop( $( elem ).offset().top - $( '.demo-menu' ).outerHeight() - 10 ); + } +}; + /* Methods */ /** @@ -279,13 +289,29 @@ direction = this.directionSelect.getSelectedItem().getData(), platform = this.platformSelect.getSelectedItem().getData(); - location.hash = '#' + [ page, theme, direction, platform ].join( '-' ); + history.pushState( null, document.title, this.getUrlQuery( [ page, theme, direction, platform ] ) ); + $( window ).triggerHandler( 'popstate' ); +}; + +/** + * Get URL query for given factors describing the demo's mode. + * + * @param {string[]} factors Factors, as returned e.g. by #getCurrentFactorValues + * @return {string} URL query part, starting with '?' + */ +Demo.prototype.getUrlQuery = function ( factors ) { + return '?page=' + factors[ 0 ] + + '&theme=' + factors[ 1 ] + + '&direction=' + factors[ 2 ] + + '&platform=' + factors[ 3 ] + + // Preserve current URL 'fragment' part + location.hash; }; /** * Get a list of mode factors. * - * Factors are a mapping between symbolic names used in the URL hash and internal information used + * Factors are a mapping between symbolic names used in the URL query and internal information used * to act on those symbolic names. * * Factor lists are in URL order: page, theme, direction, platform. Page contains the symbolic @@ -317,7 +343,7 @@ * Get a list of default factors. * * Factor defaults are in URL order: page, theme, direction, platform. Each contains a symbolic - * factor name which should be used as a fallback when the URL hash is missing or invalid. + * factor name which should be used as a fallback when the URL query is missing or invalid. * * @return {Object[]} List of default factors */ @@ -331,18 +357,29 @@ }; /** - * Parse the current URL hash into factor values. + * Parse the current URL query into factor values. * - * @return {string[]} Factor values in URL order: page, theme, direction + * @return {string[]} Factor values in URL order: page, theme, direction, platform */ Demo.prototype.getCurrentFactorValues = function () { - return location.hash.slice( 1 ).split( '-' ); + var i, parts, index, + factors = this.getDefaultFactorValues(), + order = [ 'page', 'theme', 'direction', 'platform' ], + query = location.search.slice( 1 ).split( '&' ); + for ( i = 0; i < query.length; i++ ) { + parts = query[ i ].split( '=', 2 ); + index = order.indexOf( parts[ 0 ] ); + if ( index !== -1 ) { + factors[ index ] = decodeURIComponent( parts[ 1 ] ); + } + } + return factors; }; /** * Get the current mode. * - * Generated from parsed URL hash values. + * Generated from parsed URL query values. * * @return {Object} List of factor values keyed by factor name */ @@ -403,21 +440,43 @@ }; /** - * Normalize the URL hash. + * Normalize the URL query. */ -Demo.prototype.normalizeHash = function () { - var i, len, factorValues, +Demo.prototype.normalizeQuery = function () { + var i, len, factorValues, match, valid, factorValue, modes = [], factors = this.getFactors(), defaults = this.getDefaultFactorValues(); factorValues = this.getCurrentFactorValues(); for ( i = 0, len = factors.length; i < len; i++ ) { - modes[ i ] = factors[ i ][ factorValues[ i ] ] !== undefined ? factorValues[ i ] : defaults[ i ]; + factorValue = factorValues[ i ]; + modes[ i ] = factors[ i ][ factorValue ] !== undefined ? factorValue : defaults[ i ]; } - // Update hash - location.hash = modes.join( '-' ); + // Backwards-compatibility with old URLs that used the 'fragment' part to link to demo sections: + // if a fragment is specified and it describes valid factors, turn the URL into the new style. + match = location.hash.match( /^#(\w+)-(\w+)-(\w+)-(\w+)$/ ); + if ( match ) { + factorValues = []; + valid = true; + for ( i = 0, len = factors.length; i < len; i++ ) { + factorValue = match[ i + 1 ]; + if ( factors[ i ][ factorValue ] !== undefined ) { + factorValues[ i ] = factorValue; + } else { + valid = false; + break; + } + } + if ( valid ) { + location.hash = ''; + modes = factorValues; + } + } + + // Update query + history.replaceState( null, document.title, this.getUrlQuery( modes ) ); }; /** @@ -501,19 +560,23 @@ $log.prop( 'scrollTop', $log.prop( 'scrollHeight' ) ); } - function getCode( item ) { - var config, isDemoWidget, constructorName, defaultConfig, url, params, out, - replaceKeyword = 'replace-', - replaceLater = []; + function getCode( item, toplevel ) { + var config, defaultConfig, url, params, out, i, + items = [], + demoLinks = [], + docLinks = []; + + function getConstructorName( item ) { + var isDemoWidget = item.constructor.name.indexOf( 'Demo' ) === 0; + return ( isDemoWidget ? 'Demo.' : 'OO.ui.' ) + item.constructor.name.slice( 4 ); + } // If no item was passed we shouldn't show a code block if ( item === undefined ) { return false; } - isDemoWidget = item.constructor.name.indexOf( 'Demo' ) === 0; config = item.initialConfig; - constructorName = ( isDemoWidget ? 'Demo.' : 'OO.ui.' ) + item.constructor.name.slice( 4 ); // Prevent the default config from being part of the code if ( item instanceof OO.ui.ActionFieldLayout ) { @@ -534,67 +597,71 @@ } } ); - config = JSON.stringify( config, function ( k, v ) { - if ( v instanceof OO.ui.Element || v instanceof OO.ui.HtmlSnippet || v instanceof jQuery || v instanceof Function ) { - replaceLater.push( v ); - return replaceKeyword + ( replaceLater.length - 1 ).toString(); + config = javascriptStringify( config, function ( obj, indent, stringify ) { + if ( obj instanceof Function ) { + // Get function's source code, with extraneous indentation removed + return obj.toString().replace( /^\t\t\t\t\t\t/gm, '' ); + } else if ( obj instanceof jQuery ) { + if ( $.contains( item.$element[ 0 ], obj[ 0 ] ) ) { + // If this element appears inside the generated widget, + // assume this was something like `$label: $( '<p>Text</p>' )` + return '$( ' + javascriptStringify( obj.prop( 'outerHTML' ) ) + ' )'; + } else { + // Otherwise assume this was something like `$overlay: $( '#overlay' )` + return '$( ' + javascriptStringify( '#' + obj.attr( 'id' ) ) + ' )'; + } + } else if ( obj instanceof OO.ui.HtmlSnippet ) { + return 'new OO.ui.HtmlSnippet( ' + javascriptStringify( obj.toString() ) + ' )'; + } else if ( obj instanceof OO.ui.Element ) { + return getCode( obj ); + } else { + return stringify( obj ); } - return v; }, '\t' ); - // We replace later, because running getCode in place will treat the new code - // as a string and won't do proper indentation either - replaceLater.forEach( function ( obj, i ) { - config = config.replace( - // Match any number of tabs (for indentation) and optional object key, followed by our placeholder - new RegExp( '(\t*)("[^"]+?": |)"' + replaceKeyword + i + '"' ), - function ( all, indent, objectKey ) { - var code; - if ( obj instanceof Function ) { - // Get function's source code, with extraneous indentation removed - code = obj.toString().replace( /^\t\t\t\t\t\t/gm, '' ); - } else if ( obj instanceof jQuery ) { - if ( $.contains( item.$element[ 0 ], obj[ 0 ] ) ) { - // If this element appears inside the generated widget, - // assume this was something like `$label: $( '<p>Text</p>' )` - code = '$( \"' + obj.prop( 'outerHTML' ).replace( /'/g, '\\\'' ) + '\" )'; - } else { - // Otherwise assume this was something like `$overlay: $( '#overlay' )` - code = '$( \"#' + obj.attr( 'id' ) + '\" )'; - } - } else if ( obj instanceof OO.ui.HtmlSnippet ) { - code = 'new OO.ui.HtmlSnippet( "' + obj.toString() + '" )'; - } else { - code = getCode( obj ); - } - // Re-add the indent at the beginning, and after every newline - return indent + objectKey + code.replace( /\n/g, '\n' + indent ); - } - ); - } ); - // The generated code needs to include different arguments, based on the object type + items.push( item ); if ( item instanceof OO.ui.ActionFieldLayout ) { params = getCode( item.fieldWidget ) + ', ' + getCode( item.buttonWidget ); + items.push( item.fieldWidget ); + items.push( item.buttonWidget ); } else if ( item instanceof OO.ui.FieldLayout ) { params = getCode( item.fieldWidget ); + items.push( item.fieldWidget ); } else { params = ''; } if ( config !== '{}' ) { params += ( params ? ', ' : '' ) + config; } - out = 'new ' + constructorName + '(' + ( params ? ' ' : '' ) + params + ( params ? ' ' : '' ) + ')'; + out = 'new ' + getConstructorName( item ) + '(' + + ( params ? ' ' : '' ) + params + ( params ? ' ' : '' ) + + ')'; - // The code generated for Demo widgets cannot be copied and used - if ( item.constructor.name.indexOf( 'Demo' ) === 0 ) { - url = - 'https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/demos/classes/' + - item.constructor.name.slice( 4 ) + '.js'; - out = '// See source code:\n// ' + url + '\n' + out; + if ( toplevel ) { + for ( i = 0; i < items.length; i++ ) { + item = items[ i ]; + // The code generated for Demo widgets cannot be copied and used + if ( item.constructor.name.indexOf( 'Demo' ) === 0 ) { + url = + 'https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/demos/classes/' + + item.constructor.name.slice( 4 ) + '.js'; + demoLinks.push( url ); + } else { + url = 'https://doc.wikimedia.org/oojs-ui/master/js/#!/api/' + getConstructorName( item ); + url = '[' + url + '](' + url + ')'; + docLinks.push( url ); + } + } } - return out; + return ( + ( docLinks.length ? '// See documentation at: \n// ' : '' ) + + docLinks.join( '\n// ' ) + ( docLinks.length ? '\n' : '' ) + + ( demoLinks.length ? '// See source code:\n// ' : '' ) + + demoLinks.join( '\n// ' ) + ( demoLinks.length ? '\n' : '' ) + + out + ); } $toggle = $( '<span>' ) @@ -613,9 +680,9 @@ console.log( '[demo]', item ); if ( showLayoutCode === true ) { - code = getCode( item ); + code = getCode( item, true ); } else { - code = getCode( item.fieldWidget ); + code = getCode( item.fieldWidget, true ); } if ( code ) { @@ -670,3 +737,41 @@ return $console; }; + +/** + * Build a link to this example. + * + * @param {OO.ui.Layout} item + * @param {OO.ui.FieldsetLayout} parentItem + * @return {jQuery} Link interface element + */ +Demo.prototype.buildLinkExample = function ( item, parentItem ) { + var $linkExample, label, fragment; + + if ( item.$label.text() === '' ) { + item = parentItem; + } + fragment = item.elementId; + if ( !fragment ) { + label = item.$label.text(); + fragment = label.replace( /[^\w]+/g, '-' ).replace( /^-|-$/g, '' ); + item.setElementId( fragment ); + } + + $linkExample = $( '<a>' ) + .addClass( 'demo-link-example' ) + .attr( 'title', 'Link to this example' ) + .attr( 'href', '#' + fragment ) + .on( 'click', function ( e ) { + // We have to handle this manually in order to call .scrollToFragment() even if it's the same + // fragment. Normally, the browser will scroll but not fire a 'hashchange' event in this + // situation, and the scroll position will be off because of our fixed header. + if ( e.which === OO.ui.MouseButtons.LEFT ) { + location.hash = $( this ).attr( 'href' ); + Demo.static.scrollToFragment(); + e.preventDefault(); + } + } ); + + return $linkExample; +}; diff --git a/oojs/oojs-ui/demos/demos.php b/oojs/oojs-ui/demos/demos.php index 8a779b2..91dd2e1 100644 --- a/oojs/oojs-ui/demos/demos.php +++ b/oojs/oojs-ui/demos/demos.php @@ -28,9 +28,9 @@ ? $_GET['page'] : 'widgets'; $query = [ + 'page' => $page, 'theme' => $theme, 'direction' => $direction, - 'page' => $page, ]; $additionalThemeImagesSuffixes = [ @@ -123,7 +123,7 @@ 'items' => [ new OOUI\ButtonWidget( [ 'label' => 'JS', - 'href' => ".#$page-$theme-$direction", + 'href' => '.?' . http_build_query( $query ), 'active' => false, ] ), new OOUI\ButtonWidget( [ diff --git a/oojs/oojs-ui/demos/index.html b/oojs/oojs-ui/demos/index.html index cabe444..94c9203 100644 --- a/oojs/oojs-ui/demos/index.html +++ b/oojs/oojs-ui/demos/index.html @@ -14,6 +14,7 @@ <script src="node_modules/oojs/dist/oojs.jquery.js"></script> <script src="node_modules/prismjs/prism.js"></script> <script src="node_modules/prismjs/plugins/autolinker/prism-autolinker.js"></script> + <script src="node_modules/javascript-stringify/javascript-stringify.js"></script> <script src="dist/oojs-ui.js"></script> <script src="dist/oojs-ui-wikimediaui.js"></script> <!-- Do not change this line or you'll break `grunt add-theme` --> <script src="dist/oojs-ui-apex.js"></script> @@ -52,28 +53,35 @@ <script src="pages/toolbars.js"></script> <script> $( function () { - var demo; + var demo, lastQuery = location.search; function setup() { var prevPage = demo ? demo.mode.page : null, scrollPos = $( window ).scrollTop(); if ( demo ) { + if ( lastQuery === location.search ) { + return false; + } demo.destroy(); } + lastQuery = location.search; demo = new Demo(); $( 'body' ).append( demo.$element ); demo.initialize().done( function () { - if ( prevPage === demo.mode.page ) { + if ( prevPage === demo.mode.page && scrollPos ) { // Restore scroll position from before we destroyed the demo $( window ).scrollTop( scrollPos ); + } else { + Demo.static.scrollToFragment(); } } ); } setup(); - $( window ).on( 'hashchange', setup ); + $( window ).on( 'popstate', setup ); + $( window ).on( 'hashchange', Demo.static.scrollToFragment ); } ) </script> </body> diff --git a/oojs/oojs-ui/demos/pages/icons.js b/oojs/oojs-ui/demos/pages/icons.js index 3ce0269..876dc35 100644 --- a/oojs/oojs-ui/demos/pages/icons.js +++ b/oojs/oojs-ui/demos/pages/icons.js @@ -12,7 +12,8 @@ 'last', 'expand', 'collapse', - 'move' + 'move', + 'draggable' ], content: [ 'article', @@ -175,7 +176,6 @@ layout: [ 'menu', 'stripeFlow', - 'stripeSideMenu', 'stripeSummary', 'stripeToC', 'viewCompact', diff --git a/oojs/oojs-ui/demos/pages/widgets.js b/oojs/oojs-ui/demos/pages/widgets.js index 6f30c6b..d4ccc95 100644 --- a/oojs/oojs-ui/demos/pages/widgets.js +++ b/oojs/oojs-ui/demos/pages/widgets.js @@ -28,7 +28,7 @@ verticalHandledDragItems.push( new Demo.DraggableHandledItemWidget( { data: 'item' + i, - icon: 'menu', + icon: 'draggable', label: 'Item ' + i } ) ); @@ -648,7 +648,7 @@ } ), new OO.ui.FieldsetLayout( { id: 'demo-section-inputs', - label: 'Inputs: TextInput, TextInput (multiline), SearchInput, NumberInput', + label: 'Inputs: TextInput, TextInput, MultilineTextInput, SearchInput, NumberInput', items: [ new OO.ui.FieldLayout( new OO.ui.TextInputWidget( { value: 'Text input' } ), @@ -723,52 +723,47 @@ } ), new OO.ui.FieldLayout( - new OO.ui.TextInputWidget( { - multiline: true, + new OO.ui.MultilineTextInputWidget( { value: 'Multiline\nMultiline' } ), { - label: 'TextInputWidget (multiline)\u200E', + label: 'MultilineTextInputWidget\u200E', align: 'top' } ), new OO.ui.FieldLayout( - new OO.ui.TextInputWidget( { - multiline: true, + new OO.ui.MultilineTextInputWidget( { rows: 15, value: 'Multiline\nMultiline' } ), { - label: 'TextInputWidget (multiline, rows=15)\u200E', + label: 'MultilineTextInputWidget (rows=15)\u200E', align: 'top' } ), new OO.ui.FieldLayout( - new OO.ui.TextInputWidget( { - multiline: true, + new OO.ui.MultilineTextInputWidget( { autosize: true, value: 'Autosize\nAutosize\nAutosize\nAutosize' } ), { - label: 'TextInputWidget (autosize)\u200E', + label: 'MultilineTextInputWidget (autosize)\u200E', align: 'top' } ), new OO.ui.FieldLayout( - new OO.ui.TextInputWidget( { - multiline: true, + new OO.ui.MultilineTextInputWidget( { rows: 10, autosize: true, value: 'Autosize\nAutosize\nAutosize\nAutosize' } ), { - label: 'TextInputWidget (autosize, rows=10)\u200E', + label: 'MultilineTextInputWidget (autosize, rows=10)\u200E', align: 'top' } ), new OO.ui.FieldLayout( - new OO.ui.TextInputWidget( { - multiline: true, + new OO.ui.MultilineTextInputWidget( { autosize: true, icon: 'tag', indicator: 'alert', @@ -776,7 +771,7 @@ value: 'Autosize\nAutosize\nAutosize\nAutosize' } ), { - label: 'TextInputWidget (autosize, icon, indicator, label)\u200E', + label: 'MultilineTextInputWidget (autosize, icon, indicator, label)\u200E', align: 'top' } ), @@ -2745,6 +2740,7 @@ $.each( fieldsetLayout.getItems(), function ( j, fieldLayout ) { fieldLayout.$element.append( + demo.buildLinkExample( fieldLayout, fieldsetLayout instanceof OO.ui.FormLayout ? fieldLayout : fieldsetLayout ), demo.buildConsole( fieldLayout, 'layout', 'widget', showLayoutCode ) ); } ); diff --git a/oojs/oojs-ui/demos/styles/demo.css b/oojs/oojs-ui/demos/styles/demo.css index 73ae333..34611fe 100644 --- a/oojs/oojs-ui/demos/styles/demo.css +++ b/oojs/oojs-ui/demos/styles/demo.css @@ -63,45 +63,82 @@ box-sizing: border-box; } +.demo-link-example, .demo-console-toggle { float: right; - margin-top: -2em; + margin-top: -2.5em; cursor: pointer; - -webkit-transition: background-color 100ms, color 100ms, transform 100ms; - -moz-transition: background-color 100ms, color 100ms, transform 100ms; - transition: background-color 100ms, color 100ms, transform 100ms; + -webkit-transition: background-color 100ms, color 100ms; + -moz-transition: background-color 100ms, color 100ms; + transition: background-color 100ms, color 100ms; +} + +.demo-link-example { + position: relative; + top: 1px; + right: 2em; + opacity: 0; + -webkit-transition: opacity 100ms; + -moz-transition: opacity 100ms; + transition: opacity 100ms; +} + +.oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-link-example { + right: 7.2em; + opacity: 1; } .demo-console-expanded .demo-console-toggle { - margin-top: -2.5em; + margin-top: -3em; +} + +.demo-link-example::after, +.demo-console-toggle::after { + color: #36c; + position: relative; + display: inline-block; + margin-right: -1.2em; + padding: 0.703125em 0.9375em 0.625em; /* equals frameless button */ + border-radius: 2px; + line-height: 1.172em; + font-weight: bold; + text-align: center; + -webkit-transition: color 100ms; + -moz-transition: color 100ms; + transition: color 100ms; +} + +.demo-link-example::after, +.demo-console-toggle:hover::after { + color: #447ff5; +} + +.demo-link-example::after { + content: '#'; +} + +.demo-container > .oo-ui-fieldsetLayout:first-child .oo-ui-fieldLayout:first-child .demo-link-example::after { + content: 'Link to this example #'; } .demo-console-toggle::after { - content: '→'; - background-color: #f8f9fa; - color: #222; - position: relative; - display: inline-block; - width: 1.6em; - border-radius: 2px; - line-height: 1.6; - text-align: center; - -webkit-transition: background-color 100ms, color 100ms, transform 100ms; - -moz-transition: background-color 100ms, color 100ms, transform 100ms; - transition: background-color 100ms, color 100ms, transform 100ms; -} - -.demo-console-toggle:hover::after, -.demo-console-expanded .demo-console-toggle::after { - background: #fff; - color: #444; + content: '↓'; } .demo-console-expanded .demo-console-toggle::after { - -webkit-transform: rotate( -90deg ); - -moz-transform: rotate( -90deg ); - -ms-transform: rotate( -90deg ); - transform: rotate( -90deg ); + content: '↑'; +} + +.oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-console-toggle::after { + content: 'Show code ↓'; +} + +.oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-console-expanded .demo-console-toggle::after { + content: 'Hide code ↑'; +} + +.oo-ui-fieldLayout:hover > .demo-link-example { + opacity: 1; } .demo-console-collapsed .demo-sample-code, @@ -276,8 +313,11 @@ max-width: 50em; } +.oo-ui-fieldLayout-align-top.oo-ui-actionFieldLayout .demo-link-example, .oo-ui-fieldLayout-align-top.oo-ui-actionFieldLayout .demo-console-toggle, +.oo-ui-fieldLayout-align-left .demo-link-example, .oo-ui-fieldLayout-align-left .demo-console-toggle, +.oo-ui-fieldLayout-align-right .demo-link-example, .oo-ui-fieldLayout-align-right .demo-console-toggle { margin-right: -2em; } @@ -344,8 +384,26 @@ border-width: 1px; } + .demo-console-expanded { + width: calc( 100% + 2.5em ); /* equals `100%` + `padding` of `.demo-container` above */ + } + + .demo-link-example, .demo-console-toggle { - margin-right: -2em; + margin-right: -2.5em; + } + + .demo-console-expanded .demo-console-toggle { + margin-right: 0; + } + + .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-link-example, + .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-console-toggle { + margin-top: 0; + } + + .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-console-expanded .demo-console-toggle { + margin-top: -0.5em; } @supports ( position: fixed ) { @@ -380,14 +438,14 @@ .demo-container.oo-ui-panelLayout { margin-top: 7.5em; } + + .demo-console-expanded .demo-console-toggle { + margin-right: 0.5em; + } } } -@media ( min-width: 768px ) { - .demo-console-toggle { - margin-right: 0; - } -} +/* @media ( min-width: 768px ) {} */ @media ( min-width: 960px ) { body { @@ -405,6 +463,25 @@ white-space: nowrap; } + .demo-console-expanded { + width: 100%; + } + + .demo-link-example, + .demo-console-toggle, + .demo-console-expanded .demo-console-toggle { + margin-right: 0; + } + + .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-link-example, + .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-console-toggle { + margin-top: -2.5em; + } + + .oo-ui-fieldsetLayout-group > .oo-ui-fieldLayout:first-child .demo-console-expanded .demo-console-toggle { + margin-top: -3em; + } + @supports ( position: fixed ) { .demo-menu { min-height: 4.5em; diff --git a/oojs/oojs-ui/i18n/jv.json b/oojs/oojs-ui/i18n/jv.json index 25aff68..5ade015 100644 --- a/oojs/oojs-ui/i18n/jv.json +++ b/oojs/oojs-ui/i18n/jv.json @@ -16,9 +16,9 @@ "ooui-toolgroup-collapse": "Sacukupé", "ooui-dialog-message-accept": "Oké", "ooui-dialog-message-reject": "Wurung", - "ooui-dialog-process-error": "Ana sing klèru", + "ooui-dialog-process-error": "Ana sing salah", "ooui-dialog-process-dismiss": "Tutup", - "ooui-dialog-process-retry": "Jajal manèh", + "ooui-dialog-process-retry": "Jajalen manèh", "ooui-dialog-process-continue": "Bacutaké", "ooui-selectfile-button-select": "Pilih barkas", "ooui-selectfile-not-supported": "Ora bisa milih barkas", diff --git a/oojs/oojs-ui/package.json b/oojs/oojs-ui/package.json index 7a18873..fb48d4d 100644 --- a/oojs/oojs-ui/package.json +++ b/oojs/oojs-ui/package.json @@ -1,6 +1,6 @@ { "name": "oojs-ui", - "version": "0.22.1", + "version": "0.22.2", "description": "User interface classes built on the OOjs framework.", "keywords": [ "oojs-plugin", @@ -49,6 +49,7 @@ "grunt-stylelint": "0.8.0", "grunt-svg2png": "git://github.com/jdforrester/grunt-svg2png.git#v0.2.7-wmf.1", "grunt-tyops": "0.1.0", + "javascript-stringify": "1.6.0", "karma": "1.1.1", "karma-chrome-launcher": "1.0.1", "karma-coverage": "1.1.0", diff --git a/oojs/oojs-ui/php/mixins/TabIndexedElement.php b/oojs/oojs-ui/php/mixins/TabIndexedElement.php index ecf884a..ec88335 100644 --- a/oojs/oojs-ui/php/mixins/TabIndexedElement.php +++ b/oojs/oojs-ui/php/mixins/TabIndexedElement.php @@ -22,8 +22,8 @@ /** * @param array $config Configuration options - * @param number|null $config['tabIndex'] Tab index value. Use 0 to use default ordering, use -1 to - * prevent tab focusing, use null to suppress the `tabindex` attribute. (default: 0) + * @param string|number|null $config['tabIndex'] Tab index value. Use 0 to use default ordering, + * use -1 to prevent tab focusing, use null to suppress the `tabindex` attribute. (default: 0) */ public function initializeTabIndexedElement( array $config = [] ) { // Properties @@ -43,11 +43,11 @@ /** * Set tab index value. * - * @param number|null $tabIndex Tab index value or null for no tab index + * @param string|number|null $tabIndex Tab index value or null for no tab index * @return $this */ public function setTabIndex( $tabIndex ) { - $tabIndex = is_numeric( $tabIndex ) ? $tabIndex : null; + $tabIndex = preg_match( '/^-?\d+$/', $tabIndex ) ? (int)$tabIndex : null; if ( $this->tabIndex !== $tabIndex ) { $this->tabIndex = $tabIndex; -- To view, visit https://gerrit.wikimedia.org/r/362144 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: If6a7aa1d924b416fc95831e1a1b26313e6482cbf Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/vendor Gerrit-Branch: master Gerrit-Owner: Jforrester <jforres...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits