Prepare to break the server side MobileFrontend dependency on Minerva

Changes:
* Minerva now maintains a MinervaUI - a simplified version of
MobileUI that provides iconClass and buttonClass helpers.
* Minerva now maintains its own ResourceLoaderParserMessageModule

Remaining issues:
* Main menu links to '#'
* Unknown dependency errors are thrown due to the missing
JS libraries e.g. mobile.watchstar
thus JS based UI components are unusable e.g. search autocomplete,
and edit button
* Language button navigates to a missing special page without
MobileFrontend (see T104660)

Bug: T169569
Change-Id: I89e2e15faabab73b0cba91afc2f2c5e785edef29
This commit is contained in:
jdlrobson 2017-08-16 14:18:08 -05:00 committed by Jdlrobson
parent 67766539f9
commit 0dd994edda
6 changed files with 186 additions and 24 deletions

View File

@ -0,0 +1,107 @@
<?php
/**
* ResourceLoaderModule subclass for Minerva
* Allows basic server side parsing of messages without arguments
*/
class MinervaResourceLoaderParsedMessageModule extends ResourceLoaderFileModule {
/** @var array Saves a list of messages which have been marked as needing parsing. */
protected $parsedMessages = [];
/** @var array Saves a list of message keys used by this module. */
protected $messages = [];
/** @var array Saves the target for the module (e.g. desktop and mobile). */
protected $targets = [ 'mobile', 'desktop' ];
/** @var boolean Whether the module abuses getScript. */
protected $hasHackedScriptMode = false;
/**
* Registers core modules and runs registration hooks.
* @param array $options List of options; if not given or empty,
* an empty module will be constructed
*/
public function __construct( $options ) {
foreach ( $options as $member => $option ) {
switch ( $member ) {
case 'messages':
$this->processMessages( $option );
$this->hasHackedScriptMode = true;
// Prevent them being reinitialised when parent construct is called.
unset( $options[$member] );
break;
}
}
parent::__construct( $options );
}
/**
* Process messages which have been marked as needing parsing
*
* @param ResourceLoaderContext $context
* @return string JavaScript code
*/
public function addParsedMessages( ResourceLoaderContext $context ) {
if ( !$this->parsedMessages ) {
return '';
}
$messages = [];
foreach ( $this->parsedMessages as $key ) {
$messages[ $key ] = $context->msg( $key )->parse();
}
return Xml::encodeJsCall( 'mw.messages.set', [ $messages ] );
}
/**
* Separate messages which have been marked as needing parsing from standard messages
* @param array $messages Array of messages to process
*/
private function processMessages( $messages ) {
foreach ( $messages as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $directive ) {
if ( $directive == 'parse' ) {
$this->parsedMessages[] = $key;
}
}
} else {
$this->messages[] = $value;
}
}
}
/**
* Gets all scripts for a given context concatenated together including processed messages
*
* @param ResourceLoaderContext $context Context in which to generate script
* @return string JavaScript code for $context
*/
public function getScript( ResourceLoaderContext $context ) {
$script = parent::getScript( $context );
return $this->addParsedMessages( $context ) . $script;
}
/**
* Get the URL or URLs to load for this module's JS in debug mode.
* @param ResourceLoaderContext $context
* @return array list of urls
* @see ResourceLoaderModule::getScriptURLsForDebug
*/
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
if ( $this->hasHackedScriptMode ) {
$derivative = new DerivativeResourceLoaderContext( $context );
$derivative->setDebug( true );
$derivative->setModules( [ $this->getName() ] );
// @todo FIXME: Make this templates and update
// makeModuleResponse so that it only outputs template code.
// When this is done you can merge with parent array and
// retain file names.
$derivative->setOnly( 'scripts' );
$rl = $derivative->getResourceLoader();
$urls = [
$rl->createLoaderURL( $this->getSource(), $derivative ),
];
} else {
$urls = parent::getScriptURLsForDebug( $context );
}
return $urls;
}
}

53
includes/MinervaUI.php Normal file
View File

@ -0,0 +1,53 @@
<?php
/**
* MinervaUI.php
*/
// FIXME: Use OOUI PHP when available.
/**
* Helper methods for generating parts of the UI.
*/
class MinervaUI {
/**
* Get CSS classes for icons
* @param string $iconName
* @param string $iconType element or before
* @param string $additionalClassNames additional class names you want to associate
* with the iconed element
* @return string class name for use with HTML element
*/
public static function iconClass( $iconName, $iconType = 'element', $additionalClassNames = '' ) {
$base = 'mw-ui-icon';
$modifiers = 'mw-ui-icon-' . $iconType;
if ( $iconName ) {
$modifiers .= ' mw-ui-icon-' . $iconName;
}
return $base . ' ' . $modifiers . ' ' . $additionalClassNames;
}
/**
* Get CSS classes for a mediawiki ui semantic element
* @param string $base The base class
* @param string $modifier Type of anchor (progressive, constructive, destructive)
* @param string $additionalClassNames additional class names you want to associate
* with the iconed element
* @return string class name for use with HTML element
*/
public static function semanticClass( $base, $modifier, $additionalClassNames = '' ) {
$modifier = empty( $modifier ) ? '' : 'mw-ui-' . $modifier;
return $base . ' ' . $modifier . ' ' . $additionalClassNames;
}
/**
* Get CSS classes for buttons
* @param string $modifier Type of button (progressive, constructive, destructive)
* @param string $additionalClassNames additional class names you want to associate
* with the button element
* @return string class name for use with HTML element
*/
public static function buttonClass( $modifier = '', $additionalClassNames = '' ) {
return self::semanticClass( 'mw-ui-button', $modifier, $additionalClassNames );
}
}

View File

@ -109,8 +109,8 @@ class MinervaTemplate extends BaseTemplate {
if ( isset( $data['historyLink'] ) && $action === 'view' ) {
$historyLink = $data['historyLink'];
$args = [
'clockIconClass' => MobileUI::iconClass( 'clock-gray', 'before' ),
'arrowIconClass' => MobileUI::iconClass(
'clockIconClass' => MinervaUI::iconClass( 'clock-gray', 'before' ),
'arrowIconClass' => MinervaUI::iconClass(
'arrow-gray', 'element', 'mw-ui-icon-small mf-mw-ui-icon-rotate-anti-clockwise indicator' ),
'isMainPage' => $this->getSkin()->getTitle()->isMainPage(),
'link' => $historyLink['href'],
@ -146,7 +146,7 @@ class MinervaTemplate extends BaseTemplate {
* @return string
*/
protected function getSecondaryActionsHtml() {
$baseClass = MobileUI::buttonClass( '', 'button' );
$baseClass = MinervaUI::buttonClass( '', 'button' );
$html = Html::openElement( 'div', [
'class' => 'post-content',
'id' => 'page-secondary-actions'
@ -281,7 +281,7 @@ class MinervaTemplate extends BaseTemplate {
// which is problematic in Opera Mini (see T140490)
'searchButton' => Html::rawElement( 'button', [
'id' => 'searchIcon',
'class' => MobileUI::iconClass( 'magnifying-glass', 'element' ),
'class' => MinervaUI::iconClass( 'magnifying-glass', 'element' ),
], wfMessage( 'searchbutton' ) ),
'secondaryButtonData' => $data['secondaryButtonData'],
'mainmenuhtml' => $this->getMainMenuHtml( $data ),

View File

@ -236,7 +236,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
'data-section' => $section,
// Note visibility of the edit section link button is controlled by .edit-page in ui.less so
// we default to enabled even though this may not be true.
'class' => MobileUI::iconClass( 'edit-enabled', 'element', 'edit-page' ),
'class' => MinervaUI::iconClass( 'edit-enabled', 'element', 'edit-page' ),
], $message );
$html .= Html::closeElement( 'span' );
return $html;
@ -408,7 +408,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
[ 'returnto' => $currentTitle->getPrefixedText() ] );
$tpl->set( 'secondaryButtonData', [
'notificationIconClass' => MobileUI::iconClass( 'notifications' ),
'notificationIconClass' => MinervaUI::iconClass( 'notifications' ),
'title' => $notificationsMsg,
'url' => $url,
'notificationCount' => $countLabel,
@ -452,7 +452,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
->addComponent(
$this->msg( 'mobile-frontend-main-menu-contributions' )->escaped(),
SpecialPage::getTitleFor( 'Contributions', $user->getName() )->getLocalUrl(),
MobileUI::iconClass( 'mf-contributions', 'before' ),
MinervaUI::iconClass( 'mf-contributions', 'before' ),
[ 'data-event-name' => 'contributions' ]
);
}
@ -490,7 +490,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
'mobile-frontend-watchlist-purpose',
$watchlistQuery
),
MobileUI::iconClass( 'mf-watchlist', 'before' ),
MinervaUI::iconClass( 'mf-watchlist', 'before' ),
[ 'data-event-name' => 'watchlist' ]
);
}
@ -512,7 +512,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
$this->msg( 'mobile-frontend-main-menu-settings' )->escaped(),
SpecialPage::getTitleFor( 'MobileOptions' )->
getLocalUrl( [ 'returnto' => $returnToTitle ] ),
MobileUI::iconClass( 'mf-settings', 'before' ),
MinervaUI::iconClass( 'mf-settings', 'before' ),
[ 'data-event-name' => 'settings' ]
);
@ -527,7 +527,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
SpecialPage::getTitleFor( 'Preferences' ),
'prefsnologintext2'
),
MobileUI::iconClass( 'mf-settings', 'before' ),
MinervaUI::iconClass( 'mf-settings', 'before' ),
[ 'data-event-name' => 'preferences' ]
);
}
@ -613,7 +613,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
->addComponent(
$this->msg( 'mobile-frontend-home-button' )->escaped(),
Title::newMainPage()->getLocalUrl(),
MobileUI::iconClass( 'mf-home', 'before' ),
MinervaUI::iconClass( 'mf-home', 'before' ),
[ 'data-event-name' => 'home' ]
);
@ -622,7 +622,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
->addComponent(
$this->msg( 'mobile-frontend-random-button' )->escaped(),
SpecialPage::getTitleFor( 'Randompage' )->getLocalUrl() . '#/random',
MobileUI::iconClass( 'mf-random', 'before' ),
MinervaUI::iconClass( 'mf-random', 'before' ),
[
'id' => 'randomButton',
'data-event-name' => 'random',
@ -639,7 +639,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
->addComponent(
$this->msg( 'mobile-frontend-main-menu-nearby' )->escaped(),
SpecialPage::getTitleFor( 'Nearby' )->getLocalURL(),
MobileUI::iconClass( 'mf-nearby', 'before', 'nearby' ),
MinervaUI::iconClass( 'mf-nearby', 'before', 'nearby' ),
[ 'data-event-name' => 'nearby' ]
);
}
@ -689,13 +689,13 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
->addComponent(
$username,
Title::newFromText( $username, NS_USER )->getLocalUrl(),
MobileUI::iconClass( 'mf-profile', 'before', 'truncated-text primary-action' ),
MinervaUI::iconClass( 'mf-profile', 'before', 'truncated-text primary-action' ),
[ 'data-event-name' => 'profile' ]
)
->addComponent(
$this->msg( 'mobile-frontend-main-menu-logout' )->escaped(),
$url,
MobileUI::iconClass(
MinervaUI::iconClass(
'mf-logout', 'element', 'secondary-action truncated-text' ),
[ 'data-event-name' => 'logout' ]
);
@ -711,7 +711,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
->addComponent(
$this->msg( 'mobile-frontend-main-menu-login' )->escaped(),
$url,
MobileUI::iconClass( 'mf-anonymous', 'before' ),
MinervaUI::iconClass( 'mf-anonymous', 'before' ),
[ 'data-event-name' => 'login' ]
);
}
@ -913,7 +913,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
Html::element( 'a', [
'title' => $this->msg( 'mobile-frontend-main-menu-button-tooltip' ),
'href' => $url,
'class' => MobileUI::iconClass( 'mainmenu', 'element', 'main-menu-button' ),
'class' => MinervaUI::iconClass( 'mainmenu', 'element', 'main-menu-button' ),
'id' => 'mw-mf-main-menu-button',
], $this->msg( 'mobile-frontend-main-menu-button-tooltip' ) )
);
@ -1105,7 +1105,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
'id' => 'ca-edit',
'text' => '',
'itemtitle' => $this->msg( 'mobile-frontend-pageaction-edit-tooltip' ),
'class' => MobileUI::iconClass( 'edit-enabled', 'element' ),
'class' => MinervaUI::iconClass( 'edit-enabled', 'element' ),
'links' => [
'edit' => [
'href' => $title->getLocalURL( $editArgs )
@ -1127,7 +1127,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
$baseResult = [
'id' => 'ca-watch',
// Use blank icon to reserve space for watchstar icon once JS loads
'class' => MobileUI::iconClass( '', 'element', 'watch-this-article' ),
'class' => MinervaUI::iconClass( '', 'element', 'watch-this-article' ),
'is_js_only' => true
];
$title = $this->getTitle();
@ -1170,7 +1170,7 @@ class SkinMinerva extends SkinTemplate implements ICustomizableSkin {
return [
'text' => '',
'itemtitle' => $this->msg( 'mobile-frontend-language-article-heading' ),
'class' => MobileUI::iconClass( 'language-switcher', 'element', $languageSwitcherClasses ),
'class' => MinervaUI::iconClass( 'language-switcher', 'element', $languageSwitcherClasses ),
'links' => $languageSwitcherLinks,
'is_js_only' => false
];

View File

@ -1,5 +1,7 @@
{
"AutoloadClasses": {
"MinervaResourceLoaderParsedMessageModule": "includes/MinervaResourceLoaderParsedMessageModule.php",
"MinervaUI": "includes/MinervaUI.php",
"MinervaHooks": "includes/Minerva.hooks.php",
"MinervaTemplate": "includes/skins/MinervaTemplate.php",
"SkinMinerva": "includes/skins/SkinMinerva.php",
@ -350,7 +352,7 @@
]
},
"skins.minerva.editor": {
"class": "MFResourceLoaderParsedMessageModule",
"class": "MinervaResourceLoaderParsedMessageModule",
"dependencies": [
"mediawiki.util",
"mediawiki.router",
@ -426,7 +428,7 @@
]
},
"skins.minerva.toggling": {
"class": "MFResourceLoaderParsedMessageModule",
"class": "MinervaResourceLoaderParsedMessageModule",
"dependencies": [
"mobile.toggle",
"skins.minerva.icons.images.variants",

View File

@ -3,7 +3,7 @@
namespace Tests\MediaWiki\Minerva;
use MediaWikiTestCase;
use MobileUI;
use MinervaUI;
use MWTimestamp;
use OutputPage;
use QuickTemplate;
@ -289,7 +289,7 @@ class SkinMinervaTest extends MediaWikiTestCase {
$hasUnseen
) {
return [
'notificationIconClass' => MobileUI::iconClass( 'notifications' ),
'notificationIconClass' => MinervaUI::iconClass( 'notifications' ),
'title' => $notificationsMsg,
'url' => SpecialPage::getTitleFor( $notificationsTitle )
->getLocalURL(