Convert EditPage buttons, checkboxes and summary input to OOUI
authorFlorian <florian.schmidt.stargatewissen@gmail.com>
Fri, 14 Aug 2015 18:07:35 +0000 (20:07 +0200)
committerJforrester <jforrester@wikimedia.org>
Wed, 12 Apr 2017 01:35:24 +0000 (01:35 +0000)
Several methods now have a new implementation using OOjs UI widgets
(ButtonInputWidget/ButtonWidget, CheckboxInputWidget, TextInputWidget).
The existing (public) methods are unchanged. The OOjs UI version is
used by default.

Because this change can cause problems for extensions and on-wiki
scripts depending on the exact HTML, the old version is still available
and can be used by setting $wgOOUIEditPage = false; in LocalSettings.php.
This will be removed later and OOjs UI will become the only option.
To make testing easier, users can also force either mode by adding
&ooui=true or &ooui=false to the action=edit URL.

* EditPage::getSummaryInput() and EditPage::getSummaryInputOOUI()
* EditPage::getCheckboxes() and EditPage::getCheckboxesOOUI()
* EditPage::getCancelLink()
* EditPage::getEditButtons()

Bug: T111088
Co-Authored-By: Amir Sarabadani <ladsgroup@gmail.com>
Co-Authored-By: Florian Schmidt <florian.schmidt.welzow@t-online.de>
Change-Id: I25aa78ac59082789938ecfb5878eb16614392995

RELEASE-NOTES-1.29
includes/DefaultSettings.php
includes/EditPage.php
resources/src/mediawiki.action/mediawiki.action.edit.js
resources/src/mediawiki.action/mediawiki.action.edit.styles.css
resources/src/mediawiki.skinning/interface.css

index e86ab1c..2c19452 100644 (file)
@@ -63,6 +63,13 @@ production.
 * Completely new user interface for the RecentChanges page, which
   structures filters into user-friendly groups.  This has corresponding
   changes to how filters are registered by core and extensions.
+* The edit form now uses pretty OOjs UI buttons, checkboxes and summary input.
+  Because this change can cause problems for extensions and on-wiki
+  scripts depending on the exact HTML, the old version is still available
+  and can be used by setting $wgOOUIEditPage = false; in LocalSettings.php.
+  This will be removed later and OOjs UI will become the only option.
+  To make testing easier, users can also force either mode by adding
+  &ooui=true or &ooui=false to the action=edit URL.
 
 === External library changes in 1.29 ===
 
index 53a147b..d3171a8 100644 (file)
@@ -3216,6 +3216,14 @@ $wgHTMLFormAllowTableFormat = true;
  */
 $wgUseMediaWikiUIEverywhere = false;
 
+/**
+ * Temporary variable that determines whether the EditPage class should use OOjs UI or not.
+ * This will be removed later and OOjs UI will become the only option.
+ *
+ * @since 1.29
+ */
+$wgOOUIEditPage = true;
+
 /**
  * Whether to label the store-to-database-and-show-to-others button in the editor
  * as "Save page"/"Save changes" if false (the default) or, if true, instead as
index 7bdc3bc..c19ed5e 100644 (file)
@@ -413,10 +413,17 @@ class EditPage {
         */
        private $isOldRev = false;
 
+       /**
+        * @var bool Whether OOUI should be enabled here
+        */
+       private $oouiEnabled = false;
+
        /**
         * @param Article $article
         */
        public function __construct( Article $article ) {
+               global $wgOOUIEditPage;
+
                $this->mArticle = $article;
                $this->page = $article->getPage(); // model object
                $this->mTitle = $article->getTitle();
@@ -426,6 +433,8 @@ class EditPage {
 
                $handler = ContentHandler::getForModelID( $this->contentModel );
                $this->contentFormat = $handler->getDefaultFormat();
+
+               $this->oouiEnabled = $wgOOUIEditPage;
        }
 
        /**
@@ -476,6 +485,14 @@ class EditPage {
                }
        }
 
+       /**
+        * Check if the edit page is using OOUI controls
+        * @return bool
+        */
+       public function isOouiEnabled() {
+               return $this->oouiEnabled;
+       }
+
        /**
         * Returns if the given content model is editable.
         *
@@ -843,6 +860,9 @@ class EditPage {
        public function importFormData( &$request ) {
                global $wgContLang, $wgUser;
 
+               # Allow users to change the mode for testing
+               $this->oouiEnabled = $request->getFuzzyBool( 'ooui', $this->oouiEnabled );
+
                # Section edit can come from either the form or a link
                $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
 
@@ -2637,6 +2657,7 @@ class EditPage {
                $wgOut->addHTML( Html::openElement(
                        'form',
                        [
+                               'class' => $this->oouiEnabled ? 'mw-editform-ooui' : 'mw-editform-legacy',
                                'id' => self::EDITFORM_ID,
                                'name' => self::EDITFORM_ID,
                                'method' => 'post',
@@ -2736,6 +2757,11 @@ class EditPage {
                $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
                $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
 
+               // following functions will need OOUI, enable it only once; here.
+               if ( $this->oouiEnabled ) {
+                       $wgOut->enableOOUI();
+               }
+
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
                        $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
@@ -3007,6 +3033,24 @@ class EditPage {
                $this->showHeaderCopyrightWarning();
        }
 
+       /**
+        * Helper function for summary input functions, which returns the neccessary
+        * attributes for the input.
+        *
+        * @param array|null $inputAttrs Array of attrs to use on the input
+        * @return array
+        */
+       private function getSummaryInputAttributes( array $inputAttrs = null ) {
+               // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
+               return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
+                       'id' => 'wpSummary',
+                       'maxlength' => '200',
+                       'tabindex' => '1',
+                       'size' => 60,
+                       'spellcheck' => 'true',
+               ] + Linker::tooltipAndAccesskeyAttribs( 'summary' );
+       }
+
        /**
         * Standard summary input and label (wgSummary), abstracted so EditPage
         * subclasses may reorganize the form.
@@ -3024,14 +3068,7 @@ class EditPage {
        public function getSummaryInput( $summary = "", $labelText = null,
                $inputAttrs = null, $spanLabelAttrs = null
        ) {
-               // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
-               $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
-                       'id' => 'wpSummary',
-                       'maxlength' => '200',
-                       'tabindex' => '1',
-                       'size' => 60,
-                       'spellcheck' => 'true',
-               ] + Linker::tooltipAndAccesskeyAttribs( 'summary' );
+               $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
 
                $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
                        'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
@@ -3053,6 +3090,34 @@ class EditPage {
                return [ $label, $input ];
        }
 
+       /**
+        * Same as self::getSummaryInput, but uses OOUI, instead of plain HTML.
+        * Builds a standard summary input with a label.
+        *
+        * @param string $summary The value of the summary input
+        * @param string $labelText The html to place inside the label
+        * @param array $inputAttrs Array of attrs to use on the input
+        *
+        * @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
+        */
+       function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
+               $inputAttrs = OOUI\Element::configFromHtmlAttributes(
+                       $this->getSummaryInputAttributes( $inputAttrs )
+               );
+
+               return new OOUI\FieldLayout(
+                       new OOUI\TextInputWidget( [
+                               'value' => $summary,
+                       ] + $inputAttrs ),
+                       [
+                               'label' => new OOUI\HtmlSnippet( $labelText ),
+                               'align' => 'top',
+                               'id' => 'wpSummaryLabel',
+                               'classes' => [ $this->missingSummary ? 'mw-summarymissed' : 'mw-summary' ],
+                       ]
+               );
+       }
+
        /**
         * @param bool $isSubjectPreview True if this is the section subject/title
         *   up top, or false if this is the comment summary
@@ -3073,14 +3138,23 @@ class EditPage {
                                return;
                        }
                }
+
                $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
-               list( $label, $input ) = $this->getSummaryInput(
-                       $summary,
-                       $labelText,
-                       [ 'class' => $summaryClass ],
-                       []
-               );
-               $wgOut->addHTML( "{$label} {$input}" );
+               if ( $this->oouiEnabled ) {
+                       $wgOut->addHTML( $this->getSummaryInputOOUI(
+                               $summary,
+                               $labelText,
+                               [ 'class' => $summaryClass ]
+                       ) );
+               } else {
+                       list( $label, $input ) = $this->getSummaryInput(
+                               $summary,
+                               $labelText,
+                               [ 'class' => $summaryClass ]
+                       );
+                       $wgOut->addHTML( "{$label} {$input}" );
+               }
+
        }
 
        /**
@@ -3490,9 +3564,21 @@ HTML
                        $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
                }
 
-               $checkboxes = $this->getCheckboxes( $tabindex,
-                       [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ] );
-               $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
+               if ( $this->oouiEnabled ) {
+                       $checkboxes = $this->getCheckboxesOOUI(
+                               $tabindex,
+                               [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
+                       );
+                       $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
+               } else {
+                       $checkboxes = $this->getCheckboxes(
+                               $tabindex,
+                               [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
+                       );
+                       $checkboxesHTML = implode( $checkboxes, "\n" );
+               }
+
+               $wgOut->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
 
                // Show copyright warning.
                $wgOut->addWikiText( $this->getCopywarn() );
@@ -3580,13 +3666,22 @@ HTML
                } elseif ( $this->getContextTitle()->isRedirect() ) {
                        $cancelParams['redirect'] = 'no';
                }
-
-               return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
-                       $this->getContextTitle(),
-                       new HtmlArmor( $this->context->msg( 'cancel' )->parse() ),
-                       Html::buttonAttributes( [ 'id' => 'mw-editform-cancel' ], [ 'mw-ui-quiet' ] ),
-                       $cancelParams
-               );
+               if ( $this->oouiEnabled ) {
+                       return new OOUI\ButtonWidget( [
+                               'id' => 'mw-editform-cancel',
+                               'href' => $this->getContextTitle()->getLinkUrl( $cancelParams ),
+                               'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
+                               'framed' => false,
+                               'flags' => 'destructive',
+                       ] );
+               } else {
+                       return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                               $this->getContextTitle(),
+                               new HtmlArmor( $this->context->msg( 'cancel' )->parse() ),
+                               Html::buttonAttributes( [ 'id' => 'mw-editform-cancel' ], [ 'mw-ui-quiet' ] ),
+                               $cancelParams
+                       );
+               }
        }
 
        /**
@@ -4062,7 +4157,7 @@ HTML
        }
 
        /**
-        * Returns an array of html code of the following checkboxes:
+        * Returns an array of html code of the following checkboxes old style:
         * minor and watch
         *
         * @param int $tabindex Current tabindex
@@ -4119,6 +4214,68 @@ HTML
                return $checkboxes;
        }
 
+       /**
+        * Returns an array of html code of the following checkboxes:
+        * minor and watch
+        *
+        * @param int $tabindex Current tabindex
+        * @param array $checked Array of checkbox => bool, where bool indicates the checked
+        *                 status of the checkbox
+        *
+        * @return array
+        */
+       public function getCheckboxesOOUI( &$tabindex, $checked ) {
+               $checkboxes = [];
+               $checkboxesDef = $this->getCheckboxesDefinition( $checked );
+
+               $origTabindex = $tabindex;
+
+               foreach ( $checkboxesDef as $name => $options ) {
+                       $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
+
+                       $title = null;
+                       $accesskey = null;
+                       if ( isset( $options['tooltip'] ) ) {
+                               $accesskey = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
+                               $title = Linker::titleAttrib( $options['tooltip'], 'withaccess' );
+                       }
+                       if ( isset( $options['title-message'] ) ) {
+                               $title = $this->context->msg( $options['title-message'] )->text();
+                       }
+                       if ( isset( $options['label-id'] ) ) {
+                               $labelAttribs['id'] = $options['label-id'];
+                       }
+
+                       $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
+                               new OOUI\CheckboxInputWidget( [
+                                       'tabIndex' => ++$tabindex,
+                                       'accessKey' => $accesskey,
+                                       'id' => $options['id'],
+                                       'name' => $name,
+                                       'selected' => $options['default'],
+                               ] ),
+                               [
+                                       'align' => 'inline',
+                                       'label' => new OOUI\HtmlSnippet( $this->context->msg( $options['label-message'] )->parse() ),
+                                       'title' => $title,
+                                       'id' => isset( $options['label-id'] ) ? $options['label-id'] : null,
+                               ]
+                       );
+               }
+
+               // Backwards-compatibility hack to run the EditPageBeforeEditChecks hook. It's important,
+               // people have used it for the weirdest things completely unrelated to checkboxes...
+               // And if we're gonna run it, might as well allow its legacy checkboxes to be shown.
+               $legacyCheckboxes = $this->getCheckboxes( $origTabindex, $checked );
+               foreach ( $legacyCheckboxes as $name => $html ) {
+                       if ( $html && !isset( $checkboxes[$name] ) ) {
+                               $checkboxes[$name] = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $html ) ] );
+                       }
+               }
+
+               return $checkboxes;
+       }
+
        /**
         * Returns an array of html code of the following buttons:
         * save, diff and preview
@@ -4144,31 +4301,56 @@ HTML
                        'name' => 'wpSave',
                        'tabindex' => ++$tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'save' );
-               $buttons['save'] = Html::submitButton(
-                       $this->context->msg( $buttonLabelKey )->text(),
-                       $attribs,
-                       [ 'mw-ui-progressive' ]
-               );
+
+               if ( $this->oouiEnabled ) {
+                       $saveConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
+                       $buttons['save'] = new OOUI\ButtonInputWidget( [
+                               'flags' => [ 'constructive', 'primary' ],
+                               'label' => $this->context->msg( $buttonLabelKey )->text(),
+                               'type' => 'submit',
+                       ] + $saveConfig );
+               } else {
+                       $buttons['save'] = Html::submitButton(
+                               $this->context->msg( $buttonLabelKey )->text(),
+                               $attribs,
+                               [ 'mw-ui-progressive' ]
+                       );
+               }
 
                $attribs = [
                        'id' => 'wpPreview',
                        'name' => 'wpPreview',
                        'tabindex' => ++$tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'preview' );
-               $buttons['preview'] = Html::submitButton(
-                       $this->context->msg( 'showpreview' )->text(),
-                       $attribs
-               );
-
+               if ( $this->oouiEnabled ) {
+                       $previewConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
+                       $buttons['preview'] = new OOUI\ButtonInputWidget( [
+                               'label' => $this->context->msg( 'showpreview' )->text(),
+                               'type' => 'submit'
+                       ] + $previewConfig );
+               } else {
+                       $buttons['preview'] = Html::submitButton(
+                               $this->context->msg( 'showpreview' )->text(),
+                               $attribs
+                       );
+               }
                $attribs = [
                        'id' => 'wpDiff',
                        'name' => 'wpDiff',
                        'tabindex' => ++$tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'diff' );
-               $buttons['diff'] = Html::submitButton(
-                       $this->context->msg( 'showdiff' )->text(),
-                       $attribs
-               );
+               if ( $this->oouiEnabled ) {
+                       $diffConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
+                       $buttons['diff'] = new OOUI\ButtonInputWidget( [
+                               'label' => $this->context->msg( 'showdiff' )->text(),
+                               'type' => 'submit',
+                       ] + $diffConfig );
+               } else {
+                       $buttons['diff'] = Html::submitButton(
+                               $this->context->msg( 'showdiff' )->text(),
+                               $attribs
+                       );
+               }
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
index c9834f0..f6a9c54 100644 (file)
@@ -20,7 +20,9 @@
                var editBox, scrollTop, $editForm;
 
                // Make sure edit summary does not exceed byte limit
-               $( '#wpSummary' ).byteLimit( 255 );
+               // TODO: Replace with this when $wgOOUIEditPage is removed:
+               // OO.ui.infuse( 'wpSummary' ).$input.byteLimit( 255 );
+               $( 'input#wpSummary, #wpSummary > input' ).byteLimit( 255 );
 
                // Restore the edit box scroll state following a preview operation,
                // and set up a form submission handler to remember this state.
index d228236..8287264 100644 (file)
        min-height: 5em;
 }
 
+/*
+ * Add a bit of margin space between the preview and the toolbar.
+ * This replaces the ugly <p><br /></p> we used to insert into the page source
+ */
+#wikiPreview.ontop {
+       margin-bottom: 1em;
+}
+
 /* Adjustments to edit form elements */
-.editCheckboxes {
+#editpage-copywarn {
+       font-size: 0.9em;
+}
+
+#wpSummary {
+       display: block;
+       width: 80%;
        margin-bottom: 1em;
 }
 
-.editCheckboxes input:first-child {
-       margin-left: 0;
+/* Adjustments to edit form elements (only when $wgOOUIEditPage is false) */
+.mw-editform-legacy .editCheckboxes {
+       margin-bottom: 1em;
 }
 
-.cancelLink {
-       margin-left: 0.5em;
+.mw-editform-legacy .editCheckboxes input:first-child {
+       margin-left: 0;
 }
 
-#editpage-copywarn {
-       font-size: 0.9em;
+.mw-editform-legacy .cancelLink {
+       margin-left: 0.5em;
 }
 
-input#wpSummary {
-       display: block;
+.mw-editform-legacy input#wpSummary {
        background-color: #fff;
        color: #000;
-       width: 80%;
        margin-top: 0;
-       margin-bottom: 1em;
        padding: 0.625em 0.546875em 0.546875em;
        border: 1px solid #a2a9b1;
        border-radius: 2px;
@@ -47,21 +59,43 @@ input#wpSummary {
        transition: border-color 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 ), box-shadow 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 );
 }
 
-input#wpSummary:focus,
-input#wpSummary:active {
+.mw-editform-legacy input#wpSummary:focus,
+.mw-editform-legacy input#wpSummary:active {
        outline: 0;
        border-color: #36c;
        box-shadow: inset 0 0 0 1px #36c;
 }
 
-.editButtons input:first-child {
+.mw-editform-legacy .editButtons input:first-child {
        margin-left: 0.1em;
 }
 
-/*
- * Add a bit of margin space between the preview and the toolbar.
- * This replaces the ugly <p><br /></p> we used to insert into the page source
- */
-#wikiPreview.ontop {
-       margin-bottom: 1em;
+/* Adjustments to edit form elements (only when $wgOOUIEditPage is true) */
+.mw-editform-ooui #editpage-copywarn {
+       line-height: 1.26;
+}
+
+.mw-editform-ooui #wpSummary {
+       max-width: none;
+}
+
+.mw-editform-ooui #wpSummaryLabel {
+       margin: 0;
+}
+
+.mw-editform-ooui .editCheckboxes .oo-ui-fieldLayout {
+       margin-right: 1em;
+}
+
+.mw-editform-ooui .editHelp {
+       margin-left: 0.5em;
+       vertical-align: middle;
+}
+
+.mw-editform-ooui .editHelp a {
+       font-weight: bold;
+}
+
+.mw-editform-ooui .editOptions {
+       border-radius: 0 0 2px 2px;
 }
index 2be3bb2..c6d5082 100644 (file)
@@ -21,7 +21,7 @@ textarea {
 }
 
 .editOptions {
-       background-color: #f8f9fa;
+       background-color: #eaecf0;
        border: 1px solid #c8ccd1;
        border-top: 0;
        padding: 1em 1em 1.5em 1em;