Mollywhite has uploaded a new change for review. https://gerrit.wikimedia.org/r/80677
Change subject: Bug 53105: Make the extension function independently from the schema. ...................................................................... Bug 53105: Make the extension function independently from the schema. This patch makes the extension less reliant on the schema looking exactly like the one at https://meta.wikimedia.org/wiki/Schema:BookManagerv2. It also formats the dates more nicely, with a couple of requirements for the schema, which I will document shortly. Change-Id: I4d9faa38b2d74daf7c787b6ca163bd4d862d7e30 --- M BookManagerv2.hooks.php M BookManagerv2.i18n.php M JsonEditor.php M modules/ext.BookManagerv2.editor.css M schemas/bookschema.json 5 files changed, 217 insertions(+), 157 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/BookManagerv2 refs/changes/77/80677/1 diff --git a/BookManagerv2.hooks.php b/BookManagerv2.hooks.php index 937ab60..8b866a2 100644 --- a/BookManagerv2.hooks.php +++ b/BookManagerv2.hooks.php @@ -24,7 +24,6 @@ */ class BookManagerv2Hooks { - /** * Validates that the revised contents of an NS_BOOK page are valid JSON. * If not valid, rejects edit with error message. @@ -60,6 +59,7 @@ return true; } + /** * Adds a navigation bar to the page @@ -176,14 +176,14 @@ /** * Creates a list item element with a comma-separated list of the array values * - * @param string $key Name of the JSON property + * @param string $i18n Name of the JSON property * @param array $array Array of strings * @return string HTML list item element */ - public static function addArray( $key, $array ) { + public static function addArray( $i18n, $array ) { global $wgContLang; $output = Html::element( 'li', array(), - wfMessage( 'bookmanagerv2-' . $key ) + wfMessage( $i18n ) ->numParams( count( $array ) ) ->params( $wgContLang->commaList( $array ) ) ->text() ); @@ -193,42 +193,48 @@ /** * Creates a list item element with a string * - * @param string $key Name of the JSON property + * @param string $i18n Name of the JSON property * @param string $string Name of the string value * @return string HTML list item element */ - public static function addString( $key, $string ) { + public static function addString( $i18n, $string ) { $output = Html::element( 'li', array(), - wfMessage( 'bookmanagerv2-' . $key, $string )->text() ); + wfMessage( $i18n, $string )->text() ); return $output; } /** * Creates a list item element with a date. * - * @param int|null $year Year - * @param int|null $month Month - * @param int|null $day Day + * @param string $i18n The key name, to be used to label the value + * @param string $date The date string, which must match the format + * @param string $format The format, which is a string containing 'y', + * 'm', or 'd' in any order. * @return string HTML list item element */ - public static function addDate( $year, $month, $day ) { + public static function addDate( $i18n, $date, $format ) { global $wgLang, $wgUser; + + $year = $month = $day = null; + if ( strlen( $date ) >= 4 && stristr( $format, 'y' ) !== false ) { + $year = substr( $date, 0, 4 ); + $date = substr( $date, 4 ); + } + if ( strlen( $date ) >= 2 && stristr( $format, 'm' ) !== false ) { + $month = substr( $date, 0, 2 ); + $date = substr( $date, 2 ); + } + if ( strlen( $date ) >= 2 && stristr( $format, 'd' ) !== false ) { + $day = substr( $date, 0, 2 ); + } // Basic validation of inputs - if ( !$month || $month < 1 || $month > 12 ) { + if ( !$month || (int)$month < 1 || (int)$month > 12 ) { $month = null; - } else { - $monthStr = str_pad( (string)$month, 2, "0", STR_PAD_LEFT ); } - if ( !$day || $day < 1 || $day > 31 ) { + if ( !$day || (int)$day < 1 || (int)$day > 31 ) { $day = null; - } else { - $dayStr = str_pad( (string)$day, 2, "0", STR_PAD_LEFT ); - } - - if ( $year ) { - $yearStr = str_pad( (string)$year, 4, "0", STR_PAD_LEFT ); } if ( $year && !$month ) { @@ -236,26 +242,36 @@ $date = $year; $datetime = $year; } else if ( $year && $month && !$day ) { - $ts = $yearStr . $monthStr . "01000000"; + $ts = $year . $month . "01000000"; $format = $wgLang->getDateFormatString( 'monthonly', $wgUser->getDatePreference() ? : 'default' ); $date = $wgLang->sprintfDate( $format, $ts ); - $datetime = $yearStr . "-" . $monthStr; + $datetime = $year . "-" . $month; } else { - $ts = $yearStr . $monthStr . $dayStr . "000000"; + $ts = $year . $month . $day . "000000"; $format = $wgLang->getDateFormatString( 'date', $wgUser->getDatePreference() ? : 'default' ); $date = $wgLang->sprintfDate( $format, $ts ); - $datetime = $yearStr . "-" . $monthStr . "-" . $dayStr; + $datetime = $year . "-" . $month . "-" . $day; } $output = Html::openElement( 'li', array() ) . Html::openElement( 'time', array( 'datetime' => $datetime ) ) - . wfMessage( 'bookmanagerv2-publication-date', $date )->text() + . wfMessage( $i18n, $date )->text() . Html::closeElement( 'time' ) . Html::closeElement( 'li' ); return $output; } + /** + * Adds a header to the dropdowns. The header labels the dropdown as + * metadata or contents, and adds [read|edit] links that direct the + * user to the JSON schema page. + * + * @param Title $jsonPageTitle Title object for the JSON page + * @param string $title Localization message key for the title of the + * dropdown + * @return string HTML div element + */ public static function addJsonPageLink( $jsonPageTitle, $title ) { $html = Html::openElement( 'div', array( 'class' => 'mw-bookmanagerv2-dropdown-header' ) ) @@ -327,86 +343,37 @@ * @return string HTML unordered list element */ public static function formatMetadata( $jsonBook, $jsonPageTitle = null ) { + $schema = FormatJson::decode( + file_get_contents( __DIR__ . '/schemas/bookschema.json' ) ); $metadata = ''; if ( $jsonPageTitle !== null ) { $metadata = self::addJsonPageLink( $jsonPageTitle, 'bookmanagerv2-metadata-header' ); } - $metadata .= Html::openElement( 'ul', array() ) - . Html::openElement( 'li', array() ) - . wfMessage( 'bookmanagerv2-title', - $jsonBook->title )->text() - . Html::closeElement( 'li' ); - if ( isset( $jsonBook->alternate_titles ) ) { - $metadata .= self::addArray( "alternate-titles", - $jsonBook->alternate_titles ); - } - if ( isset( $jsonBook->authors ) ) { - $metadata .= self::addArray( "authors", $jsonBook->authors ); - } - if ( isset( $jsonBook->translators ) ) { - $metadata .= self::addArray( "translators", - $jsonBook->translators ); - } - if ( isset( $jsonBook->editors ) ) { - $metadata .= self::addArray( "editors", $jsonBook->editors ); - } - if ( isset( $jsonBook->illustrators ) ) { - $metadata .= self::addArray( "illustrators", - $jsonBook->illustrators ); - } - if ( isset( $jsonBook->subtitle ) ) { - $metadata .= self::addString( "subtitle", $jsonBook->subtitle ); - } - if ( isset( $jsonBook->series_title ) ) { - $metadata .= self::addString( "series-title", - $jsonBook->series_title ); - } - if ( isset( $jsonBook->volume ) ) { - $metadata .= self::addString( "volume", - (string)$jsonBook->volume ); - } - if ( isset( $jsonBook->edition ) ) { - $metadata .= self::addString( "edition", - (string)$jsonBook->edition ); - } - if ( isset( $jsonBook->publisher ) ) { - $metadata .= self::addString( "publisher", $jsonBook->publisher ); - } - if ( isset( $jsonBook->publication_city ) ) { - $metadata .= self::addString( "publication-city", - $jsonBook->publication_city ); - } - if ( isset( $jsonBook->publication_year ) ) { - $year = isset( $jsonBook->publication_year ) ? - $jsonBook->publication_year : null; - $month = isset( $jsonBook->publication_month ) ? - $jsonBook->publication_month : null; - $day = isset( $jsonBook->publication_day ) ? - $jsonBook->publication_day : null; - $metadata .= self::addDate( $year, $month, $day ); - } - if ( isset( $jsonBook->printer ) ) { - $metadata .= self::addString( "printer", $jsonBook->printer ); - } - if ( isset( $jsonBook->language ) ) { - // TODO: Transform the language code to the correct long-form language - $metadata .= self::addString( "language", $jsonBook->language ); - } - if ( isset( $jsonBook->description ) ) { - $metadata .= self::addString( "description", - $jsonBook->description ); - } - if ( isset( $jsonBook->isbn ) ) { - $metadata .= self::addString( "isbn", $jsonBook->isbn ); - } - if ( isset( $jsonBook->lccn ) ) { - $metadata .= self::addString( "lccn", $jsonBook->lccn ); - } - if ( isset( $jsonBook->oclc ) ) { - $metadata .= self::addString( "oclc", $jsonBook->oclc ); - } + $metadata .= Html::openElement( 'ul', array() ); + foreach ( $jsonBook as $key => $val ) { + if ( $key === 'sections' ) { + continue; + } + $schemaVal = $schema->properties->$key; + $type = $schemaVal->type; + $i18n = $schemaVal->additionalProperties->i18n; + + if ( $type === 'string' ) { + if ( isset( $schemaVal->additionalProperties->date_format ) ) { + $format = $schemaVal->additionalProperties->date_format; + $date = $jsonBook->$key; + $metadata .= self::addDate( $i18n, $date, $format ); + } else { + $metadata .= self::addString( $i18n, $jsonBook->$key ); + } + } else if ( $type === 'array' ) { + $metadata .= self::addArray( $i18n, $jsonBook->$key ); + } else if ( $type === 'number' || $type === 'integer' ) { + $metadata .= self::addString( $i18n, (string)$jsonBook->$key ); + } + } $metadata .= Html::closeElement( 'ul' ); return $metadata; diff --git a/BookManagerv2.i18n.php b/BookManagerv2.i18n.php index 22ba901..17051eb 100644 --- a/BookManagerv2.i18n.php +++ b/BookManagerv2.i18n.php @@ -65,9 +65,13 @@ 'bookmanagerv2-edition-field' => 'Edition', 'bookmanagerv2-publisher-field' => 'Publisher', 'bookmanagerv2-printer-field' => 'Printer', + 'bookmanagerv2-publication-date-field' => 'Publication date', 'bookmanagerv2-publication-year-field' => 'Publication year', 'bookmanagerv2-publication-month-field' => 'Publication month', 'bookmanagerv2-publication-day-field' => 'Publication day', + 'bookmanagerv2-year-placeholder' => 'YYYY', + 'bookmanagerv2-month-placeholder' => 'MM', + 'bookmanagerv2-day-placeholder' => 'DD', 'bookmanagerv2-publication-city-field' => 'Publication city', 'bookmanagerv2-language-field' => 'Language', 'bookmanagerv2-description-field' => 'Description', @@ -204,9 +208,13 @@ 'bookmanagerv2-edition-field' => 'JSON editor input field label', 'bookmanagerv2-publisher-field' => 'JSON editor input field label', 'bookmanagerv2-printer-field' => 'JSON editor input field label', + 'bookmanagerv2-publication-date-field' => 'JSON editor input field label', 'bookmanagerv2-publication-year-field' => 'JSON editor input field label', 'bookmanagerv2-publication-month-field' => 'JSON editor input field label', 'bookmanagerv2-publication-day-field' => 'JSON editor input field label', + 'bookmanagerv2-year-placeholder' => 'Short prompt to appear in a year input box in the JSON editor to prompt the user to enter a four-digit year.', + 'bookmanagerv2-month-placeholder' => 'Short prompt to appear in a month input box in the JSON editor to prompt the user to enter a two-digit month.', + 'bookmanagerv2-day-placeholder' => 'Short prompt to appear in a day input box in the JSON editor to prompt the user to enter a two-digit month.', 'bookmanagerv2-publication-city-field' => 'JSON editor input field label', 'bookmanagerv2-language-field' => 'JSON editor input field label', 'bookmanagerv2-description-field' => 'JSON editor input field label', diff --git a/JsonEditor.php b/JsonEditor.php index 70ab037..e8f6d5b 100644 --- a/JsonEditor.php +++ b/JsonEditor.php @@ -39,23 +39,32 @@ ) { $key = htmlentities( $key ); $type = $val->type; - $i18n = $val->additionalProperties->i18n; - switch ( $type ) { - case 'string': - case 'array': + $i18n = $val->additionalProperties->i18n . '-field'; + if ( $type === 'array' ) { + //TODO: Array handling $inputType = 'text'; - break; - case 'number': - case 'integer': + } else if ( $type === 'string' ) { + if ( isset( $val->additionalProperties->date_format ) ) { + $inputType = 'date'; + } else { + $inputType = 'text'; + } + } else if ( $type === 'number' || $type === 'integer' ) { $inputType = 'number'; - break; } - $inputAttributes[ 'type' ] = $type; + $inputAttributes[ 'name' ] = 'json-editor-' . $key; $inputAttributes[ 'id' ] = 'json-editor-' . $key; if ( $val->required ) { $inputAttributes[ 'required' ] = 'required'; } + + if ( $inputType === 'date' ) { + // Handle dates separately + return self::addDateField( $key, $val, $original, $inputAttributes ); + } + + $inputAttributes[ 'type' ] = $type; if ( $inputType === 'text' ) { $inputAttributes[ 'size' ] = 60; } @@ -70,6 +79,88 @@ . Html::closeElement( 'th' ) . Html::openElement( 'td' ) . Html::element( 'input', $inputAttributes, '' ) + . Html::closeElement( 'td' ) + . Html::closeElement( 'tr' ); + return $html; + } + + protected function addDateField( $key, $val, $original, $inputAttributes ) { + global $wgLang, $wgUser; + $inputFormat = $val->additionalProperties->date_format; + $preferenceFormat = $wgLang->getDateFormatString( 'date', $wgUser->getDatePreference() ?: 'default' ); + + $inputAttributes[ 'type' ] = 'number'; + $tabindex = $inputAttributes[ 'tabindex' ]; + $yearInput = $monthInput = $dayInput = null; + + if ( stristr( $inputFormat, 'y' ) !== false ) { + $yearInputAttributes = $inputAttributes; + if ( strlen( $original ) >= 4 ) { + $yearInputAttributes[ 'value' ] = (int)substr( $original, 0, 4 ); + $original = substr( $original, 4 ); + } + $yearInputAttributes[ 'tabindex' ] = $tabindex++; + $yearInputAttributes[ 'size' ] = 4; + $yearInputAttributes[ 'min' ] = 0; + $yearInputAttributes[ 'max' ] = 9999; + $yearInputAttributes[ 'placeholder' ] = wfMessage( + 'bookmanagerv2-year-placeholder' )->escaped(); + $yearInput = Html::element( 'input', $yearInputAttributes, '' ); + } + + if ( stristr( $inputFormat, 'm' ) !== false ) { + $monthInputAttributes = $inputAttributes; + if ( strlen( $original ) >= 2 ) { + $monthInputAttributes[ 'value' ] = (int)substr( $original, 0, 2 ); + $original = substr( $original, 2 ); + } + $monthInputAttributes[ 'tabindex' ] = $tabindex++; + $monthInputAttributes[ 'size' ] = 2; + $monthInputAttributes[ 'min' ] = 1; + $monthInputAttributes[ 'max' ] = 12; + $monthInputAttributes[ 'placeholder' ] = wfMessage( + 'bookmanagerv2-month-placeholder' )->escaped(); + $monthInput = Html::element( 'input', $monthInputAttributes, '' ); + } + + if ( stristr( $inputFormat, 'm' ) !== false ) { + $dayInputAttributes = $inputAttributes; + if ( strlen( $original ) >= 2 ) { + $dayInputAttributes[ 'value' ] = (int)substr( $original, 0, 2 ); + } + $dayInputAttributes[ 'tabindex' ] = $tabindex++; + $dayInputAttributes[ 'size' ] = 2; + $dayInputAttributes[ 'min' ] = 1; + $dayInputAttributes[ 'max' ] = 31; + $dayInputAttributes[ 'placeholder' ] = wfMessage( + 'bookmanagerv2-day-placeholder' )->escaped(); + $dayInput = Html::element( 'input', $dayInputAttributes, '' ); + } + + $yearInd = stripos( $preferenceFormat, 'y' ); + $monthInd = stripos( $preferenceFormat, 'f' ); + $dayInd = stripos( $preferenceFormat, 'j' ); + $dateInputs = null; + + if ( $dayInd < $monthInd && $monthInd < $yearInd ) { + //j F Y (day, month, year) + $dateInputs = $dayInput . $monthInput . $yearInput; + } else if ( $monthInd < $dayInd && $dayInd < $yearInd ) { + //F j, Y (month, day, year) + $dateInputs = $monthInput . $dayInput . $yearInput; + } else { + //Default to Y F j (year, month, day) + $dateInputs = $yearInput . $monthInput . $dayInput; + } + + $html = Html::openElement( 'tr' ) + . Html::openElement( 'th', array( 'scope' => 'row' ) ) + . Html::element( 'label', array( + 'for' => $key ), + wfMessage( $val->additionalProperties->i18n . '-field' )->text() ) + . Html::closeElement( 'th' ) + . Html::openElement( 'td' ) + . $dateInputs . Html::closeElement( 'td' ) . Html::closeElement( 'tr' ); return $html; @@ -140,7 +231,7 @@ foreach ( $schema->properties as $key => $schemaAttribs ) { if ( $key === 'sections' ) { - break; + continue; } $original = null; if ( $originalJson ) { @@ -163,7 +254,13 @@ } } } - $inputAttributes[ 'tabindex' ] = $tabindex++; + if ( isset( $schemaAttribs->additionalProperties->date_format ) ) { + // Allow extra tabindices for dates + $inputAttributes[ 'tabindex' ] = $tabindex; + $tabindex += 3; + } else { + $inputAttributes[ 'tabindex' ] = $tabindex++; + } // Add each property to the form, with the current value if there // is one. diff --git a/modules/ext.BookManagerv2.editor.css b/modules/ext.BookManagerv2.editor.css index 1930528..1eeec7b 100644 --- a/modules/ext.BookManagerv2.editor.css +++ b/modules/ext.BookManagerv2.editor.css @@ -19,6 +19,10 @@ text-align: left; } +table.mw-bookmanagerv2-edit-form td { + padding-left: 10px; +} + h2.mw-bookmanagerv2-sections-heading { margin-top: 10px; } diff --git a/schemas/bookschema.json b/schemas/bookschema.json index e083931..427b1eb 100644 --- a/schemas/bookschema.json +++ b/schemas/bookschema.json @@ -6,7 +6,7 @@ "type": "string", "required": true, "additionalProperties": { - "i18n": "bookmanagerv2-title-field" + "i18n": "bookmanagerv2-title" } }, "alternate_titles": { @@ -20,7 +20,7 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-alternate-titles-field" + "i18n": "bookmanagerv2-alternate-titles" } }, "authors": { @@ -34,7 +34,7 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-authors-field" + "i18n": "bookmanagerv2-authors" } }, "translators": { @@ -48,7 +48,7 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-translators-field" + "i18n": "bookmanagerv2-translators" } }, "editors": { @@ -62,7 +62,7 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-editors-field" + "i18n": "bookmanagerv2-editors" } }, "illustrators": { @@ -76,7 +76,7 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-illustrators-field" + "i18n": "bookmanagerv2-illustrators" } }, "subtitle": { @@ -84,7 +84,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-subtitle-field" + "i18n": "bookmanagerv2-subtitle" } }, "series_title": { @@ -92,7 +92,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-series-title-field" + "i18n": "bookmanagerv2-series-title" } }, "volume": { @@ -100,7 +100,7 @@ "type": "number", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-volume-field" + "i18n": "bookmanagerv2-volume" } }, "edition": { @@ -108,7 +108,7 @@ "type": "number", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-edition-field" + "i18n": "bookmanagerv2-edition" } }, "publisher": { @@ -116,7 +116,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-publisher-field" + "i18n": "bookmanagerv2-publisher" } }, "printer": { @@ -124,31 +124,16 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-printer-field" + "i18n": "bookmanagerv2-printer" } }, - "publication_year": { - "description": "Year of publication of the work", - "type": "integer", + "publication_date": { + "description": "Date of publication of the work", + "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-publication-year-field" - } - }, - "publication_month": { - "description": "Month of publication of the work", - "type": "integer", - "required": false, - "additionalProperties": { - "i18n": "bookmanagerv2-publication-month-field" - } - }, - "publication_day": { - "description": "Day of publication of the work", - "type": "integer", - "required": false, - "additionalProperties": { - "i18n": "bookmanagerv2-publication-day-field" + "date_format": "YMD", + "i18n": "bookmanagerv2-publication-date" } }, "publication_city": { @@ -156,7 +141,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-publication-city-field" + "i18n": "bookmanagerv2-publication-city" } }, "language": { @@ -164,7 +149,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-language-field" + "i18n": "bookmanagerv2-language" } }, "description": { @@ -172,7 +157,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-description-field" + "i18n": "bookmanagerv2-description" } }, "source": { @@ -180,7 +165,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-source-field" + "i18n": "bookmanagerv2-source" } }, "permission": { @@ -188,7 +173,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-permission-field" + "i18n": "bookmanagerv2-permission" } }, "other_versions": { @@ -202,7 +187,7 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-other-versions-field" + "i18n": "bookmanagerv2-other-versions" } }, "isbn": { @@ -210,7 +195,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-isbn-field" + "i18n": "bookmanagerv2-isbn" } }, "lccn": { @@ -218,7 +203,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-lccn-field" + "i18n": "bookmanagerv2-lccn" } }, "oclc": { @@ -226,7 +211,7 @@ "type": "string", "required": false, "additionalProperties": { - "i18n": "bookmanagerv2-oclc-field" + "i18n": "bookmanagerv2-oclc" } }, "sections": { @@ -243,7 +228,7 @@ "type": "string", "required": true, "additionalProperties": { - "i18n": "bookmanagerv2-name-field" + "i18n": "bookmanagerv2-name" } }, "link": { @@ -251,7 +236,7 @@ "type": "string", "required": true, "additionalProperties": { - "i18n": "bookmanagerv2-link-field" + "i18n": "bookmanagerv2-link" } }, "source": { @@ -259,7 +244,7 @@ "required": false, "type": "string", "additionalProperties": { - "i18n": "bookmanagerv2-source-field" + "i18n": "bookmanagerv2-source" } } }, @@ -267,10 +252,9 @@ } ], "additionalProperties": { - "i18n": "bookmanagerv2-sections-field" + "i18n": "bookmanagerv2-sections" } } }, "additionalProperties": false } - -- To view, visit https://gerrit.wikimedia.org/r/80677 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I4d9faa38b2d74daf7c787b6ca163bd4d862d7e30 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/BookManagerv2 Gerrit-Branch: master Gerrit-Owner: Mollywhite <[email protected]> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
