Merge "ApiSandbox: Move labels outside progress bars"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 23 May 2019 17:56:06 +0000 (17:56 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 23 May 2019 17:56:06 +0000 (17:56 +0000)
includes/DefaultSettings.php
includes/api/ApiQueryRevisions.php
includes/specials/SpecialSearch.php
includes/widget/SearchInputWidget.php
includes/widget/search/SearchFormWidget.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.special.apisandbox/apisandbox.js
resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.css [new file with mode: 0644]
resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.js [new file with mode: 0644]

index 9efcfb4..de94f77 100644 (file)
@@ -9083,6 +9083,17 @@ $wgReportToEndpoints = [];
  */
 $wgFeaturePolicyReportOnly = [];
 
+/**
+ * Options for Special:Search completion widget form created by SearchFormWidget class.
+ * Settings that can be used:
+ * - showDescriptions: true/false - whether to show opensearch description results
+ * - performSearchOnClick:  true/false - whether to perform search on click
+ * See also TitleWidget.js UI widget.
+ * @since 1.34
+ * @var array
+ */
+$wgSpecialSearchFormOptions = [];
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index fc50289..e60835b 100644 (file)
@@ -114,7 +114,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
 
                if ( $revCount > 0 && $enumRevMode ) {
                        $this->dieWithError(
-                               [ 'apierror-revisions-nolist', $this->getModulePrefix() ], 'invalidparammix'
+                               [ 'apierror-revisions-norevids', $this->getModulePrefix() ], 'invalidparammix'
                        );
                }
 
index 4adc247..ed83aaf 100644 (file)
@@ -290,6 +290,7 @@ class SpecialSearch extends SpecialPage {
                }
 
                $out = $this->getOutput();
+               $widgetOptions = $this->getConfig()->get( 'SpecialSearchFormOptions' );
                $formWidget = new MediaWiki\Widget\Search\SearchFormWidget(
                        $this,
                        $this->searchConfig,
@@ -308,7 +309,7 @@ class SpecialSearch extends SpecialPage {
                        // only do the form render here for the empty $term case. Rendering
                        // the form when a search is provided is repeated below.
                        $out->addHTML( $formWidget->render(
-                               $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch()
+                               $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch(), $widgetOptions
                        ) );
                        return;
                }
@@ -365,7 +366,7 @@ class SpecialSearch extends SpecialPage {
                // start rendering the page
                $out->enableOOUI();
                $out->addHTML( $formWidget->render(
-                       $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch()
+                       $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch(), $widgetOptions
                ) );
 
                // did you mean... suggestions
index d4ffed2..2f0c23d 100644 (file)
@@ -14,6 +14,7 @@ class SearchInputWidget extends TitleInputWidget {
        protected $validateTitle = false;
        protected $highlightFirst = false;
        protected $dataLocation = 'header';
+       protected $showDescriptions = false;
 
        /**
         * @param array $config Configuration options
@@ -41,6 +42,10 @@ class SearchInputWidget extends TitleInputWidget {
                        $this->dataLocation = $config['dataLocation'];
                }
 
+               if ( !empty( $config['showDescriptions'] ) ) {
+                       $this->showDescriptions = true;
+               }
+
                // Initialization
                $this->addClasses( [ 'mw-widget-searchInputWidget' ] );
        }
@@ -58,6 +63,9 @@ class SearchInputWidget extends TitleInputWidget {
                if ( $this->dataLocation ) {
                        $config['dataLocation'] = $this->dataLocation;
                }
+               if ( $this->showDescriptions ) {
+                       $config['showDescriptions'] = true;
+               }
                $config['$overlay'] = true;
                return parent::getConfig( $config );
        }
index 7c28b5e..62ee9cb 100644 (file)
@@ -40,6 +40,7 @@ class SearchFormWidget {
         * @param int $totalResults The total estimated results found
         * @param int $offset Current offset in search results
         * @param bool $isPowerSearch Is the 'advanced' section open?
+        * @param array $options Widget options
         * @return string HTML
         */
        public function render(
@@ -48,7 +49,8 @@ class SearchFormWidget {
                $numResults,
                $totalResults,
                $offset,
-               $isPowerSearch
+               $isPowerSearch,
+               array $options = []
        ) {
                $user = $this->specialSearch->getUser();
 
@@ -63,7 +65,7 @@ class SearchFormWidget {
                                ]
                        ) .
                                '<div id="mw-search-top-table">' .
-                                       $this->shortDialogHtml( $profile, $term, $numResults, $totalResults, $offset ) .
+                                       $this->shortDialogHtml( $profile, $term, $numResults, $totalResults, $offset, $options ) .
                                '</div>' .
                                "<div class='mw-search-visualclear'></div>" .
                                "<div class='mw-search-profile-tabs'>" .
@@ -81,12 +83,20 @@ class SearchFormWidget {
         * @param int $numResults The number of results shown
         * @param int $totalResults The total estimated results found
         * @param int $offset Current offset in search results
+        * @param array $options Widget options
         * @return string HTML
         */
-       protected function shortDialogHtml( $profile, $term, $numResults, $totalResults, $offset ) {
+       protected function shortDialogHtml(
+               $profile,
+               $term,
+               $numResults,
+               $totalResults,
+               $offset,
+               array $options = []
+       ) {
                $html = '';
 
-               $searchWidget = new SearchInputWidget( [
+               $searchWidget = new SearchInputWidget( $options + [
                        'id' => 'searchText',
                        'name' => 'search',
                        'autofocus' => trim( $term ) === '',
index 71f5633..0dd9fe0 100644 (file)
        "mw-widgets-abandonedit-discard": "Discard edits",
        "mw-widgets-abandonedit-keep": "Continue editing",
        "mw-widgets-abandonedit-title": "Are you sure?",
+       "mw-widgets-copytextlayout-copy": "Copy",
+       "mw-widgets-copytextlayout-copy-fail": "Failed to copy to clipboard.",
+       "mw-widgets-copytextlayout-copy-success": "Copied to clipboard.",
        "mw-widgets-dateinput-no-date": "No date selected",
        "mw-widgets-dateinput-placeholder-day": "YYYY-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "YYYY-MM",
index 10fdb84..683e975 100644 (file)
        "mw-widgets-abandonedit-discard": "Text shown on the button which closes an editor and discards changes when the user confirms that they want to leave the editor.\n\nPreceded by the prompt {{msg-mw|mw-widgets-abandonedit}}.\n\nFollowed by the button {{msg-mw|mw-widgets-abandonedit-keep}}.",
        "mw-widgets-abandonedit-keep": "Text shown on the button which does not do anything when the user decides that they do not want to leave the editor.\n\nPreceded by the button {{msg-mw|mw-widgets-abandonedit-discard}}.",
        "mw-widgets-abandonedit-title": "Title of the dialog shown when the user tries to leave the editor without saving their changes.\n\nFollowed by the following buttons:\n* {{msg-mw|mw-widgets-abandonedit-discard}}\n* {{msg-mw|mw-widgets-abandonedit-keep}}\n{{Identical|Are you sure?}}",
+       "mw-widgets-copytextlayout-copy": "Label of button to copy text to clipboard.",
+       "mw-widgets-copytextlayout-copy-fail": "Message shown when copying to clipboard failed.",
+       "mw-widgets-copytextlayout-copy-success": "Message shown when copying to clipboard failed.",
        "mw-widgets-dateinput-no-date": "Label of a date input field when no date has been selected.",
        "mw-widgets-dateinput-placeholder-day": "[[File:DateInputWidget active, empty.png|frame|Screenshot]]\nPlaceholder displayed in a date input field when it's empty, representing a date format with 4 digits for year, 2 digits for month, and 2 digits for day, separated with hyphens. This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
        "mw-widgets-dateinput-placeholder-month": "Placeholder displayed in a date input field when it's empty, representing a date format with 4 digits for year and 2 digits for month, separated with hyphens (without a day). This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
index 4c359ee..1b8cffb 100644 (file)
@@ -2043,6 +2043,7 @@ return [
                        'oojs-ui.styles.icons-editing-advanced',
                        'oojs-ui.styles.icons-interactions',
                        'oojs-ui.styles.icons-moderation',
+                       'mediawiki.widgets',
                        'mediawiki.widgets.datetime',
                        'jquery.makeCollapsible',
                ],
@@ -2503,12 +2504,16 @@ return [
                'scripts' => [
                        'resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.ComplexNamespaceInputWidget.js',
+                       'resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleSearchWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.ComplexTitleInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js',
                ],
+               'styles' => [
+                       'resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.css',
+               ],
                'skinStyles' => [
                        'default' => [
                                'resources/src/mediawiki.widgets/mw.widgets.TitleWidget.less',
@@ -2522,11 +2527,17 @@ return [
                        'mediawiki.Title',
                        'mediawiki.api',
                        'mediawiki.String',
+                       // CopyTextLayout
+                       'mediawiki.notify',
                ],
                'messages' => [
                        // NamespaceInputWidget
                        'blanknamespace',
                        'namespacesall',
+                       // CopyTextLayout
+                       'mw-widgets-copytextlayout-copy',
+                       'mw-widgets-copytextlayout-copy-fail',
+                       'mw-widgets-copytextlayout-copy-success',
                        // TitleInputWidget
                        'mw-widgets-titleinput-description-new-page',
                        'mw-widgets-titleinput-description-redirect',
index f56c0db..631a5c6 100644 (file)
                 * @return {OO.ui.MenuOptionWidget[]} Each item's data should be an OO.ui.FieldLayout
                 */
                formatRequest: function ( displayParams, rawParams ) {
-                       var jsonInput,
+                       var jsonLayout,
                                items = [
                                        new OO.ui.MenuOptionWidget( {
                                                label: Util.parseMsg( 'apisandbox-request-format-url-label' ),
-                                               data: new OO.ui.FieldLayout(
-                                                       new OO.ui.TextInputWidget( {
-                                                               readOnly: true,
-                                                               value: mw.util.wikiScript( 'api' ) + '?' + $.param( displayParams )
-                                                       } ), {
-                                                               label: Util.parseMsg( 'apisandbox-request-url-label' )
-                                                       }
-                                               )
+                                               data: new mw.widgets.CopyTextLayout( {
+                                                       label: Util.parseMsg( 'apisandbox-request-url-label' ),
+                                                       copyText: mw.util.wikiScript( 'api' ) + '?' + $.param( displayParams )
+                                               } )
                                        } ),
                                        new OO.ui.MenuOptionWidget( {
                                                label: Util.parseMsg( 'apisandbox-request-format-json-label' ),
-                                               data: new OO.ui.FieldLayout(
-                                                       jsonInput = new OO.ui.MultilineTextInputWidget( {
+                                               data: jsonLayout = new mw.widgets.CopyTextLayout( {
+                                                       label: Util.parseMsg( 'apisandbox-request-json-label' ),
+                                                       copyText: JSON.stringify( displayParams, null, '\t' ),
+                                                       multiline: true,
+                                                       textInput: {
                                                                classes: [ 'mw-apisandbox-textInputCode' ],
-                                                               readOnly: true,
                                                                autosize: true,
-                                                               maxRows: 6,
-                                                               value: JSON.stringify( displayParams, null, '\t' )
-                                                       } ), {
-                                                               label: Util.parseMsg( 'apisandbox-request-json-label' )
+                                                               maxRows: 6
                                                        }
-                                               ).on( 'toggle', function ( visible ) {
+                                               ).on( 'toggle', function ( visible ) {
                                                        if ( visible ) {
                                                                // Call updatePosition instead of adjustSize
                                                                // because the latter has weird caching
                                                                // behavior and the former bypasses it.
-                                                               jsonInput.updatePosition();
+                                                               jsonLayout.textInput.updatePosition();
                                                        }
                                                } )
                                        } )
diff --git a/resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.css b/resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.css
new file mode 100644 (file)
index 0000000..f09128a
--- /dev/null
@@ -0,0 +1,19 @@
+/*!
+ * MediaWiki Widgets – CopyTextLayout styles.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+.mw-widget-copyTextLayout > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+       /* TODO: This should be upstreamed to OOUI */
+       max-width: 50em;
+}
+
+.mw-widget-copyTextLayout-multiline-button {
+       display: block;
+       max-width: 50em;
+       margin-top: 0.5em;
+       /* Float to right of inline help */
+       float: right;
+}
diff --git a/resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.js b/resources/src/mediawiki.widgets/mw.widgets.CopyTextLayout.js
new file mode 100644 (file)
index 0000000..65e7eb7
--- /dev/null
@@ -0,0 +1,114 @@
+/*!
+ * MediaWiki Widgets - CopyTextLayout class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function () {
+
+       /**
+        * An action field layout containing some readonly text and a button to copy
+        * it to the clipboard.
+        *
+        * @class
+        * @extends OO.ui.ActionFieldLayout
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {string} copyText Text to copy, can also be provided as textInput.value
+        * @cfg {Object} textInput Config for text input
+        * @cfg {Object} button Config for button
+        * @cfg {string} successMessage Success message,
+        *  defaults to 'mw-widgets-copytextlayout-copy-success'.
+        * @cfg {string} failMessage Failure message,
+        *  defaults to 'mw-widgets-copytextlayout-copy-fail'.
+        */
+       mw.widgets.CopyTextLayout = function MwWidgetsCopyTextLayout( config ) {
+               var TextClass;
+               config = config || {};
+
+               // Properties
+               TextClass = config.multiline ? OO.ui.MultilineTextInputWidget : OO.ui.TextInputWidget;
+               this.textInput = new TextClass( $.extend( {
+                       value: config.copyText,
+                       readOnly: true
+               }, config.textInput ) );
+               this.button = new OO.ui.ButtonWidget( $.extend( {
+                       label: mw.msg( 'mw-widgets-copytextlayout-copy' ),
+                       icon: 'articles'
+               }, config.button ) );
+               this.successMessage = config.successMessage || mw.msg( 'mw-widgets-copytextlayout-copy-success' );
+               this.failMessage = config.failMessage || mw.msg( 'mw-widgets-copytextlayout-copy-fail' );
+
+               // Parent constructor
+               mw.widgets.CopyTextLayout.super.call( this, this.textInput, this.button, config );
+
+               // HACK: Remove classes which connect widgets when using
+               // a multiline text input. TODO: This should be handled in OOUI.
+               if ( config.multiline ) {
+                       this.$input.removeClass( 'oo-ui-actionFieldLayout-input' );
+                       this.$button
+                               .removeClass( 'oo-ui-actionFieldLayout-button' )
+                               .addClass( 'mw-widget-copyTextLayout-multiline-button' );
+               }
+
+               // Events
+               this.button.connect( this, { click: 'onButtonClick' } );
+               this.textInput.$input.on( 'click', this.onInputClick.bind( this ) );
+
+               this.$element.addClass( 'mw-widget-copyTextLayout' );
+       };
+
+       /* Inheritence */
+
+       OO.inheritClass( mw.widgets.CopyTextLayout, OO.ui.ActionFieldLayout );
+
+       /* Methods */
+
+       /**
+        * Handle button click events
+        *
+        * @fires copy
+        */
+       mw.widgets.CopyTextLayout.prototype.onButtonClick = function () {
+               var copied;
+
+               this.selectText();
+
+               try {
+                       copied = document.execCommand( 'copy' );
+               } catch ( e ) {
+                       copied = false;
+               }
+               if ( copied ) {
+                       mw.notify( this.successMessage );
+               } else {
+                       mw.notify( this.failMessage, { type: 'error' } );
+               }
+
+               this.emit( 'copy', copied );
+       };
+
+       /**
+        * Handle button click events
+        */
+       mw.widgets.CopyTextLayout.prototype.onInputClick = function () {
+               this.selectText();
+       };
+
+       /**
+        * Select the text to copy
+        */
+       mw.widgets.CopyTextLayout.prototype.selectText = function () {
+               var input = this.textInput.$input[ 0 ],
+                       scrollTop = input.scrollTop,
+                       scrollLeft = input.scrollLeft;
+
+               this.textInput.select();
+
+               // Restore scroll position
+               input.scrollTop = scrollTop;
+               input.scrollLeft = scrollLeft;
+       };
+
+}() );