From 8edbfb5feb708f80d63f118f9b00eb341278f68a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bartosz=20Dziewo=C5=84ski?= Date: Mon, 23 Mar 2015 22:04:54 +0100 Subject: [PATCH] ResourceLoaderImageModule: Implement CSS selector templates Instead of a 'prefix', a 'selector' can be specified, with a string containing a simple template to use for CSS selector. For example: 'selector' => '.mw-ui-icon-{name}' 'prefix' continues to work as before. When using variants, one might want to provide separate 'selectorWithoutVariant' and 'selectorWithVariant' options. Available variables are {prefix}, {type}, {name}, and {variant}. Bug: T78215 Change-Id: I99ccaf25e8d24fed5afd0c4b770d2f389789ce4b --- .../ResourceLoaderImageModule.php | 81 +++++++++++++++++-- .../ResourceLoaderImageModuleTest.php | 46 +++++++++++ 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/includes/resourceloader/ResourceLoaderImageModule.php b/includes/resourceloader/ResourceLoaderImageModule.php index 3d65745fed..2faacd6dd4 100644 --- a/includes/resourceloader/ResourceLoaderImageModule.php +++ b/includes/resourceloader/ResourceLoaderImageModule.php @@ -38,7 +38,9 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { protected $images = array(); protected $variants = array(); - protected $prefix = array(); + protected $prefix = null; + protected $selectorWithoutVariant = '.{prefix}-{type}-{name}'; + protected $selectorWithVariant = '.{prefix}-{type}-{name}-{variant}'; protected $targets = array( 'desktop', 'mobile' ); /** @@ -57,6 +59,11 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { * 'localBasePath' => [base path], * // CSS class prefix to use in all style rules * 'prefix' => [CSS class prefix], + * // Alternatively: Format of CSS selector to use in all style rules + * 'selector' => [CSS selector template, variables: {prefix} {type} {name} {variant}], + * // Alternatively: When using variants + * 'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {type} {name}], + * 'selectorWithVariant' => [CSS selector template, variables: {prefix} {type} {name} {variant}], * // List of variants that may be used for the image files * 'variants' => array( * // ([image type] is a string, used in generated CSS class @@ -87,10 +94,29 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { public function __construct( $options = array(), $localBasePath = null ) { $this->localBasePath = self::extractLocalBasePath( $options, $localBasePath ); - if ( !isset( $options['prefix'] ) || !$options['prefix'] ) { - throw new MWException( - "Required 'prefix' option not given or empty." - ); + // Accepted combinations: + // * prefix + // * selector + // * selectorWithoutVariant + selectorWithVariant + // * prefix + selector + // * prefix + selectorWithoutVariant + selectorWithVariant + + $prefix = isset( $options['prefix'] ) && $options['prefix']; + $selector = isset( $options['selector'] ) && $options['selector']; + $selectorWithoutVariant = isset( $options['selectorWithoutVariant'] ) && $options['selectorWithoutVariant']; + $selectorWithVariant = isset( $options['selectorWithVariant'] ) && $options['selectorWithVariant']; + + if ( $selectorWithoutVariant && !$selectorWithVariant ) { + throw new MWException( "Given 'selectorWithoutVariant' but no 'selectorWithVariant'." ); + } + if ( $selectorWithVariant && !$selectorWithoutVariant ) { + throw new MWException( "Given 'selectorWithVariant' but no 'selectorWithoutVariant'." ); + } + if ( $selector && $selectorWithVariant ) { + throw new MWException( "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given." ); + } + if ( !$prefix && !$selector && !$selectorWithVariant ) { + throw new MWException( "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given." ); } foreach ( $options as $member => $option ) { @@ -121,8 +147,13 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { break; case 'prefix': + case 'selectorWithoutVariant': + case 'selectorWithVariant': $this->{$member} = (string)$option; break; + + case 'selector': + $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option; } } } @@ -135,6 +166,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { return $this->prefix; } + /** + * Get CSS selector templates used by this module. + * @return string + */ + public function getSelectors() { + return array( + 'selectorWithoutVariant' => $this->selectorWithoutVariant, + 'selectorWithVariant' => $this->selectorWithVariant, + ); + } + /** * Get a ResourceLoaderImage object for given image. * @param string $name Image name @@ -233,7 +275,10 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { // Build CSS rules $rules = array(); $script = $context->getResourceLoader()->getLoadScript( $this->getSource() ); - $prefix = $this->getPrefix(); + $selectors = $this->getSelectors(); + + $needsTypeWithoutVariant = strpos( $selectors['selectorWithoutVariant'], '{type}' ) !== false; + $needsTypeWithVariant = strpos( $selectors['selectorWithVariant'], '{type}' ) !== false; foreach ( $this->getImages() as $name => $image ) { $type = $this->getImageType( $name ); @@ -243,7 +288,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { $image->getUrl( $context, $script, null, 'rasterized' ) ); $declarations = implode( "\n\t", $declarations ); - $rules[] = ".$prefix-$type-$name {\n\t$declarations\n}"; + $selector = strtr( + $selectors['selectorWithoutVariant'], + array( + '{prefix}' => $this->getPrefix(), + '{name}' => $name, + '{variant}' => '', + // Somewhat expensive, don't compute if not needed + '{type}' => $needsTypeWithoutVariant ? $this->getImageType( $name ) : null, + ) + ); + $rules[] = "$selector {\n\t$declarations\n}"; // TODO: Get variant configurations from $context->getSkin() foreach ( $image->getVariants() as $variant ) { @@ -252,7 +307,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule { $image->getUrl( $context, $script, $variant, 'rasterized' ) ); $declarations = implode( "\n\t", $declarations ); - $rules[] = ".$prefix-$type-$name-$variant {\n\t$declarations\n}"; + $selector = strtr( + $selectors['selectorWithVariant'], + array( + '{prefix}' => $this->getPrefix(), + '{name}' => $name, + '{variant}' => $variant, + // Somewhat expensive, don't compute if not needed + '{type}' => $needsTypeWithVariant ? $this->getImageType( $name ) : null, + ) + ); + $rules[] = "$selector {\n\t$declarations\n}"; } } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php index f98f5fc847..939016bc0c 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php @@ -98,6 +98,52 @@ class ResourceLoaderImageModuleTest extends ResourceLoaderTestCase { } .oo-ui-icon-bold-invert { ... +}', + ), + array( + array( + 'class' => 'ResourceLoaderImageModule', + 'selectorWithoutVariant' => '.mw-ui-icon-{name}:after, .mw-ui-icon-{name}:before', + 'selectorWithVariant' => '.mw-ui-icon-{name}-{variant}:after, .mw-ui-icon-{name}-{variant}:before', + 'variants' => array( + 'icon' => $commonVariants, + ), + 'images' => array( + 'icon' => $commonImageData, + ), + ), + '.mw-ui-icon-advanced:after, .mw-ui-icon-advanced:before { + ... +} +.mw-ui-icon-advanced-invert:after, .mw-ui-icon-advanced-invert:before { + ... +} +.mw-ui-icon-remove:after, .mw-ui-icon-remove:before { + ... +} +.mw-ui-icon-remove-invert:after, .mw-ui-icon-remove-invert:before { + ... +} +.mw-ui-icon-remove-destructive:after, .mw-ui-icon-remove-destructive:before { + ... +} +.mw-ui-icon-next:after, .mw-ui-icon-next:before { + ... +} +.mw-ui-icon-next-invert:after, .mw-ui-icon-next-invert:before { + ... +} +.mw-ui-icon-help:after, .mw-ui-icon-help:before { + ... +} +.mw-ui-icon-help-invert:after, .mw-ui-icon-help-invert:before { + ... +} +.mw-ui-icon-bold:after, .mw-ui-icon-bold:before { + ... +} +.mw-ui-icon-bold-invert:after, .mw-ui-icon-bold-invert:before { + ... }', ), ); -- 2.20.1